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
package/index.old.js
ADDED
|
@@ -0,0 +1,4137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AI Agent Guardrails - MCP Server
|
|
5
|
+
* Version: 1.0.0
|
|
6
|
+
*
|
|
7
|
+
* Professional Model Context Protocol server for AI development environments.
|
|
8
|
+
* Compatible with Cursor, Claude Desktop, VS Code, Windsurf, and other MCP-enabled editors.
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Project validation and architecture analysis
|
|
12
|
+
* - Design system enforcement
|
|
13
|
+
* - API endpoint registration
|
|
14
|
+
* - Knowledge base building
|
|
15
|
+
* - Semantic code search
|
|
16
|
+
* - Change impact analysis
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
20
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
21
|
+
import {
|
|
22
|
+
CallToolRequestSchema,
|
|
23
|
+
ListToolsRequestSchema,
|
|
24
|
+
ListResourcesRequestSchema,
|
|
25
|
+
ReadResourceRequestSchema,
|
|
26
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
27
|
+
import fs from "fs/promises";
|
|
28
|
+
import path from "path";
|
|
29
|
+
import { fileURLToPath } from "url";
|
|
30
|
+
import { execSync } from "child_process";
|
|
31
|
+
|
|
32
|
+
// Import premium tools
|
|
33
|
+
import { PREMIUM_TOOLS, handlePremiumTool } from "./premium-tools.js";
|
|
34
|
+
|
|
35
|
+
// Import hygiene tools
|
|
36
|
+
import { createRequire } from "module";
|
|
37
|
+
const require = createRequire(import.meta.url);
|
|
38
|
+
const {
|
|
39
|
+
hygieneTools,
|
|
40
|
+
hygieneFullScan,
|
|
41
|
+
hygieneDuplicates,
|
|
42
|
+
hygieneUnused,
|
|
43
|
+
hygieneErrors,
|
|
44
|
+
hygieneRootCleanup,
|
|
45
|
+
hygieneDeletionPlan,
|
|
46
|
+
} = require("./hygiene-tools.js");
|
|
47
|
+
|
|
48
|
+
// Professional logging system
|
|
49
|
+
class Logger {
|
|
50
|
+
constructor(debug = false) {
|
|
51
|
+
this.debug = debug;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
log(level, message, data = null) {
|
|
55
|
+
const timestamp = new Date().toISOString();
|
|
56
|
+
const logEntry = `[${timestamp}] [${level}] [GUARDRAIL MCP] ${message}`;
|
|
57
|
+
|
|
58
|
+
if (level === "ERROR") {
|
|
59
|
+
console.error(logEntry);
|
|
60
|
+
if (data) console.error(JSON.stringify(data, null, 2));
|
|
61
|
+
} else if (this.debug) {
|
|
62
|
+
console.error(logEntry);
|
|
63
|
+
if (data) console.error(JSON.stringify(data, null, 2));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
info(message, data) {
|
|
68
|
+
this.log("INFO", message, data);
|
|
69
|
+
}
|
|
70
|
+
warn(message, data) {
|
|
71
|
+
this.log("WARN", message, data);
|
|
72
|
+
}
|
|
73
|
+
error(message, data) {
|
|
74
|
+
this.log("ERROR", message, data);
|
|
75
|
+
}
|
|
76
|
+
debug(message, data) {
|
|
77
|
+
this.log("DEBUG", message, data);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
82
|
+
const __dirname = path.dirname(__filename);
|
|
83
|
+
|
|
84
|
+
class GuardrailsMCPServer {
|
|
85
|
+
constructor() {
|
|
86
|
+
// Initialize logger
|
|
87
|
+
this.logger = new Logger(process.env.GUARDRAIL_DEBUG === "true");
|
|
88
|
+
|
|
89
|
+
this.server = new Server(
|
|
90
|
+
{
|
|
91
|
+
name: "GUARDRAIL-ai",
|
|
92
|
+
version: "1.0.0",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
capabilities: {
|
|
96
|
+
tools: {},
|
|
97
|
+
resources: {},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
this.setupHandlers();
|
|
103
|
+
this.setupErrorHandling();
|
|
104
|
+
this.logger.info("GUARDRAIL MCP Server initialized");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
setupHandlers() {
|
|
108
|
+
// List available tools with professional descriptions and categorization
|
|
109
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
110
|
+
tools: [
|
|
111
|
+
// 🏗️ Project Analysis & Validation
|
|
112
|
+
{
|
|
113
|
+
name: "validate_project",
|
|
114
|
+
description:
|
|
115
|
+
"🔍 Comprehensive project validation - checks structure, API endpoints, and identifies mock data usage",
|
|
116
|
+
inputSchema: {
|
|
117
|
+
type: "object",
|
|
118
|
+
properties: {
|
|
119
|
+
projectPath: {
|
|
120
|
+
type: "string",
|
|
121
|
+
description: "Path to project root directory",
|
|
122
|
+
default: ".",
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: "check_project_drift",
|
|
129
|
+
description:
|
|
130
|
+
"📊 Detect architectural drift - analyzes if project structure has deviated from intended patterns",
|
|
131
|
+
inputSchema: {
|
|
132
|
+
type: "object",
|
|
133
|
+
properties: {
|
|
134
|
+
projectPath: {
|
|
135
|
+
type: "string",
|
|
136
|
+
description: "Path to project root directory",
|
|
137
|
+
default: ".",
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: "get_project_health",
|
|
144
|
+
description:
|
|
145
|
+
"💯 Generate project health score with actionable recommendations (Professional feature)",
|
|
146
|
+
inputSchema: {
|
|
147
|
+
type: "object",
|
|
148
|
+
properties: {
|
|
149
|
+
projectPath: {
|
|
150
|
+
type: "string",
|
|
151
|
+
description: "Path to project root directory",
|
|
152
|
+
default: ".",
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
// 🎨 Design System Management
|
|
158
|
+
{
|
|
159
|
+
name: "check_design_system",
|
|
160
|
+
description:
|
|
161
|
+
"🎨 Validate components against locked design system - ensures visual consistency",
|
|
162
|
+
inputSchema: {
|
|
163
|
+
type: "object",
|
|
164
|
+
properties: {
|
|
165
|
+
projectPath: {
|
|
166
|
+
type: "string",
|
|
167
|
+
description: "Path to project root directory",
|
|
168
|
+
default: ".",
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: "setup_design_system",
|
|
175
|
+
description:
|
|
176
|
+
"🔧 Initialize and lock a professional design system for your project",
|
|
177
|
+
inputSchema: {
|
|
178
|
+
type: "object",
|
|
179
|
+
properties: {
|
|
180
|
+
projectPath: {
|
|
181
|
+
type: "string",
|
|
182
|
+
description: "Path to project root directory",
|
|
183
|
+
default: ".",
|
|
184
|
+
},
|
|
185
|
+
theme: {
|
|
186
|
+
type: "string",
|
|
187
|
+
enum: ["modern", "dark", "elegant", "minimal", "corporate"],
|
|
188
|
+
description: "Pre-built theme to apply",
|
|
189
|
+
default: "modern",
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
// 🔌 API Management
|
|
195
|
+
{
|
|
196
|
+
name: "register_api_endpoint",
|
|
197
|
+
description:
|
|
198
|
+
"📝 Register new API endpoints to prevent mock data usage and maintain API integrity",
|
|
199
|
+
inputSchema: {
|
|
200
|
+
type: "object",
|
|
201
|
+
properties: {
|
|
202
|
+
projectPath: {
|
|
203
|
+
type: "string",
|
|
204
|
+
description: "Path to project root directory",
|
|
205
|
+
default: ".",
|
|
206
|
+
},
|
|
207
|
+
path: {
|
|
208
|
+
type: "string",
|
|
209
|
+
description: "API endpoint path (e.g., /api/users)",
|
|
210
|
+
},
|
|
211
|
+
method: {
|
|
212
|
+
type: "string",
|
|
213
|
+
enum: ["GET", "POST", "PUT", "DELETE", "PATCH"],
|
|
214
|
+
description: "HTTP method",
|
|
215
|
+
},
|
|
216
|
+
description: {
|
|
217
|
+
type: "string",
|
|
218
|
+
description: "Detailed endpoint description",
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
required: ["path", "method"],
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
name: "get_guardrails_rules",
|
|
226
|
+
description:
|
|
227
|
+
"📋 Retrieve current guardrails rules and project constraints",
|
|
228
|
+
inputSchema: {
|
|
229
|
+
type: "object",
|
|
230
|
+
properties: {
|
|
231
|
+
projectPath: {
|
|
232
|
+
type: "string",
|
|
233
|
+
description: "Path to project root directory",
|
|
234
|
+
default: ".",
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
// 🧠 Intelligence & Knowledge
|
|
240
|
+
{
|
|
241
|
+
name: "architect_analyze",
|
|
242
|
+
description:
|
|
243
|
+
"🏗️ Intelligent project analysis - understands context and recommends optimal implementation order",
|
|
244
|
+
inputSchema: {
|
|
245
|
+
type: "object",
|
|
246
|
+
properties: {
|
|
247
|
+
projectPath: {
|
|
248
|
+
type: "string",
|
|
249
|
+
description: "Path to project root directory",
|
|
250
|
+
default: ".",
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
name: "architect_apply",
|
|
257
|
+
description:
|
|
258
|
+
"⚡ Automatically apply recommended templates with dependency resolution",
|
|
259
|
+
inputSchema: {
|
|
260
|
+
type: "object",
|
|
261
|
+
properties: {
|
|
262
|
+
projectPath: {
|
|
263
|
+
type: "string",
|
|
264
|
+
description: "Path to project root directory",
|
|
265
|
+
default: ".",
|
|
266
|
+
},
|
|
267
|
+
autoApply: {
|
|
268
|
+
type: "boolean",
|
|
269
|
+
description:
|
|
270
|
+
"Automatically apply critical templates without confirmation",
|
|
271
|
+
default: true,
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: "build_knowledge_base",
|
|
278
|
+
description:
|
|
279
|
+
"🧠 Build deep codebase knowledge - analyzes architecture, patterns, and relationships",
|
|
280
|
+
inputSchema: {
|
|
281
|
+
type: "object",
|
|
282
|
+
properties: {
|
|
283
|
+
projectPath: {
|
|
284
|
+
type: "string",
|
|
285
|
+
description: "Path to project root directory",
|
|
286
|
+
default: ".",
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
name: "get_deep_context",
|
|
293
|
+
description:
|
|
294
|
+
"💬 Get project-specific answers using knowledge base with customizable response styles",
|
|
295
|
+
inputSchema: {
|
|
296
|
+
type: "object",
|
|
297
|
+
properties: {
|
|
298
|
+
projectPath: {
|
|
299
|
+
type: "string",
|
|
300
|
+
description: "Path to project root directory",
|
|
301
|
+
default: ".",
|
|
302
|
+
},
|
|
303
|
+
query: {
|
|
304
|
+
type: "string",
|
|
305
|
+
description:
|
|
306
|
+
'Question about your codebase (e.g., "How is authentication implemented?")',
|
|
307
|
+
},
|
|
308
|
+
style: {
|
|
309
|
+
type: "string",
|
|
310
|
+
enum: [
|
|
311
|
+
"blunt",
|
|
312
|
+
"excited",
|
|
313
|
+
"strict",
|
|
314
|
+
"friendly",
|
|
315
|
+
"professional",
|
|
316
|
+
"casual",
|
|
317
|
+
"technical",
|
|
318
|
+
"encouraging",
|
|
319
|
+
"concise",
|
|
320
|
+
"detailed",
|
|
321
|
+
],
|
|
322
|
+
description:
|
|
323
|
+
"Response tone: blunt (direct), excited (energetic), strict (formal), friendly (warm), professional (business), casual (relaxed), technical (precise), encouraging (supportive), concise (brief), detailed (thorough)",
|
|
324
|
+
default: "professional",
|
|
325
|
+
},
|
|
326
|
+
useEmojis: {
|
|
327
|
+
type: "boolean",
|
|
328
|
+
description:
|
|
329
|
+
"Include emojis in responses for better engagement",
|
|
330
|
+
default: true,
|
|
331
|
+
},
|
|
332
|
+
includeExamples: {
|
|
333
|
+
type: "boolean",
|
|
334
|
+
description: "Include code examples in recommendations",
|
|
335
|
+
default: false,
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
required: ["query"],
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
// 🔍 Code Analysis & Search
|
|
342
|
+
{
|
|
343
|
+
name: "semantic_search",
|
|
344
|
+
description:
|
|
345
|
+
"🔍 Semantic code search - find code by meaning, not just text matching",
|
|
346
|
+
inputSchema: {
|
|
347
|
+
type: "object",
|
|
348
|
+
properties: {
|
|
349
|
+
projectPath: {
|
|
350
|
+
type: "string",
|
|
351
|
+
description: "Path to project root directory",
|
|
352
|
+
default: ".",
|
|
353
|
+
},
|
|
354
|
+
query: {
|
|
355
|
+
type: "string",
|
|
356
|
+
description:
|
|
357
|
+
'Describe what you\'re looking for (e.g., "authentication middleware", "user validation logic")',
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
required: ["query"],
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
name: "analyze_change_impact",
|
|
365
|
+
description:
|
|
366
|
+
"💥 Analyze impact of changes - identifies dependencies and potential breaking changes",
|
|
367
|
+
inputSchema: {
|
|
368
|
+
type: "object",
|
|
369
|
+
properties: {
|
|
370
|
+
projectPath: {
|
|
371
|
+
type: "string",
|
|
372
|
+
description: "Path to project root directory",
|
|
373
|
+
default: ".",
|
|
374
|
+
},
|
|
375
|
+
file: {
|
|
376
|
+
type: "string",
|
|
377
|
+
description: "File path to analyze for impact",
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
required: ["file"],
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
name: "generate_code_context",
|
|
385
|
+
description:
|
|
386
|
+
"⚙️ Generate code prompts that follow your project's patterns and conventions",
|
|
387
|
+
inputSchema: {
|
|
388
|
+
type: "object",
|
|
389
|
+
properties: {
|
|
390
|
+
projectPath: {
|
|
391
|
+
type: "string",
|
|
392
|
+
description: "Path to project root directory",
|
|
393
|
+
default: ".",
|
|
394
|
+
},
|
|
395
|
+
task: {
|
|
396
|
+
type: "string",
|
|
397
|
+
description:
|
|
398
|
+
'Describe what code to generate (e.g., "Create a user authentication hook with TypeScript")',
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
required: ["task"],
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
// 🛡️ Security Orchestrator
|
|
405
|
+
{
|
|
406
|
+
name: "security_scan",
|
|
407
|
+
description:
|
|
408
|
+
"🛡️ Full security scan - runs policy checks, SAST, supply chain analysis, and secret detection",
|
|
409
|
+
inputSchema: {
|
|
410
|
+
type: "object",
|
|
411
|
+
properties: {
|
|
412
|
+
projectPath: {
|
|
413
|
+
type: "string",
|
|
414
|
+
description: "Path to project root directory",
|
|
415
|
+
default: ".",
|
|
416
|
+
},
|
|
417
|
+
environment: {
|
|
418
|
+
type: "string",
|
|
419
|
+
enum: ["development", "staging", "production"],
|
|
420
|
+
description: "Target environment for security checks",
|
|
421
|
+
default: "production",
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
name: "policy_check",
|
|
428
|
+
description:
|
|
429
|
+
"📋 Run policy checks - validates banned patterns, mock data, localhost URLs, and env vars",
|
|
430
|
+
inputSchema: {
|
|
431
|
+
type: "object",
|
|
432
|
+
properties: {
|
|
433
|
+
projectPath: {
|
|
434
|
+
type: "string",
|
|
435
|
+
description: "Path to project root directory",
|
|
436
|
+
default: ".",
|
|
437
|
+
},
|
|
438
|
+
environment: {
|
|
439
|
+
type: "string",
|
|
440
|
+
enum: ["development", "staging", "production"],
|
|
441
|
+
description: "Target environment",
|
|
442
|
+
default: "production",
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
name: "secret_scan",
|
|
449
|
+
description:
|
|
450
|
+
"🔐 Scan for secrets - detects API keys, tokens, passwords, and credentials in code",
|
|
451
|
+
inputSchema: {
|
|
452
|
+
type: "object",
|
|
453
|
+
properties: {
|
|
454
|
+
projectPath: {
|
|
455
|
+
type: "string",
|
|
456
|
+
description: "Path to project root directory",
|
|
457
|
+
default: ".",
|
|
458
|
+
},
|
|
459
|
+
scanHistory: {
|
|
460
|
+
type: "boolean",
|
|
461
|
+
description: "Also scan git history for leaked secrets",
|
|
462
|
+
default: false,
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
name: "supply_chain_scan",
|
|
469
|
+
description:
|
|
470
|
+
"📦 Supply chain analysis - generates SBOM, scans vulnerabilities, checks licenses",
|
|
471
|
+
inputSchema: {
|
|
472
|
+
type: "object",
|
|
473
|
+
properties: {
|
|
474
|
+
projectPath: {
|
|
475
|
+
type: "string",
|
|
476
|
+
description: "Path to project root directory",
|
|
477
|
+
default: ".",
|
|
478
|
+
},
|
|
479
|
+
repoUrl: {
|
|
480
|
+
type: "string",
|
|
481
|
+
description: "GitHub repo URL for OpenSSF Scorecard (optional)",
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
name: "ship_check",
|
|
488
|
+
description:
|
|
489
|
+
"🚀 Ship readiness check - MockProof build gate to ensure no fakes in production",
|
|
490
|
+
inputSchema: {
|
|
491
|
+
type: "object",
|
|
492
|
+
properties: {
|
|
493
|
+
projectPath: {
|
|
494
|
+
type: "string",
|
|
495
|
+
description: "Path to project root directory",
|
|
496
|
+
default: ".",
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
},
|
|
500
|
+
},
|
|
501
|
+
// 🔗 API Endpoint Audit
|
|
502
|
+
{
|
|
503
|
+
name: "audit_api_endpoints",
|
|
504
|
+
description:
|
|
505
|
+
"🔗 Audit API endpoints - identifies disconnected endpoints, missing backend implementations, and unused routes",
|
|
506
|
+
inputSchema: {
|
|
507
|
+
type: "object",
|
|
508
|
+
properties: {
|
|
509
|
+
projectPath: {
|
|
510
|
+
type: "string",
|
|
511
|
+
description: "Path to project root directory",
|
|
512
|
+
default: ".",
|
|
513
|
+
},
|
|
514
|
+
showDetails: {
|
|
515
|
+
type: "boolean",
|
|
516
|
+
description: "Show detailed endpoint lists",
|
|
517
|
+
default: false,
|
|
518
|
+
},
|
|
519
|
+
},
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
name: "get_deploy_verdict",
|
|
524
|
+
description:
|
|
525
|
+
"🚦 Get deploy verdict - returns ship/no-ship decision with blockers and risk score",
|
|
526
|
+
inputSchema: {
|
|
527
|
+
type: "object",
|
|
528
|
+
properties: {
|
|
529
|
+
projectPath: {
|
|
530
|
+
type: "string",
|
|
531
|
+
description: "Path to project root directory",
|
|
532
|
+
default: ".",
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
},
|
|
536
|
+
},
|
|
537
|
+
// 🛡️ Production Integrity Suite
|
|
538
|
+
{
|
|
539
|
+
name: "production_integrity_check",
|
|
540
|
+
description:
|
|
541
|
+
"🛡️ Full production integrity audit - combines API wiring, auth coverage, secrets, routes, and mock detection into one comprehensive report with integrity score",
|
|
542
|
+
inputSchema: {
|
|
543
|
+
type: "object",
|
|
544
|
+
properties: {
|
|
545
|
+
projectPath: {
|
|
546
|
+
type: "string",
|
|
547
|
+
description: "Path to project root directory",
|
|
548
|
+
default: ".",
|
|
549
|
+
},
|
|
550
|
+
},
|
|
551
|
+
},
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
name: "audit_auth_coverage",
|
|
555
|
+
description:
|
|
556
|
+
"🔐 Auth/RBAC coverage scanner - detects unprotected endpoints, missing auth middleware, exposed admin routes, and missing role checks",
|
|
557
|
+
inputSchema: {
|
|
558
|
+
type: "object",
|
|
559
|
+
properties: {
|
|
560
|
+
projectPath: {
|
|
561
|
+
type: "string",
|
|
562
|
+
description: "Path to project root directory",
|
|
563
|
+
default: ".",
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
},
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
name: "audit_env_secrets",
|
|
570
|
+
description:
|
|
571
|
+
"🔑 Environment & secrets audit - detects hardcoded secrets, leaked NEXT_PUBLIC vars, missing env vars, and generates .env.example",
|
|
572
|
+
inputSchema: {
|
|
573
|
+
type: "object",
|
|
574
|
+
properties: {
|
|
575
|
+
projectPath: {
|
|
576
|
+
type: "string",
|
|
577
|
+
description: "Path to project root directory",
|
|
578
|
+
default: ".",
|
|
579
|
+
},
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
name: "audit_route_integrity",
|
|
585
|
+
description:
|
|
586
|
+
"🗺️ Route integrity scanner - finds dead links, unused pages, placeholder content, and feature flags that may hide sections",
|
|
587
|
+
inputSchema: {
|
|
588
|
+
type: "object",
|
|
589
|
+
properties: {
|
|
590
|
+
projectPath: {
|
|
591
|
+
type: "string",
|
|
592
|
+
description: "Path to project root directory",
|
|
593
|
+
default: ".",
|
|
594
|
+
},
|
|
595
|
+
},
|
|
596
|
+
},
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
name: "audit_mock_blocker",
|
|
600
|
+
description:
|
|
601
|
+
"🚫 Mock/stub ship blocker - detects test imports, mock code, debug statements, and test credentials that shouldn't ship to production",
|
|
602
|
+
inputSchema: {
|
|
603
|
+
type: "object",
|
|
604
|
+
properties: {
|
|
605
|
+
projectPath: {
|
|
606
|
+
type: "string",
|
|
607
|
+
description: "Path to project root directory",
|
|
608
|
+
default: ".",
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
},
|
|
612
|
+
},
|
|
613
|
+
// 🔮 Reality Check - The Ultimate Truth Detector
|
|
614
|
+
{
|
|
615
|
+
name: "reality_check",
|
|
616
|
+
description:
|
|
617
|
+
"🔮 Reality Check - The ultimate production truth detector. Runs comprehensive integrity audit: API wiring, auth coverage, secrets, routes, mock code detection, plus code-level self-deception analysis. Returns full report with integrity score and ship/no-ship verdict.",
|
|
618
|
+
inputSchema: {
|
|
619
|
+
type: "object",
|
|
620
|
+
properties: {
|
|
621
|
+
projectPath: {
|
|
622
|
+
type: "string",
|
|
623
|
+
description: "Path to project root directory",
|
|
624
|
+
default: ".",
|
|
625
|
+
},
|
|
626
|
+
mode: {
|
|
627
|
+
type: "string",
|
|
628
|
+
enum: ["full", "quick", "code-only"],
|
|
629
|
+
description:
|
|
630
|
+
"full = complete production audit (default), quick = summary only, code-only = just analyze specific file",
|
|
631
|
+
default: "full",
|
|
632
|
+
},
|
|
633
|
+
file: {
|
|
634
|
+
type: "string",
|
|
635
|
+
description: "Specific file to analyze (for code-only mode)",
|
|
636
|
+
},
|
|
637
|
+
},
|
|
638
|
+
},
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
name: "reality_check_deep",
|
|
642
|
+
description:
|
|
643
|
+
"🔮 Deep Reality Check (Pro) - Cross-file analysis, call graph tracing, async lifecycle analysis, and AI intent verification",
|
|
644
|
+
inputSchema: {
|
|
645
|
+
type: "object",
|
|
646
|
+
properties: {
|
|
647
|
+
projectPath: {
|
|
648
|
+
type: "string",
|
|
649
|
+
description: "Path to project root directory",
|
|
650
|
+
default: ".",
|
|
651
|
+
},
|
|
652
|
+
file: {
|
|
653
|
+
type: "string",
|
|
654
|
+
description: "File path to analyze",
|
|
655
|
+
},
|
|
656
|
+
includeCallGraph: {
|
|
657
|
+
type: "boolean",
|
|
658
|
+
description: "Include call graph analysis",
|
|
659
|
+
default: true,
|
|
660
|
+
},
|
|
661
|
+
includeAsyncAnalysis: {
|
|
662
|
+
type: "boolean",
|
|
663
|
+
description: "Include async/await lifecycle analysis",
|
|
664
|
+
default: true,
|
|
665
|
+
},
|
|
666
|
+
},
|
|
667
|
+
required: ["file"],
|
|
668
|
+
},
|
|
669
|
+
},
|
|
670
|
+
// 🤖 AI-Enhanced Production Integrity
|
|
671
|
+
{
|
|
672
|
+
name: "ai_production_integrity",
|
|
673
|
+
description:
|
|
674
|
+
"🤖 AI-Enhanced Production Integrity - Uses LLM to analyze findings, generate intelligent fix suggestions, explain security risks, and provide prioritized action items with business impact analysis",
|
|
675
|
+
inputSchema: {
|
|
676
|
+
type: "object",
|
|
677
|
+
properties: {
|
|
678
|
+
projectPath: {
|
|
679
|
+
type: "string",
|
|
680
|
+
description: "Path to project root directory",
|
|
681
|
+
default: ".",
|
|
682
|
+
},
|
|
683
|
+
enableAI: {
|
|
684
|
+
type: "boolean",
|
|
685
|
+
description:
|
|
686
|
+
"Enable AI-powered analysis (requires OPENAI_API_KEY)",
|
|
687
|
+
default: true,
|
|
688
|
+
},
|
|
689
|
+
},
|
|
690
|
+
},
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
name: "ai_explain_finding",
|
|
694
|
+
description:
|
|
695
|
+
"🧠 AI Explain Finding - Get detailed AI explanation of a security finding including attack scenarios, compliance impact, and step-by-step fix with code examples",
|
|
696
|
+
inputSchema: {
|
|
697
|
+
type: "object",
|
|
698
|
+
properties: {
|
|
699
|
+
finding: {
|
|
700
|
+
type: "object",
|
|
701
|
+
description: "The finding object to explain",
|
|
702
|
+
properties: {
|
|
703
|
+
category: { type: "string" },
|
|
704
|
+
severity: { type: "string" },
|
|
705
|
+
title: { type: "string" },
|
|
706
|
+
description: { type: "string" },
|
|
707
|
+
file: { type: "string" },
|
|
708
|
+
code: { type: "string" },
|
|
709
|
+
},
|
|
710
|
+
required: ["category", "title"],
|
|
711
|
+
},
|
|
712
|
+
context: {
|
|
713
|
+
type: "string",
|
|
714
|
+
description: "Additional context about the codebase",
|
|
715
|
+
},
|
|
716
|
+
},
|
|
717
|
+
required: ["finding"],
|
|
718
|
+
},
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
name: "ai_generate_fix",
|
|
722
|
+
description:
|
|
723
|
+
"🔧 AI Generate Fix - Generate AI-powered code fix for a specific finding with step-by-step instructions and working code example",
|
|
724
|
+
inputSchema: {
|
|
725
|
+
type: "object",
|
|
726
|
+
properties: {
|
|
727
|
+
finding: {
|
|
728
|
+
type: "object",
|
|
729
|
+
description: "The finding to fix",
|
|
730
|
+
properties: {
|
|
731
|
+
category: { type: "string" },
|
|
732
|
+
severity: { type: "string" },
|
|
733
|
+
title: { type: "string" },
|
|
734
|
+
description: { type: "string" },
|
|
735
|
+
file: { type: "string" },
|
|
736
|
+
code: { type: "string" },
|
|
737
|
+
},
|
|
738
|
+
required: ["category", "title"],
|
|
739
|
+
},
|
|
740
|
+
},
|
|
741
|
+
required: ["finding"],
|
|
742
|
+
},
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
name: "ai_security_assessment",
|
|
746
|
+
description:
|
|
747
|
+
"🛡️ AI Security Assessment - Get comprehensive AI-powered security posture assessment with risk matrix, compliance gaps, and remediation roadmap",
|
|
748
|
+
inputSchema: {
|
|
749
|
+
type: "object",
|
|
750
|
+
properties: {
|
|
751
|
+
projectPath: {
|
|
752
|
+
type: "string",
|
|
753
|
+
description: "Path to project root directory",
|
|
754
|
+
default: ".",
|
|
755
|
+
},
|
|
756
|
+
complianceFrameworks: {
|
|
757
|
+
type: "array",
|
|
758
|
+
items: { type: "string" },
|
|
759
|
+
description:
|
|
760
|
+
"Compliance frameworks to check (e.g., SOC2, GDPR, HIPAA)",
|
|
761
|
+
default: ["SOC2", "GDPR"],
|
|
762
|
+
},
|
|
763
|
+
},
|
|
764
|
+
},
|
|
765
|
+
},
|
|
766
|
+
// Premium Command Palette Tools
|
|
767
|
+
...PREMIUM_TOOLS,
|
|
768
|
+
// 🧹 Repo Hygiene + Debt Radar
|
|
769
|
+
{
|
|
770
|
+
name: "repo_hygiene_scan",
|
|
771
|
+
description:
|
|
772
|
+
"🧹 Full repo hygiene scan - duplicates, unused files, lint/type errors, root cleanup. Generates deletion-safe plan.",
|
|
773
|
+
inputSchema: {
|
|
774
|
+
type: "object",
|
|
775
|
+
properties: {
|
|
776
|
+
projectPath: {
|
|
777
|
+
type: "string",
|
|
778
|
+
description: "Path to project root",
|
|
779
|
+
default: ".",
|
|
780
|
+
},
|
|
781
|
+
mode: {
|
|
782
|
+
type: "string",
|
|
783
|
+
enum: ["report", "safe-fix"],
|
|
784
|
+
default: "report",
|
|
785
|
+
},
|
|
786
|
+
saveArtifacts: { type: "boolean", default: true },
|
|
787
|
+
},
|
|
788
|
+
},
|
|
789
|
+
},
|
|
790
|
+
{
|
|
791
|
+
name: "repo_hygiene_duplicates",
|
|
792
|
+
description:
|
|
793
|
+
"📋 Find duplicate files - exact (same hash), near-duplicate (85%+ similar), and copy-paste blocks",
|
|
794
|
+
inputSchema: {
|
|
795
|
+
type: "object",
|
|
796
|
+
properties: {
|
|
797
|
+
projectPath: { type: "string", default: "." },
|
|
798
|
+
threshold: {
|
|
799
|
+
type: "number",
|
|
800
|
+
description: "Similarity threshold",
|
|
801
|
+
default: 0.85,
|
|
802
|
+
},
|
|
803
|
+
},
|
|
804
|
+
},
|
|
805
|
+
},
|
|
806
|
+
{
|
|
807
|
+
name: "repo_hygiene_unused",
|
|
808
|
+
description:
|
|
809
|
+
"📦 Find unused files via import graph analysis from entrypoints. Classifies by deletion safety.",
|
|
810
|
+
inputSchema: {
|
|
811
|
+
type: "object",
|
|
812
|
+
properties: {
|
|
813
|
+
projectPath: { type: "string", default: "." },
|
|
814
|
+
scope: {
|
|
815
|
+
type: "string",
|
|
816
|
+
enum: ["all", "prod", "test"],
|
|
817
|
+
default: "all",
|
|
818
|
+
},
|
|
819
|
+
},
|
|
820
|
+
},
|
|
821
|
+
},
|
|
822
|
+
{
|
|
823
|
+
name: "repo_hygiene_errors",
|
|
824
|
+
description:
|
|
825
|
+
"🔴 Unified lint/type/import/syntax error collection. CI-friendly with counts and top offenders.",
|
|
826
|
+
inputSchema: {
|
|
827
|
+
type: "object",
|
|
828
|
+
properties: {
|
|
829
|
+
projectPath: { type: "string", default: "." },
|
|
830
|
+
eslint: { type: "boolean", default: true },
|
|
831
|
+
tsc: { type: "boolean", default: true },
|
|
832
|
+
imports: { type: "boolean", default: true },
|
|
833
|
+
syntax: { type: "boolean", default: true },
|
|
834
|
+
},
|
|
835
|
+
},
|
|
836
|
+
},
|
|
837
|
+
{
|
|
838
|
+
name: "repo_hygiene_root_cleanup",
|
|
839
|
+
description:
|
|
840
|
+
"🏠 Root directory analyzer - junk files, missing standards, duplicate configs, misplaced files",
|
|
841
|
+
inputSchema: {
|
|
842
|
+
type: "object",
|
|
843
|
+
properties: {
|
|
844
|
+
projectPath: { type: "string", default: "." },
|
|
845
|
+
},
|
|
846
|
+
},
|
|
847
|
+
},
|
|
848
|
+
{
|
|
849
|
+
name: "repo_hygiene_deletion_plan",
|
|
850
|
+
description:
|
|
851
|
+
"🗑️ Generate safe deletion plan for duplicates and unused files. Never auto-deletes.",
|
|
852
|
+
inputSchema: {
|
|
853
|
+
type: "object",
|
|
854
|
+
properties: {
|
|
855
|
+
projectPath: { type: "string", default: "." },
|
|
856
|
+
includeReview: { type: "boolean", default: false },
|
|
857
|
+
},
|
|
858
|
+
},
|
|
859
|
+
},
|
|
860
|
+
],
|
|
861
|
+
}));
|
|
862
|
+
|
|
863
|
+
// List available resources
|
|
864
|
+
this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
865
|
+
resources: [
|
|
866
|
+
{
|
|
867
|
+
uri: "guardrails://rules",
|
|
868
|
+
name: "Guardrails Rules",
|
|
869
|
+
description:
|
|
870
|
+
"Current guardrails rules and file organization constraints",
|
|
871
|
+
mimeType: "text/markdown",
|
|
872
|
+
},
|
|
873
|
+
{
|
|
874
|
+
uri: "guardrails://templates",
|
|
875
|
+
name: "Available Templates",
|
|
876
|
+
description: "List of available project templates",
|
|
877
|
+
mimeType: "application/json",
|
|
878
|
+
},
|
|
879
|
+
{
|
|
880
|
+
uri: "guardrails://design-tokens",
|
|
881
|
+
name: "Design Tokens",
|
|
882
|
+
description: "Current design system tokens (if locked)",
|
|
883
|
+
mimeType: "application/json",
|
|
884
|
+
},
|
|
885
|
+
],
|
|
886
|
+
}));
|
|
887
|
+
|
|
888
|
+
// Read resources
|
|
889
|
+
this.server.setRequestHandler(
|
|
890
|
+
ReadResourceRequestSchema,
|
|
891
|
+
async (request) => {
|
|
892
|
+
const { uri } = request.params;
|
|
893
|
+
|
|
894
|
+
switch (uri) {
|
|
895
|
+
case "guardrails://rules":
|
|
896
|
+
return {
|
|
897
|
+
contents: [
|
|
898
|
+
{
|
|
899
|
+
uri,
|
|
900
|
+
mimeType: "text/markdown",
|
|
901
|
+
text: await this.getGuardrailsRules(),
|
|
902
|
+
},
|
|
903
|
+
],
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
case "guardrails://templates":
|
|
907
|
+
return {
|
|
908
|
+
contents: [
|
|
909
|
+
{
|
|
910
|
+
uri,
|
|
911
|
+
mimeType: "application/json",
|
|
912
|
+
text: JSON.stringify(
|
|
913
|
+
await this.getAvailableTemplates(),
|
|
914
|
+
null,
|
|
915
|
+
2,
|
|
916
|
+
),
|
|
917
|
+
},
|
|
918
|
+
],
|
|
919
|
+
};
|
|
920
|
+
|
|
921
|
+
case "guardrails://design-tokens":
|
|
922
|
+
return {
|
|
923
|
+
contents: [
|
|
924
|
+
{
|
|
925
|
+
uri,
|
|
926
|
+
mimeType: "application/json",
|
|
927
|
+
text: JSON.stringify(await this.getDesignTokens(), null, 2),
|
|
928
|
+
},
|
|
929
|
+
],
|
|
930
|
+
};
|
|
931
|
+
|
|
932
|
+
default:
|
|
933
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
934
|
+
}
|
|
935
|
+
},
|
|
936
|
+
);
|
|
937
|
+
|
|
938
|
+
// Handle tool calls with enhanced error handling and progress feedback
|
|
939
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
940
|
+
const { name, arguments: args } = request.params;
|
|
941
|
+
const projectPath = args?.projectPath || process.cwd();
|
|
942
|
+
|
|
943
|
+
// Log tool usage
|
|
944
|
+
this.logger.info(`Executing tool: ${name}`, { projectPath, args });
|
|
945
|
+
|
|
946
|
+
// Validate project path
|
|
947
|
+
if (!projectPath || typeof projectPath !== "string") {
|
|
948
|
+
const error = "Invalid project path provided";
|
|
949
|
+
this.logger.error(error, { projectPath });
|
|
950
|
+
return {
|
|
951
|
+
content: [{ type: "text", text: `❌ Error: ${error}` }],
|
|
952
|
+
isError: true,
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
try {
|
|
957
|
+
// Show progress for long-running operations
|
|
958
|
+
const startTime = Date.now();
|
|
959
|
+
let result;
|
|
960
|
+
|
|
961
|
+
switch (name) {
|
|
962
|
+
case "validate_project":
|
|
963
|
+
result = await this.validateProject(projectPath);
|
|
964
|
+
break;
|
|
965
|
+
|
|
966
|
+
case "check_design_system":
|
|
967
|
+
result = await this.checkDesignSystem(projectPath);
|
|
968
|
+
break;
|
|
969
|
+
|
|
970
|
+
case "check_project_drift":
|
|
971
|
+
result = await this.checkProjectDrift(projectPath);
|
|
972
|
+
break;
|
|
973
|
+
|
|
974
|
+
case "setup_design_system":
|
|
975
|
+
result = await this.setupDesignSystem(
|
|
976
|
+
projectPath,
|
|
977
|
+
args?.theme || "modern",
|
|
978
|
+
);
|
|
979
|
+
break;
|
|
980
|
+
|
|
981
|
+
case "get_project_health":
|
|
982
|
+
result = await this.getProjectHealth(projectPath);
|
|
983
|
+
break;
|
|
984
|
+
|
|
985
|
+
case "register_api_endpoint":
|
|
986
|
+
result = await this.registerApiEndpoint(
|
|
987
|
+
projectPath,
|
|
988
|
+
args.path,
|
|
989
|
+
args.method,
|
|
990
|
+
args.description,
|
|
991
|
+
);
|
|
992
|
+
break;
|
|
993
|
+
|
|
994
|
+
case "get_guardrails_rules":
|
|
995
|
+
result = await this.getGuardrailsRulesResponse(projectPath);
|
|
996
|
+
break;
|
|
997
|
+
|
|
998
|
+
case "architect_analyze":
|
|
999
|
+
result = await this.architectAnalyze(projectPath);
|
|
1000
|
+
break;
|
|
1001
|
+
|
|
1002
|
+
case "architect_apply":
|
|
1003
|
+
result = await this.architectApply(
|
|
1004
|
+
projectPath,
|
|
1005
|
+
args?.autoApply !== false,
|
|
1006
|
+
);
|
|
1007
|
+
break;
|
|
1008
|
+
|
|
1009
|
+
case "build_knowledge_base":
|
|
1010
|
+
result = await this.buildKnowledgeBase(projectPath);
|
|
1011
|
+
break;
|
|
1012
|
+
|
|
1013
|
+
case "get_deep_context":
|
|
1014
|
+
result = await this.getDeepContext(
|
|
1015
|
+
projectPath,
|
|
1016
|
+
args?.query,
|
|
1017
|
+
args?.style || "professional",
|
|
1018
|
+
args?.useEmojis !== undefined ? args.useEmojis : true,
|
|
1019
|
+
args?.includeExamples !== undefined
|
|
1020
|
+
? args.includeExamples
|
|
1021
|
+
: false,
|
|
1022
|
+
);
|
|
1023
|
+
break;
|
|
1024
|
+
|
|
1025
|
+
case "semantic_search":
|
|
1026
|
+
result = await this.semanticSearch(projectPath, args?.query);
|
|
1027
|
+
break;
|
|
1028
|
+
|
|
1029
|
+
case "analyze_change_impact":
|
|
1030
|
+
result = await this.analyzeChangeImpact(projectPath, args?.file);
|
|
1031
|
+
break;
|
|
1032
|
+
|
|
1033
|
+
case "generate_code_context":
|
|
1034
|
+
result = await this.generateCodeContext(projectPath, args?.task);
|
|
1035
|
+
break;
|
|
1036
|
+
|
|
1037
|
+
// Security Orchestrator tools
|
|
1038
|
+
case "security_scan":
|
|
1039
|
+
result = await this.securityScan(
|
|
1040
|
+
projectPath,
|
|
1041
|
+
args?.environment || "production",
|
|
1042
|
+
);
|
|
1043
|
+
break;
|
|
1044
|
+
|
|
1045
|
+
case "policy_check":
|
|
1046
|
+
result = await this.policyCheck(
|
|
1047
|
+
projectPath,
|
|
1048
|
+
args?.environment || "production",
|
|
1049
|
+
);
|
|
1050
|
+
break;
|
|
1051
|
+
|
|
1052
|
+
case "secret_scan":
|
|
1053
|
+
result = await this.secretScan(
|
|
1054
|
+
projectPath,
|
|
1055
|
+
args?.scanHistory || false,
|
|
1056
|
+
);
|
|
1057
|
+
break;
|
|
1058
|
+
|
|
1059
|
+
case "supply_chain_scan":
|
|
1060
|
+
result = await this.supplyChainScan(projectPath, args?.repoUrl);
|
|
1061
|
+
break;
|
|
1062
|
+
|
|
1063
|
+
case "ship_check":
|
|
1064
|
+
result = await this.shipCheck(projectPath);
|
|
1065
|
+
break;
|
|
1066
|
+
|
|
1067
|
+
case "get_deploy_verdict":
|
|
1068
|
+
result = await this.getDeployVerdict(projectPath);
|
|
1069
|
+
break;
|
|
1070
|
+
|
|
1071
|
+
case "audit_api_endpoints":
|
|
1072
|
+
result = await this.auditApiEndpoints(
|
|
1073
|
+
projectPath,
|
|
1074
|
+
args?.showDetails || false,
|
|
1075
|
+
);
|
|
1076
|
+
break;
|
|
1077
|
+
|
|
1078
|
+
case "production_integrity_check":
|
|
1079
|
+
result = await this.productionIntegrityCheck(projectPath);
|
|
1080
|
+
break;
|
|
1081
|
+
|
|
1082
|
+
case "audit_auth_coverage":
|
|
1083
|
+
result = await this.auditAuthCoverage(projectPath);
|
|
1084
|
+
break;
|
|
1085
|
+
|
|
1086
|
+
case "audit_env_secrets":
|
|
1087
|
+
result = await this.auditEnvSecrets(projectPath);
|
|
1088
|
+
break;
|
|
1089
|
+
|
|
1090
|
+
case "audit_route_integrity":
|
|
1091
|
+
result = await this.auditRouteIntegrity(projectPath);
|
|
1092
|
+
break;
|
|
1093
|
+
|
|
1094
|
+
case "audit_mock_blocker":
|
|
1095
|
+
result = await this.auditMockBlocker(projectPath);
|
|
1096
|
+
break;
|
|
1097
|
+
|
|
1098
|
+
case "reality_check":
|
|
1099
|
+
result = await this.realityCheck(
|
|
1100
|
+
projectPath,
|
|
1101
|
+
args?.mode || "full",
|
|
1102
|
+
args?.file,
|
|
1103
|
+
);
|
|
1104
|
+
break;
|
|
1105
|
+
|
|
1106
|
+
case "reality_check_deep":
|
|
1107
|
+
result = await this.realityCheckDeep(
|
|
1108
|
+
projectPath,
|
|
1109
|
+
args?.file,
|
|
1110
|
+
args?.includeCallGraph,
|
|
1111
|
+
args?.includeAsyncAnalysis,
|
|
1112
|
+
);
|
|
1113
|
+
break;
|
|
1114
|
+
|
|
1115
|
+
// 🤖 AI-Enhanced Production Integrity Tools
|
|
1116
|
+
case "ai_production_integrity":
|
|
1117
|
+
result = await this.aiProductionIntegrity(
|
|
1118
|
+
projectPath,
|
|
1119
|
+
args?.enableAI !== false,
|
|
1120
|
+
);
|
|
1121
|
+
break;
|
|
1122
|
+
|
|
1123
|
+
case "ai_explain_finding":
|
|
1124
|
+
result = await this.aiExplainFinding(args?.finding, args?.context);
|
|
1125
|
+
break;
|
|
1126
|
+
|
|
1127
|
+
case "ai_generate_fix":
|
|
1128
|
+
result = await this.aiGenerateFix(args?.finding);
|
|
1129
|
+
break;
|
|
1130
|
+
|
|
1131
|
+
case "ai_security_assessment":
|
|
1132
|
+
result = await this.aiSecurityAssessment(
|
|
1133
|
+
projectPath,
|
|
1134
|
+
args?.complianceFrameworks || ["SOC2", "GDPR"],
|
|
1135
|
+
);
|
|
1136
|
+
break;
|
|
1137
|
+
|
|
1138
|
+
// Repo Hygiene + Debt Radar tools
|
|
1139
|
+
case "repo_hygiene_scan":
|
|
1140
|
+
result = await this.repoHygieneScan(
|
|
1141
|
+
projectPath,
|
|
1142
|
+
args?.mode,
|
|
1143
|
+
args?.saveArtifacts,
|
|
1144
|
+
);
|
|
1145
|
+
break;
|
|
1146
|
+
|
|
1147
|
+
case "repo_hygiene_duplicates":
|
|
1148
|
+
result = await this.repoHygieneDuplicates(
|
|
1149
|
+
projectPath,
|
|
1150
|
+
args?.threshold,
|
|
1151
|
+
);
|
|
1152
|
+
break;
|
|
1153
|
+
|
|
1154
|
+
case "repo_hygiene_unused":
|
|
1155
|
+
result = await this.repoHygieneUnused(projectPath, args?.scope);
|
|
1156
|
+
break;
|
|
1157
|
+
|
|
1158
|
+
case "repo_hygiene_errors":
|
|
1159
|
+
result = await this.repoHygieneErrors(projectPath, args);
|
|
1160
|
+
break;
|
|
1161
|
+
|
|
1162
|
+
case "repo_hygiene_root_cleanup":
|
|
1163
|
+
result = await this.repoHygieneRootCleanup(projectPath);
|
|
1164
|
+
break;
|
|
1165
|
+
|
|
1166
|
+
case "repo_hygiene_deletion_plan":
|
|
1167
|
+
result = await this.repoHygieneDeletionPlan(
|
|
1168
|
+
projectPath,
|
|
1169
|
+
args?.includeReview,
|
|
1170
|
+
);
|
|
1171
|
+
break;
|
|
1172
|
+
|
|
1173
|
+
default:
|
|
1174
|
+
// Try premium tools first
|
|
1175
|
+
const premiumResult = await handlePremiumTool(
|
|
1176
|
+
name,
|
|
1177
|
+
args,
|
|
1178
|
+
this.logger,
|
|
1179
|
+
);
|
|
1180
|
+
if (premiumResult) {
|
|
1181
|
+
result = premiumResult;
|
|
1182
|
+
break;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
const error = `Unknown tool: ${name}`;
|
|
1186
|
+
this.logger.error(error);
|
|
1187
|
+
return {
|
|
1188
|
+
content: [{ type: "text", text: `❌ Error: ${error}` }],
|
|
1189
|
+
isError: true,
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// Log completion
|
|
1194
|
+
const duration = Date.now() - startTime;
|
|
1195
|
+
this.logger.info(`Tool completed: ${name}`, {
|
|
1196
|
+
duration: `${duration}ms`,
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
return result;
|
|
1200
|
+
} catch (error) {
|
|
1201
|
+
// Enhanced error reporting
|
|
1202
|
+
const errorMessage = error.message || "Unknown error occurred";
|
|
1203
|
+
const errorDetails = {
|
|
1204
|
+
tool: name,
|
|
1205
|
+
error: errorMessage,
|
|
1206
|
+
stack: error.stack,
|
|
1207
|
+
projectPath,
|
|
1208
|
+
};
|
|
1209
|
+
|
|
1210
|
+
this.logger.error(`Tool failed: ${name}`, errorDetails);
|
|
1211
|
+
|
|
1212
|
+
return {
|
|
1213
|
+
content: [
|
|
1214
|
+
{
|
|
1215
|
+
type: "text",
|
|
1216
|
+
text: `❌ Error executing ${name}: ${errorMessage}\n\n💡 Tip: Check if you're in a valid project directory and all required files exist.`,
|
|
1217
|
+
},
|
|
1218
|
+
],
|
|
1219
|
+
isError: true,
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
async validateProject(projectPath) {
|
|
1226
|
+
try {
|
|
1227
|
+
const scriptsPath = path.join(
|
|
1228
|
+
__dirname,
|
|
1229
|
+
"..",
|
|
1230
|
+
"scripts",
|
|
1231
|
+
"validate-api-endpoints.js",
|
|
1232
|
+
);
|
|
1233
|
+
const result = execSync(`node "${scriptsPath}"`, {
|
|
1234
|
+
cwd: projectPath,
|
|
1235
|
+
encoding: "utf8",
|
|
1236
|
+
stdio: "pipe",
|
|
1237
|
+
});
|
|
1238
|
+
|
|
1239
|
+
return {
|
|
1240
|
+
content: [
|
|
1241
|
+
{
|
|
1242
|
+
type: "text",
|
|
1243
|
+
text: result || "✅ Validation passed!",
|
|
1244
|
+
},
|
|
1245
|
+
],
|
|
1246
|
+
};
|
|
1247
|
+
} catch (error) {
|
|
1248
|
+
return {
|
|
1249
|
+
content: [
|
|
1250
|
+
{
|
|
1251
|
+
type: "text",
|
|
1252
|
+
text: `Validation issues found:\n${error.stdout || error.message}`,
|
|
1253
|
+
},
|
|
1254
|
+
],
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
async checkDesignSystem(projectPath) {
|
|
1260
|
+
try {
|
|
1261
|
+
const scriptsPath = path.join(
|
|
1262
|
+
__dirname,
|
|
1263
|
+
"..",
|
|
1264
|
+
"scripts",
|
|
1265
|
+
"validate-design-system.js",
|
|
1266
|
+
);
|
|
1267
|
+
const result = execSync(`node "${scriptsPath}"`, {
|
|
1268
|
+
cwd: projectPath,
|
|
1269
|
+
encoding: "utf8",
|
|
1270
|
+
stdio: "pipe",
|
|
1271
|
+
});
|
|
1272
|
+
|
|
1273
|
+
return {
|
|
1274
|
+
content: [
|
|
1275
|
+
{
|
|
1276
|
+
type: "text",
|
|
1277
|
+
text: result || "✅ Design system is consistent!",
|
|
1278
|
+
},
|
|
1279
|
+
],
|
|
1280
|
+
};
|
|
1281
|
+
} catch (error) {
|
|
1282
|
+
return {
|
|
1283
|
+
content: [
|
|
1284
|
+
{
|
|
1285
|
+
type: "text",
|
|
1286
|
+
text: `Design system issues:\n${error.stdout || error.message}`,
|
|
1287
|
+
},
|
|
1288
|
+
],
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
async checkProjectDrift(projectPath) {
|
|
1294
|
+
try {
|
|
1295
|
+
const scriptsPath = path.join(
|
|
1296
|
+
__dirname,
|
|
1297
|
+
"..",
|
|
1298
|
+
"scripts",
|
|
1299
|
+
"check-project-drift.js",
|
|
1300
|
+
);
|
|
1301
|
+
const result = execSync(`node "${scriptsPath}"`, {
|
|
1302
|
+
cwd: projectPath,
|
|
1303
|
+
encoding: "utf8",
|
|
1304
|
+
stdio: "pipe",
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1307
|
+
return {
|
|
1308
|
+
content: [
|
|
1309
|
+
{
|
|
1310
|
+
type: "text",
|
|
1311
|
+
text: result || "✅ No drift detected!",
|
|
1312
|
+
},
|
|
1313
|
+
],
|
|
1314
|
+
};
|
|
1315
|
+
} catch (error) {
|
|
1316
|
+
return {
|
|
1317
|
+
content: [
|
|
1318
|
+
{
|
|
1319
|
+
type: "text",
|
|
1320
|
+
text: `Drift detected:\n${error.stdout || error.message}`,
|
|
1321
|
+
},
|
|
1322
|
+
],
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
async setupDesignSystem(projectPath, theme) {
|
|
1328
|
+
try {
|
|
1329
|
+
// This would call the design system wizard
|
|
1330
|
+
// For now, return instructions
|
|
1331
|
+
return {
|
|
1332
|
+
content: [
|
|
1333
|
+
{
|
|
1334
|
+
type: "text",
|
|
1335
|
+
text: `To set up design system, run: npm run design-system\nOr use theme: ${theme}`,
|
|
1336
|
+
},
|
|
1337
|
+
],
|
|
1338
|
+
};
|
|
1339
|
+
} catch (error) {
|
|
1340
|
+
return {
|
|
1341
|
+
content: [
|
|
1342
|
+
{
|
|
1343
|
+
type: "text",
|
|
1344
|
+
text: `Error: ${error.message}`,
|
|
1345
|
+
},
|
|
1346
|
+
],
|
|
1347
|
+
isError: true,
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
async getProjectHealth(projectPath) {
|
|
1353
|
+
// This would use the project health analyzer
|
|
1354
|
+
return {
|
|
1355
|
+
content: [
|
|
1356
|
+
{
|
|
1357
|
+
type: "text",
|
|
1358
|
+
text: "Project health scoring is a premium feature. Upgrade to Professional or Enterprise tier.",
|
|
1359
|
+
},
|
|
1360
|
+
],
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
async registerApiEndpoint(projectPath, endpointPath, method, description) {
|
|
1365
|
+
try {
|
|
1366
|
+
const endpointsFile = path.join(
|
|
1367
|
+
projectPath,
|
|
1368
|
+
"src",
|
|
1369
|
+
"config",
|
|
1370
|
+
"api-endpoints.ts",
|
|
1371
|
+
);
|
|
1372
|
+
|
|
1373
|
+
// Read existing file or create new
|
|
1374
|
+
let content = "";
|
|
1375
|
+
try {
|
|
1376
|
+
content = await fs.readFile(endpointsFile, "utf8");
|
|
1377
|
+
} catch {
|
|
1378
|
+
// File doesn't exist, create it
|
|
1379
|
+
const dir = path.dirname(endpointsFile);
|
|
1380
|
+
await fs.mkdir(dir, { recursive: true });
|
|
1381
|
+
content = `import { apiValidator } from '@/lib/api-validator';
|
|
1382
|
+
|
|
1383
|
+
export function registerAllEndpoints() {
|
|
1384
|
+
`;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
// Add endpoint registration
|
|
1388
|
+
const registration = ` apiValidator.registerEndpoint({
|
|
1389
|
+
path: '${endpointPath}',
|
|
1390
|
+
method: '${method}',
|
|
1391
|
+
description: '${description || ""}',
|
|
1392
|
+
});
|
|
1393
|
+
`;
|
|
1394
|
+
|
|
1395
|
+
// Insert before the closing brace
|
|
1396
|
+
const updatedContent = content.replace(
|
|
1397
|
+
/(\s*)(\})/,
|
|
1398
|
+
`$1${registration}$1$2`,
|
|
1399
|
+
);
|
|
1400
|
+
|
|
1401
|
+
await fs.writeFile(endpointsFile, updatedContent);
|
|
1402
|
+
|
|
1403
|
+
return {
|
|
1404
|
+
content: [
|
|
1405
|
+
{
|
|
1406
|
+
type: "text",
|
|
1407
|
+
text: `✅ Registered endpoint: ${method} ${endpointPath}`,
|
|
1408
|
+
},
|
|
1409
|
+
],
|
|
1410
|
+
};
|
|
1411
|
+
} catch (error) {
|
|
1412
|
+
return {
|
|
1413
|
+
content: [
|
|
1414
|
+
{
|
|
1415
|
+
type: "text",
|
|
1416
|
+
text: `Error: ${error.message}`,
|
|
1417
|
+
},
|
|
1418
|
+
],
|
|
1419
|
+
isError: true,
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
async getGuardrailsRulesResponse(projectPath) {
|
|
1425
|
+
const rulesPath = path.join(projectPath, ".cursorrules");
|
|
1426
|
+
try {
|
|
1427
|
+
const rules = await fs.readFile(rulesPath, "utf8");
|
|
1428
|
+
return {
|
|
1429
|
+
content: [
|
|
1430
|
+
{
|
|
1431
|
+
type: "text",
|
|
1432
|
+
text: rules,
|
|
1433
|
+
},
|
|
1434
|
+
],
|
|
1435
|
+
};
|
|
1436
|
+
} catch {
|
|
1437
|
+
return {
|
|
1438
|
+
content: [
|
|
1439
|
+
{
|
|
1440
|
+
type: "text",
|
|
1441
|
+
text: "No .cursorrules file found. Run setup to create guardrails.",
|
|
1442
|
+
},
|
|
1443
|
+
],
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
async getGuardrailsRules() {
|
|
1449
|
+
return `# AI Agent Guardrails Rules
|
|
1450
|
+
|
|
1451
|
+
## CRITICAL RULES
|
|
1452
|
+
|
|
1453
|
+
### 1. FILE ORGANIZATION
|
|
1454
|
+
- NEVER create files in root directory (except allowed config files)
|
|
1455
|
+
- ALWAYS specify full file paths when creating files
|
|
1456
|
+
- Use feature-based organization: /src/features/[name]/
|
|
1457
|
+
|
|
1458
|
+
### 2. NO MOCK DATA
|
|
1459
|
+
- NEVER use mock data, fake endpoints, or placeholder data
|
|
1460
|
+
- ALWAYS use real API endpoints that are registered
|
|
1461
|
+
- Register new endpoints using register_api_endpoint tool
|
|
1462
|
+
|
|
1463
|
+
### 3. DESIGN SYSTEM
|
|
1464
|
+
- ALWAYS use design tokens from locked design system
|
|
1465
|
+
- NEVER use hardcoded colors, spacing, or values
|
|
1466
|
+
- Use validate_design_system tool to check consistency
|
|
1467
|
+
|
|
1468
|
+
### 4. CODE QUALITY
|
|
1469
|
+
- All code must pass ESLint and TypeScript checks
|
|
1470
|
+
- Use proper TypeScript types (no 'any')
|
|
1471
|
+
- Follow the project's architecture patterns
|
|
1472
|
+
`;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
async getAvailableTemplates() {
|
|
1476
|
+
return {
|
|
1477
|
+
templates: [
|
|
1478
|
+
{ id: "00", name: "Quick Start Guide" },
|
|
1479
|
+
{ id: "01", name: "UI/UX System" },
|
|
1480
|
+
{ id: "02", name: "Design System" },
|
|
1481
|
+
{ id: "03", name: "Project Architecture" },
|
|
1482
|
+
{ id: "04", name: "API Architecture" },
|
|
1483
|
+
{ id: "05", name: "AI Agent File Rules" },
|
|
1484
|
+
{ id: "06", name: "Testing Setup" },
|
|
1485
|
+
{ id: "07", name: "State Management" },
|
|
1486
|
+
{ id: "08", name: "Environment Config" },
|
|
1487
|
+
{ id: "09", name: "Database & ORM" },
|
|
1488
|
+
{ id: "10", name: "Authentication" },
|
|
1489
|
+
],
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
async getDesignTokens() {
|
|
1494
|
+
const lockFile = path.join(process.cwd(), ".design-system-lock.json");
|
|
1495
|
+
try {
|
|
1496
|
+
const lock = JSON.parse(await fs.readFile(lockFile, "utf8"));
|
|
1497
|
+
return {
|
|
1498
|
+
locked: true,
|
|
1499
|
+
theme: lock.theme,
|
|
1500
|
+
message:
|
|
1501
|
+
"Design system is locked. Use design tokens for all components.",
|
|
1502
|
+
};
|
|
1503
|
+
} catch {
|
|
1504
|
+
return {
|
|
1505
|
+
locked: false,
|
|
1506
|
+
message:
|
|
1507
|
+
"No design system locked. Run setup_design_system tool to lock one.",
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
async architectAnalyze(projectPath) {
|
|
1513
|
+
try {
|
|
1514
|
+
// Import architect agent (using dynamic import for ES modules)
|
|
1515
|
+
const { architectAgent } = await import("../src/lib/architect-agent.js");
|
|
1516
|
+
const analysis = await architectAgent.analyzeProject(projectPath);
|
|
1517
|
+
|
|
1518
|
+
const output = {
|
|
1519
|
+
context: analysis.context,
|
|
1520
|
+
recommendations: analysis.recommendations.map((rec) => ({
|
|
1521
|
+
action: rec.action,
|
|
1522
|
+
priority: rec.priority,
|
|
1523
|
+
description: rec.description,
|
|
1524
|
+
templates: rec.templates,
|
|
1525
|
+
autoApply: rec.autoApply,
|
|
1526
|
+
})),
|
|
1527
|
+
plan: {
|
|
1528
|
+
totalTemplates: analysis.plan.templates.length,
|
|
1529
|
+
estimatedTime: analysis.plan.estimatedTime,
|
|
1530
|
+
templates: analysis.plan.templates.map((t) => ({
|
|
1531
|
+
name: t.name,
|
|
1532
|
+
category: t.category,
|
|
1533
|
+
priority: t.priority,
|
|
1534
|
+
reason: t.reason,
|
|
1535
|
+
})),
|
|
1536
|
+
},
|
|
1537
|
+
};
|
|
1538
|
+
|
|
1539
|
+
return {
|
|
1540
|
+
content: [
|
|
1541
|
+
{
|
|
1542
|
+
type: "text",
|
|
1543
|
+
text:
|
|
1544
|
+
`🏗️ Architect Agent Analysis\n\n` +
|
|
1545
|
+
`Project Type: ${analysis.context.type}\n` +
|
|
1546
|
+
`Framework: ${analysis.context.framework.join(", ") || "None"}\n` +
|
|
1547
|
+
`Stage: ${analysis.context.stage}\n\n` +
|
|
1548
|
+
`Found ${analysis.recommendations.length} recommendations\n` +
|
|
1549
|
+
`Plan includes ${analysis.plan.templates.length} templates\n` +
|
|
1550
|
+
`Estimated time: ${analysis.plan.estimatedTime}\n\n` +
|
|
1551
|
+
`Use architect_apply tool to apply templates automatically.`,
|
|
1552
|
+
},
|
|
1553
|
+
{
|
|
1554
|
+
type: "text",
|
|
1555
|
+
text: JSON.stringify(output, null, 2),
|
|
1556
|
+
mimeType: "application/json",
|
|
1557
|
+
},
|
|
1558
|
+
],
|
|
1559
|
+
};
|
|
1560
|
+
} catch (error) {
|
|
1561
|
+
return {
|
|
1562
|
+
content: [
|
|
1563
|
+
{
|
|
1564
|
+
type: "text",
|
|
1565
|
+
text: `Error analyzing project: ${error.message}\n\nMake sure you're in a valid project directory.`,
|
|
1566
|
+
},
|
|
1567
|
+
],
|
|
1568
|
+
isError: true,
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
async architectApply(projectPath, autoApply) {
|
|
1574
|
+
try {
|
|
1575
|
+
const { architectAgent } = await import("../src/lib/architect-agent.js");
|
|
1576
|
+
const analysis = await architectAgent.analyzeProject(projectPath);
|
|
1577
|
+
const result = await architectAgent.applyTemplates(
|
|
1578
|
+
projectPath,
|
|
1579
|
+
analysis.plan,
|
|
1580
|
+
autoApply,
|
|
1581
|
+
);
|
|
1582
|
+
|
|
1583
|
+
const summary =
|
|
1584
|
+
`✅ Applied: ${result.applied.length}\n` +
|
|
1585
|
+
`⏭️ Skipped: ${result.skipped.length}\n` +
|
|
1586
|
+
(result.errors.length > 0
|
|
1587
|
+
? `❌ Errors: ${result.errors.length}\n`
|
|
1588
|
+
: "");
|
|
1589
|
+
|
|
1590
|
+
return {
|
|
1591
|
+
content: [
|
|
1592
|
+
{
|
|
1593
|
+
type: "text",
|
|
1594
|
+
text:
|
|
1595
|
+
`🏗️ Architect Agent - Template Application\n\n${summary}\n\n` +
|
|
1596
|
+
(result.applied.length > 0
|
|
1597
|
+
? `Applied templates:\n${result.applied.map((id) => ` ✅ ${id}`).join("\n")}\n\n`
|
|
1598
|
+
: "") +
|
|
1599
|
+
(result.errors.length > 0
|
|
1600
|
+
? `Errors:\n${result.errors.map((e) => ` ❌ ${e.template}: ${e.error}`).join("\n")}\n\n`
|
|
1601
|
+
: "") +
|
|
1602
|
+
`Review applied templates and customize as needed.`,
|
|
1603
|
+
},
|
|
1604
|
+
],
|
|
1605
|
+
};
|
|
1606
|
+
} catch (error) {
|
|
1607
|
+
return {
|
|
1608
|
+
content: [
|
|
1609
|
+
{
|
|
1610
|
+
type: "text",
|
|
1611
|
+
text: `Error applying templates: ${error.message}`,
|
|
1612
|
+
},
|
|
1613
|
+
],
|
|
1614
|
+
isError: true,
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
async buildKnowledgeBase(projectPath) {
|
|
1620
|
+
try {
|
|
1621
|
+
const { codebaseKnowledgeBase } =
|
|
1622
|
+
await import("../src/lib/codebase-knowledge.js");
|
|
1623
|
+
const knowledge = await codebaseKnowledgeBase.buildKnowledge(projectPath);
|
|
1624
|
+
|
|
1625
|
+
return {
|
|
1626
|
+
content: [
|
|
1627
|
+
{
|
|
1628
|
+
type: "text",
|
|
1629
|
+
text:
|
|
1630
|
+
`🧠 Knowledge Base Built!\n\n` +
|
|
1631
|
+
`Architecture: ${knowledge.architecture.structure.type}\n` +
|
|
1632
|
+
`Patterns: ${knowledge.patterns.length}\n` +
|
|
1633
|
+
`Files analyzed: ${knowledge.relationships.imports.size}\n` +
|
|
1634
|
+
`Active features: ${knowledge.context.activeFeatures.length}\n\n` +
|
|
1635
|
+
`Knowledge saved to .codebase-knowledge.json\n` +
|
|
1636
|
+
`Use get_deep_context tool to query this knowledge.`,
|
|
1637
|
+
},
|
|
1638
|
+
],
|
|
1639
|
+
};
|
|
1640
|
+
} catch (error) {
|
|
1641
|
+
return {
|
|
1642
|
+
content: [
|
|
1643
|
+
{
|
|
1644
|
+
type: "text",
|
|
1645
|
+
text: `Error building knowledge base: ${error.message}`,
|
|
1646
|
+
},
|
|
1647
|
+
],
|
|
1648
|
+
isError: true,
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
async getDeepContext(
|
|
1654
|
+
projectPath,
|
|
1655
|
+
query,
|
|
1656
|
+
style = "professional",
|
|
1657
|
+
useEmojis = true,
|
|
1658
|
+
includeExamples = false,
|
|
1659
|
+
) {
|
|
1660
|
+
if (!query) {
|
|
1661
|
+
return {
|
|
1662
|
+
content: [
|
|
1663
|
+
{
|
|
1664
|
+
type: "text",
|
|
1665
|
+
text: "Error: query parameter is required",
|
|
1666
|
+
},
|
|
1667
|
+
],
|
|
1668
|
+
isError: true,
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
try {
|
|
1673
|
+
const { deepContextAgent } =
|
|
1674
|
+
await import("../src/lib/deep-context-agent.js");
|
|
1675
|
+
const { responseStyleService } =
|
|
1676
|
+
await import("../src/lib/response-style-service.js");
|
|
1677
|
+
|
|
1678
|
+
// Validate style
|
|
1679
|
+
const availableStyles = responseStyleService.getAvailableStyles();
|
|
1680
|
+
const validStyle = availableStyles.includes(style)
|
|
1681
|
+
? style
|
|
1682
|
+
: "professional";
|
|
1683
|
+
|
|
1684
|
+
await deepContextAgent.initialize(projectPath);
|
|
1685
|
+
|
|
1686
|
+
// Get formatted response with style
|
|
1687
|
+
const formattedResponse = await deepContextAgent.getFormattedContext(
|
|
1688
|
+
query,
|
|
1689
|
+
projectPath,
|
|
1690
|
+
validStyle,
|
|
1691
|
+
{
|
|
1692
|
+
useEmojis,
|
|
1693
|
+
includeExamples,
|
|
1694
|
+
},
|
|
1695
|
+
);
|
|
1696
|
+
|
|
1697
|
+
return {
|
|
1698
|
+
content: [
|
|
1699
|
+
{
|
|
1700
|
+
type: "text",
|
|
1701
|
+
text: formattedResponse,
|
|
1702
|
+
},
|
|
1703
|
+
],
|
|
1704
|
+
};
|
|
1705
|
+
} catch (error) {
|
|
1706
|
+
const { responseStyleService } =
|
|
1707
|
+
await import("../src/lib/response-style-service.js");
|
|
1708
|
+
const errorMessage = responseStyleService.formatMessage(
|
|
1709
|
+
`Error getting context: ${error.message}\n\nTry running build_knowledge_base first.`,
|
|
1710
|
+
style || "professional",
|
|
1711
|
+
useEmojis,
|
|
1712
|
+
);
|
|
1713
|
+
|
|
1714
|
+
return {
|
|
1715
|
+
content: [
|
|
1716
|
+
{
|
|
1717
|
+
type: "text",
|
|
1718
|
+
text: errorMessage,
|
|
1719
|
+
},
|
|
1720
|
+
],
|
|
1721
|
+
isError: true,
|
|
1722
|
+
};
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
async semanticSearch(projectPath, query) {
|
|
1727
|
+
try {
|
|
1728
|
+
// Import the semantic search service
|
|
1729
|
+
const { semanticSearchService } =
|
|
1730
|
+
await import("../src/lib/semantic-search-service.js");
|
|
1731
|
+
|
|
1732
|
+
// Perform semantic search
|
|
1733
|
+
const results = await semanticSearchService.search(
|
|
1734
|
+
query,
|
|
1735
|
+
projectPath,
|
|
1736
|
+
10,
|
|
1737
|
+
);
|
|
1738
|
+
|
|
1739
|
+
if (results.length === 0) {
|
|
1740
|
+
return {
|
|
1741
|
+
content: [
|
|
1742
|
+
{
|
|
1743
|
+
type: "text",
|
|
1744
|
+
text: `🔍 No results found for "${query}"\n\nTry different keywords or check if the files are indexed.`,
|
|
1745
|
+
},
|
|
1746
|
+
],
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
// Format results
|
|
1751
|
+
let response = `🔍 Semantic Search Results for "${query}"\n\nFound ${results.length} relevant results:\n\n`;
|
|
1752
|
+
|
|
1753
|
+
results.forEach((result, index) => {
|
|
1754
|
+
const relativePath = result.file
|
|
1755
|
+
.replace(projectPath, "")
|
|
1756
|
+
.replace(/^[\/\\]/, "");
|
|
1757
|
+
response += `${index + 1}. **${relativePath}:${result.line}**\n`;
|
|
1758
|
+
response += ` 📄 ${result.content.substring(0, 100)}${result.content.length > 100 ? "..." : ""}\n`;
|
|
1759
|
+
response += ` 💡 ${result.context}\n`;
|
|
1760
|
+
response += ` ⭐ Relevance: ${Math.round(result.score * 10)}/10\n\n`;
|
|
1761
|
+
});
|
|
1762
|
+
|
|
1763
|
+
return {
|
|
1764
|
+
content: [
|
|
1765
|
+
{
|
|
1766
|
+
type: "text",
|
|
1767
|
+
text: response,
|
|
1768
|
+
},
|
|
1769
|
+
],
|
|
1770
|
+
};
|
|
1771
|
+
} catch (error) {
|
|
1772
|
+
this.logger.error("Semantic search failed", {
|
|
1773
|
+
error: error.message,
|
|
1774
|
+
projectPath,
|
|
1775
|
+
query,
|
|
1776
|
+
});
|
|
1777
|
+
return {
|
|
1778
|
+
content: [
|
|
1779
|
+
{
|
|
1780
|
+
type: "text",
|
|
1781
|
+
text: `❌ Error performing semantic search: ${error.message}\n\nTip: Ensure you're in a valid project directory with source files.`,
|
|
1782
|
+
},
|
|
1783
|
+
],
|
|
1784
|
+
isError: true,
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
async analyzeChangeImpact(projectPath, file) {
|
|
1790
|
+
try {
|
|
1791
|
+
// Import the change impact analyzer
|
|
1792
|
+
const { changeImpactAnalyzer } =
|
|
1793
|
+
await import("../src/lib/change-impact-analyzer.js");
|
|
1794
|
+
|
|
1795
|
+
// Analyze change impact
|
|
1796
|
+
const analysis = await changeImpactAnalyzer.analyzeImpact(
|
|
1797
|
+
projectPath,
|
|
1798
|
+
file,
|
|
1799
|
+
);
|
|
1800
|
+
|
|
1801
|
+
// Format results
|
|
1802
|
+
let response = `💥 Change Impact Analysis for "${file}"\n\n`;
|
|
1803
|
+
response += `📊 **Summary:**\n`;
|
|
1804
|
+
response += `- Total files affected: ${analysis.summary.totalAffected}\n`;
|
|
1805
|
+
response += `- Risk score: ${analysis.summary.riskScore}/100\n`;
|
|
1806
|
+
response += `- Estimated tests needed: ${analysis.summary.estimatedTests}\n\n`;
|
|
1807
|
+
|
|
1808
|
+
// Critical impact
|
|
1809
|
+
if (analysis.impact.critical.length > 0) {
|
|
1810
|
+
response += `🚨 **Critical Impact (${analysis.impact.critical.length} files):**\n`;
|
|
1811
|
+
analysis.impact.critical.forEach((dep) => {
|
|
1812
|
+
const relativePath = dep.file
|
|
1813
|
+
.replace(projectPath, "")
|
|
1814
|
+
.replace(/^[\/\\]/, "");
|
|
1815
|
+
response += ` - ${relativePath}: ${dep.description}\n`;
|
|
1816
|
+
});
|
|
1817
|
+
response += "\n";
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
// High impact
|
|
1821
|
+
if (analysis.impact.high.length > 0) {
|
|
1822
|
+
response += `⚠️ **High Impact (${analysis.impact.high.length} files):**\n`;
|
|
1823
|
+
analysis.impact.high.forEach((dep) => {
|
|
1824
|
+
const relativePath = dep.file
|
|
1825
|
+
.replace(projectPath, "")
|
|
1826
|
+
.replace(/^[\/\\]/, "");
|
|
1827
|
+
response += ` - ${relativePath}: ${dep.description}\n`;
|
|
1828
|
+
});
|
|
1829
|
+
response += "\n";
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
// Medium impact
|
|
1833
|
+
if (analysis.impact.medium.length > 0) {
|
|
1834
|
+
response += `📋 **Medium Impact (${analysis.impact.medium.length} files):**\n`;
|
|
1835
|
+
analysis.impact.medium.forEach((dep) => {
|
|
1836
|
+
const relativePath = dep.file
|
|
1837
|
+
.replace(projectPath, "")
|
|
1838
|
+
.replace(/^[\/\\]/, "");
|
|
1839
|
+
response += ` - ${relativePath}: ${dep.description}\n`;
|
|
1840
|
+
});
|
|
1841
|
+
response += "\n";
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
// Low impact
|
|
1845
|
+
if (analysis.impact.low.length > 0) {
|
|
1846
|
+
response += `📝 **Low Impact (${analysis.impact.low.length} files):**\n`;
|
|
1847
|
+
analysis.impact.low.forEach((dep) => {
|
|
1848
|
+
const relativePath = dep.file
|
|
1849
|
+
.replace(projectPath, "")
|
|
1850
|
+
.replace(/^[\/\\]/, "");
|
|
1851
|
+
response += ` - ${relativePath}: ${dep.description}\n`;
|
|
1852
|
+
});
|
|
1853
|
+
response += "\n";
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
// Recommendations
|
|
1857
|
+
response += `💡 **Recommendations:**\n`;
|
|
1858
|
+
analysis.recommendations.forEach((rec) => {
|
|
1859
|
+
response += `${rec}\n`;
|
|
1860
|
+
});
|
|
1861
|
+
|
|
1862
|
+
return {
|
|
1863
|
+
content: [
|
|
1864
|
+
{
|
|
1865
|
+
type: "text",
|
|
1866
|
+
text: response,
|
|
1867
|
+
},
|
|
1868
|
+
],
|
|
1869
|
+
};
|
|
1870
|
+
} catch (error) {
|
|
1871
|
+
this.logger.error("Change impact analysis failed", {
|
|
1872
|
+
error: error.message,
|
|
1873
|
+
projectPath,
|
|
1874
|
+
file,
|
|
1875
|
+
});
|
|
1876
|
+
return {
|
|
1877
|
+
content: [
|
|
1878
|
+
{
|
|
1879
|
+
type: "text",
|
|
1880
|
+
text: `❌ Error analyzing change impact: ${error.message}\n\nTip: Ensure the file exists and is a valid source file.`,
|
|
1881
|
+
},
|
|
1882
|
+
],
|
|
1883
|
+
isError: true,
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
async generateCodeContext(projectPath, task) {
|
|
1889
|
+
try {
|
|
1890
|
+
// Import the code context generator
|
|
1891
|
+
const { codeContextGenerator } =
|
|
1892
|
+
await import("../src/lib/code-context-generator.js");
|
|
1893
|
+
|
|
1894
|
+
// Generate code context
|
|
1895
|
+
const context = await codeContextGenerator.generatePrompt(
|
|
1896
|
+
projectPath,
|
|
1897
|
+
task,
|
|
1898
|
+
);
|
|
1899
|
+
|
|
1900
|
+
// Format results
|
|
1901
|
+
let response = `⚙️ Code Generation Context for: "${task}"\n\n`;
|
|
1902
|
+
response += `📋 **Context:**\n${context.context}\n\n`;
|
|
1903
|
+
|
|
1904
|
+
// Show detected patterns
|
|
1905
|
+
if (context.patterns.length > 0) {
|
|
1906
|
+
response += `🎨 **Detected Patterns:**\n`;
|
|
1907
|
+
context.patterns.forEach((p) => {
|
|
1908
|
+
response += `- Type: ${p.type} (${p.framework})\n`;
|
|
1909
|
+
response += ` Naming: ${p.conventions.naming}\n`;
|
|
1910
|
+
response += ` Exports: ${p.conventions.exports.join(", ")}\n\n`;
|
|
1911
|
+
});
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
// Show best practices
|
|
1915
|
+
if (context.bestPractices.length > 0) {
|
|
1916
|
+
response += `✨ **Best Practices:**\n`;
|
|
1917
|
+
context.bestPractices.forEach((practice) => {
|
|
1918
|
+
response += `- ${practice}\n`;
|
|
1919
|
+
});
|
|
1920
|
+
response += "\n";
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
// Show examples
|
|
1924
|
+
if (context.examples.length > 0) {
|
|
1925
|
+
response += `📝 **Code Examples:**\n`;
|
|
1926
|
+
context.examples.slice(0, 2).forEach((example) => {
|
|
1927
|
+
response += `\n${example.description}:\n`;
|
|
1928
|
+
response += "```typescript\n";
|
|
1929
|
+
response += example.code;
|
|
1930
|
+
response += "\n```\n";
|
|
1931
|
+
});
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
// Add the generated prompt
|
|
1935
|
+
response += `\n🚀 **Generated Prompt:**\n`;
|
|
1936
|
+
response += "```\n";
|
|
1937
|
+
response += context.prompt;
|
|
1938
|
+
response += "\n```\n";
|
|
1939
|
+
|
|
1940
|
+
return {
|
|
1941
|
+
content: [
|
|
1942
|
+
{
|
|
1943
|
+
type: "text",
|
|
1944
|
+
text: response,
|
|
1945
|
+
},
|
|
1946
|
+
],
|
|
1947
|
+
};
|
|
1948
|
+
} catch (error) {
|
|
1949
|
+
this.logger.error("Code context generation failed", {
|
|
1950
|
+
error: error.message,
|
|
1951
|
+
projectPath,
|
|
1952
|
+
task,
|
|
1953
|
+
});
|
|
1954
|
+
return {
|
|
1955
|
+
content: [
|
|
1956
|
+
{
|
|
1957
|
+
type: "text",
|
|
1958
|
+
text: `❌ Error generating code context: ${error.message}\n\nTip: Ensure you're in a valid project directory with source files.`,
|
|
1959
|
+
},
|
|
1960
|
+
],
|
|
1961
|
+
isError: true,
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
// ============ Security Orchestrator Methods ============
|
|
1967
|
+
|
|
1968
|
+
async securityScan(projectPath, environment) {
|
|
1969
|
+
try {
|
|
1970
|
+
const scriptsPath = path.join(
|
|
1971
|
+
__dirname,
|
|
1972
|
+
"..",
|
|
1973
|
+
"scripts",
|
|
1974
|
+
"orchestrator.js",
|
|
1975
|
+
);
|
|
1976
|
+
const result = execSync(
|
|
1977
|
+
`node "${scriptsPath}" --path "${projectPath}" --env ${environment} --format json`,
|
|
1978
|
+
{ cwd: projectPath, encoding: "utf8", maxBuffer: 10 * 1024 * 1024 },
|
|
1979
|
+
);
|
|
1980
|
+
|
|
1981
|
+
// Parse the JSON report
|
|
1982
|
+
const reportPath = path.join(
|
|
1983
|
+
projectPath,
|
|
1984
|
+
".GUARDRAIL",
|
|
1985
|
+
"security-report.json",
|
|
1986
|
+
);
|
|
1987
|
+
let report;
|
|
1988
|
+
try {
|
|
1989
|
+
report = JSON.parse(await fs.readFile(reportPath, "utf8"));
|
|
1990
|
+
} catch {
|
|
1991
|
+
report = {
|
|
1992
|
+
verdict: { allowed: true },
|
|
1993
|
+
metrics: { riskScore: 0, totalFindings: 0 },
|
|
1994
|
+
};
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
const icon = report.verdict.allowed ? "✅" : "🛑";
|
|
1998
|
+
let response = `${icon} **Security Scan Complete**\n\n`;
|
|
1999
|
+
response += `**Environment:** ${environment}\n`;
|
|
2000
|
+
response += `**Risk Score:** ${report.metrics.riskScore}/100\n`;
|
|
2001
|
+
response += `**Total Findings:** ${report.metrics.totalFindings}\n\n`;
|
|
2002
|
+
|
|
2003
|
+
if (report.metrics.findingsBySeverity) {
|
|
2004
|
+
response += `**Findings by Severity:**\n`;
|
|
2005
|
+
response += `- Critical: ${report.metrics.findingsBySeverity.critical || 0}\n`;
|
|
2006
|
+
response += `- High: ${report.metrics.findingsBySeverity.high || 0}\n`;
|
|
2007
|
+
response += `- Medium: ${report.metrics.findingsBySeverity.medium || 0}\n`;
|
|
2008
|
+
response += `- Low: ${report.metrics.findingsBySeverity.low || 0}\n\n`;
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
if (!report.verdict.allowed && report.verdict.blockers?.length > 0) {
|
|
2012
|
+
response += `**Blockers:**\n`;
|
|
2013
|
+
report.verdict.blockers.forEach((b) => {
|
|
2014
|
+
response += `- ❌ ${b}\n`;
|
|
2015
|
+
});
|
|
2016
|
+
response += "\n";
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
if (report.verdict.warnings?.length > 0) {
|
|
2020
|
+
response += `**Warnings:**\n`;
|
|
2021
|
+
report.verdict.warnings.forEach((w) => {
|
|
2022
|
+
response += `- ⚠️ ${w}\n`;
|
|
2023
|
+
});
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
response += `\n📄 Full report: .GUARDRAIL/security-report.json`;
|
|
2027
|
+
|
|
2028
|
+
return {
|
|
2029
|
+
content: [{ type: "text", text: response }],
|
|
2030
|
+
};
|
|
2031
|
+
} catch (error) {
|
|
2032
|
+
return {
|
|
2033
|
+
content: [
|
|
2034
|
+
{
|
|
2035
|
+
type: "text",
|
|
2036
|
+
text: `❌ Security scan failed: ${error.message}\n\nTip: Run 'npm run ship' to see detailed output.`,
|
|
2037
|
+
},
|
|
2038
|
+
],
|
|
2039
|
+
isError: true,
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
async policyCheck(projectPath, environment) {
|
|
2045
|
+
try {
|
|
2046
|
+
// Run policy check using the orchestrator
|
|
2047
|
+
const BANNED_PATTERNS = [
|
|
2048
|
+
{ pattern: "MockProvider", message: "MockProvider in production code" },
|
|
2049
|
+
{ pattern: "useMock", message: "useMock hook in production code" },
|
|
2050
|
+
{
|
|
2051
|
+
pattern: "localhost:\\d+",
|
|
2052
|
+
isRegex: true,
|
|
2053
|
+
message: "Hardcoded localhost URLs",
|
|
2054
|
+
},
|
|
2055
|
+
{
|
|
2056
|
+
pattern: "demo_|inv_demo|fake_",
|
|
2057
|
+
isRegex: true,
|
|
2058
|
+
message: "Demo/fake identifiers",
|
|
2059
|
+
},
|
|
2060
|
+
{ pattern: "sk_test_", message: "Stripe test keys" },
|
|
2061
|
+
];
|
|
2062
|
+
|
|
2063
|
+
const findings = [];
|
|
2064
|
+
const excludeDirs = [
|
|
2065
|
+
"node_modules",
|
|
2066
|
+
"__tests__",
|
|
2067
|
+
"*.test.*",
|
|
2068
|
+
"*.spec.*",
|
|
2069
|
+
"docs",
|
|
2070
|
+
"landing",
|
|
2071
|
+
];
|
|
2072
|
+
const excludeArgs = excludeDirs
|
|
2073
|
+
.map((d) => `--glob '!**/${d}/**'`)
|
|
2074
|
+
.join(" ");
|
|
2075
|
+
|
|
2076
|
+
for (const { pattern, message, isRegex } of BANNED_PATTERNS) {
|
|
2077
|
+
try {
|
|
2078
|
+
const searchPattern = isRegex
|
|
2079
|
+
? pattern
|
|
2080
|
+
: pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2081
|
+
const cmd = `rg -n --hidden ${excludeArgs} "${searchPattern}" "${projectPath}"`;
|
|
2082
|
+
const output = execSync(cmd, {
|
|
2083
|
+
encoding: "utf-8",
|
|
2084
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
2085
|
+
});
|
|
2086
|
+
|
|
2087
|
+
output
|
|
2088
|
+
.trim()
|
|
2089
|
+
.split("\n")
|
|
2090
|
+
.filter(Boolean)
|
|
2091
|
+
.forEach((line) => {
|
|
2092
|
+
const match = line.match(/^(.+?):(\d+):(.*)$/);
|
|
2093
|
+
if (match) {
|
|
2094
|
+
findings.push({
|
|
2095
|
+
pattern,
|
|
2096
|
+
message,
|
|
2097
|
+
file: path.relative(projectPath, match[1]),
|
|
2098
|
+
line: match[2],
|
|
2099
|
+
snippet: match[3].trim().substring(0, 60),
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
});
|
|
2103
|
+
} catch {
|
|
2104
|
+
// No matches found - good!
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
const passed = findings.length === 0;
|
|
2109
|
+
let response = passed
|
|
2110
|
+
? `✅ **Policy Check Passed**\n\nNo policy violations found in ${environment} configuration.\n`
|
|
2111
|
+
: `🛑 **Policy Check Failed**\n\nFound ${findings.length} policy violation(s):\n\n`;
|
|
2112
|
+
|
|
2113
|
+
if (!passed) {
|
|
2114
|
+
findings.slice(0, 10).forEach((f) => {
|
|
2115
|
+
response += `- **${f.message}**\n`;
|
|
2116
|
+
response += ` 📄 ${f.file}:${f.line}\n`;
|
|
2117
|
+
response += ` \`${f.snippet}...\`\n\n`;
|
|
2118
|
+
});
|
|
2119
|
+
|
|
2120
|
+
if (findings.length > 10) {
|
|
2121
|
+
response += `... and ${findings.length - 10} more violations\n`;
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
return {
|
|
2126
|
+
content: [{ type: "text", text: response }],
|
|
2127
|
+
};
|
|
2128
|
+
} catch (error) {
|
|
2129
|
+
return {
|
|
2130
|
+
content: [
|
|
2131
|
+
{
|
|
2132
|
+
type: "text",
|
|
2133
|
+
text: `❌ Policy check failed: ${error.message}`,
|
|
2134
|
+
},
|
|
2135
|
+
],
|
|
2136
|
+
isError: true,
|
|
2137
|
+
};
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
async secretScan(projectPath, scanHistory) {
|
|
2142
|
+
try {
|
|
2143
|
+
const SECRET_PATTERNS = [
|
|
2144
|
+
{ type: "AWS Access Key", pattern: /AKIA[0-9A-Z]{16}/g },
|
|
2145
|
+
{ type: "GitHub Token", pattern: /ghp_[a-zA-Z0-9]{36}/g },
|
|
2146
|
+
{ type: "Stripe Key", pattern: /sk_live_[a-zA-Z0-9]{24,}/g },
|
|
2147
|
+
{
|
|
2148
|
+
type: "JWT",
|
|
2149
|
+
pattern: /eyJ[a-zA-Z0-9\-_]+\.eyJ[a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+/g,
|
|
2150
|
+
},
|
|
2151
|
+
{
|
|
2152
|
+
type: "Private Key",
|
|
2153
|
+
pattern: /-----BEGIN (RSA |EC )?PRIVATE KEY-----/g,
|
|
2154
|
+
},
|
|
2155
|
+
{
|
|
2156
|
+
type: "Database URL",
|
|
2157
|
+
pattern: /(?:postgres|mysql|mongodb):\/\/[^\s"']+/gi,
|
|
2158
|
+
},
|
|
2159
|
+
];
|
|
2160
|
+
|
|
2161
|
+
const secrets = [];
|
|
2162
|
+
const codeExtensions = [
|
|
2163
|
+
".ts",
|
|
2164
|
+
".tsx",
|
|
2165
|
+
".js",
|
|
2166
|
+
".jsx",
|
|
2167
|
+
".json",
|
|
2168
|
+
".env",
|
|
2169
|
+
".yaml",
|
|
2170
|
+
".yml",
|
|
2171
|
+
];
|
|
2172
|
+
|
|
2173
|
+
// Walk directory
|
|
2174
|
+
const walkDir = async (dir) => {
|
|
2175
|
+
const files = [];
|
|
2176
|
+
try {
|
|
2177
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
2178
|
+
for (const entry of entries) {
|
|
2179
|
+
const fullPath = path.join(dir, entry.name);
|
|
2180
|
+
if (
|
|
2181
|
+
entry.isDirectory() &&
|
|
2182
|
+
!entry.name.startsWith(".") &&
|
|
2183
|
+
entry.name !== "node_modules"
|
|
2184
|
+
) {
|
|
2185
|
+
files.push(...(await walkDir(fullPath)));
|
|
2186
|
+
} else if (
|
|
2187
|
+
entry.isFile() &&
|
|
2188
|
+
codeExtensions.some((ext) => entry.name.endsWith(ext))
|
|
2189
|
+
) {
|
|
2190
|
+
files.push(fullPath);
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
} catch {}
|
|
2194
|
+
return files;
|
|
2195
|
+
};
|
|
2196
|
+
|
|
2197
|
+
const files = await walkDir(projectPath);
|
|
2198
|
+
|
|
2199
|
+
for (const file of files) {
|
|
2200
|
+
if (
|
|
2201
|
+
file.includes("__tests__") ||
|
|
2202
|
+
file.includes(".test.") ||
|
|
2203
|
+
file.includes(".spec.")
|
|
2204
|
+
)
|
|
2205
|
+
continue;
|
|
2206
|
+
|
|
2207
|
+
try {
|
|
2208
|
+
const content = await fs.readFile(file, "utf-8");
|
|
2209
|
+
const lines = content.split("\n");
|
|
2210
|
+
|
|
2211
|
+
for (const { type, pattern } of SECRET_PATTERNS) {
|
|
2212
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2213
|
+
const matches = lines[i].match(pattern);
|
|
2214
|
+
if (matches) {
|
|
2215
|
+
for (const match of matches) {
|
|
2216
|
+
if (lines[i].toLowerCase().includes("example")) continue;
|
|
2217
|
+
secrets.push({
|
|
2218
|
+
type,
|
|
2219
|
+
file: path.relative(projectPath, file),
|
|
2220
|
+
line: i + 1,
|
|
2221
|
+
redacted:
|
|
2222
|
+
match.substring(0, 4) +
|
|
2223
|
+
"..." +
|
|
2224
|
+
match.substring(match.length - 4),
|
|
2225
|
+
});
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
} catch {}
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
const passed = secrets.length === 0;
|
|
2234
|
+
let response = passed
|
|
2235
|
+
? `✅ **Secret Scan Passed**\n\nNo secrets detected in codebase.\n`
|
|
2236
|
+
: `🛑 **Secrets Detected!**\n\nFound ${secrets.length} potential secret(s):\n\n`;
|
|
2237
|
+
|
|
2238
|
+
if (!passed) {
|
|
2239
|
+
secrets.slice(0, 10).forEach((s) => {
|
|
2240
|
+
response += `- **${s.type}**\n`;
|
|
2241
|
+
response += ` 📄 ${s.file}:${s.line}\n`;
|
|
2242
|
+
response += ` 🔑 \`${s.redacted}\`\n\n`;
|
|
2243
|
+
});
|
|
2244
|
+
|
|
2245
|
+
if (secrets.length > 10) {
|
|
2246
|
+
response += `... and ${secrets.length - 10} more secrets\n\n`;
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
response += `⚠️ **Action Required:** Rotate these secrets immediately!\n`;
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
return {
|
|
2253
|
+
content: [{ type: "text", text: response }],
|
|
2254
|
+
};
|
|
2255
|
+
} catch (error) {
|
|
2256
|
+
return {
|
|
2257
|
+
content: [
|
|
2258
|
+
{
|
|
2259
|
+
type: "text",
|
|
2260
|
+
text: `❌ Secret scan failed: ${error.message}`,
|
|
2261
|
+
},
|
|
2262
|
+
],
|
|
2263
|
+
isError: true,
|
|
2264
|
+
};
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
async supplyChainScan(projectPath, repoUrl) {
|
|
2269
|
+
try {
|
|
2270
|
+
let response = `📦 **Supply Chain Analysis**\n\n`;
|
|
2271
|
+
|
|
2272
|
+
// Try npm audit
|
|
2273
|
+
try {
|
|
2274
|
+
execSync("npm audit --json", { cwd: projectPath, encoding: "utf-8" });
|
|
2275
|
+
response += `✅ **npm audit:** No vulnerabilities found\n\n`;
|
|
2276
|
+
} catch (error) {
|
|
2277
|
+
if (error.stdout) {
|
|
2278
|
+
try {
|
|
2279
|
+
const result = JSON.parse(error.stdout);
|
|
2280
|
+
const vulnCount = result.metadata?.vulnerabilities || {};
|
|
2281
|
+
const total =
|
|
2282
|
+
(vulnCount.critical || 0) +
|
|
2283
|
+
(vulnCount.high || 0) +
|
|
2284
|
+
(vulnCount.moderate || 0) +
|
|
2285
|
+
(vulnCount.low || 0);
|
|
2286
|
+
|
|
2287
|
+
if (total > 0) {
|
|
2288
|
+
response += `⚠️ **npm audit:** Found ${total} vulnerabilities\n`;
|
|
2289
|
+
response += `- Critical: ${vulnCount.critical || 0}\n`;
|
|
2290
|
+
response += `- High: ${vulnCount.high || 0}\n`;
|
|
2291
|
+
response += `- Moderate: ${vulnCount.moderate || 0}\n`;
|
|
2292
|
+
response += `- Low: ${vulnCount.low || 0}\n\n`;
|
|
2293
|
+
} else {
|
|
2294
|
+
response += `✅ **npm audit:** No vulnerabilities found\n\n`;
|
|
2295
|
+
}
|
|
2296
|
+
} catch {
|
|
2297
|
+
response += `⚠️ **npm audit:** Could not parse results\n\n`;
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
// Count dependencies
|
|
2303
|
+
try {
|
|
2304
|
+
const pkgPath = path.join(projectPath, "package.json");
|
|
2305
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, "utf8"));
|
|
2306
|
+
const deps = Object.keys(pkg.dependencies || {}).length;
|
|
2307
|
+
const devDeps = Object.keys(pkg.devDependencies || {}).length;
|
|
2308
|
+
|
|
2309
|
+
response += `📊 **Dependencies:**\n`;
|
|
2310
|
+
response += `- Production: ${deps}\n`;
|
|
2311
|
+
response += `- Development: ${devDeps}\n`;
|
|
2312
|
+
response += `- Total: ${deps + devDeps}\n\n`;
|
|
2313
|
+
} catch {}
|
|
2314
|
+
|
|
2315
|
+
// License check (basic)
|
|
2316
|
+
response += `📜 **License compliance:** Run \`npm run ship:badge\` for full analysis\n`;
|
|
2317
|
+
|
|
2318
|
+
return {
|
|
2319
|
+
content: [{ type: "text", text: response }],
|
|
2320
|
+
};
|
|
2321
|
+
} catch (error) {
|
|
2322
|
+
return {
|
|
2323
|
+
content: [
|
|
2324
|
+
{
|
|
2325
|
+
type: "text",
|
|
2326
|
+
text: `❌ Supply chain scan failed: ${error.message}`,
|
|
2327
|
+
},
|
|
2328
|
+
],
|
|
2329
|
+
isError: true,
|
|
2330
|
+
};
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
async shipCheck(projectPath) {
|
|
2335
|
+
try {
|
|
2336
|
+
// Run ship check with JSON output to get structured results
|
|
2337
|
+
let output;
|
|
2338
|
+
let exitCode = 0;
|
|
2339
|
+
try {
|
|
2340
|
+
output = execSync("npx ts-node src/bin/ship.ts check --json", {
|
|
2341
|
+
cwd: projectPath,
|
|
2342
|
+
encoding: "utf8",
|
|
2343
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
2344
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
2345
|
+
});
|
|
2346
|
+
} catch (e) {
|
|
2347
|
+
output = e.stdout || "";
|
|
2348
|
+
exitCode = e.status || 1;
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
// Find the latest run directory
|
|
2352
|
+
const runsDir = path.join(projectPath, ".GUARDRAIL", "runs");
|
|
2353
|
+
let runId = null;
|
|
2354
|
+
let summary = null;
|
|
2355
|
+
let artifacts = {};
|
|
2356
|
+
|
|
2357
|
+
try {
|
|
2358
|
+
const runs = await fs.readdir(runsDir);
|
|
2359
|
+
if (runs.length > 0) {
|
|
2360
|
+
runs.sort().reverse();
|
|
2361
|
+
runId = runs[0];
|
|
2362
|
+
const runDir = path.join(runsDir, runId);
|
|
2363
|
+
|
|
2364
|
+
// Read summary
|
|
2365
|
+
const summaryPath = path.join(runDir, "summary.json");
|
|
2366
|
+
if (
|
|
2367
|
+
await fs
|
|
2368
|
+
.access(summaryPath)
|
|
2369
|
+
.then(() => true)
|
|
2370
|
+
.catch(() => false)
|
|
2371
|
+
) {
|
|
2372
|
+
summary = JSON.parse(await fs.readFile(summaryPath, "utf-8"));
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
// Collect artifact paths
|
|
2376
|
+
artifacts = {
|
|
2377
|
+
runDir,
|
|
2378
|
+
report: path.join(runDir, "report.json"),
|
|
2379
|
+
reportTxt: path.join(runDir, "report.txt"),
|
|
2380
|
+
sarif: path.join(runDir, "sarif.json"),
|
|
2381
|
+
replay: path.join(runDir, "replay", "replay.json"),
|
|
2382
|
+
badges: path.join(runDir, "badges"),
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2385
|
+
} catch (e) {
|
|
2386
|
+
// No runs yet
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
const passed = summary?.verdict === "ship" || exitCode === 0;
|
|
2390
|
+
const icon = passed ? "✅" : "🛑";
|
|
2391
|
+
|
|
2392
|
+
let response = `${icon} **Ship Check: ${passed ? "SHIP" : "NO-SHIP"}**\n\n`;
|
|
2393
|
+
|
|
2394
|
+
if (summary) {
|
|
2395
|
+
response += `**Score:** ${summary.score}/100\n`;
|
|
2396
|
+
response += `**Exit Code:** ${summary.exitCode}\n\n`;
|
|
2397
|
+
|
|
2398
|
+
response += `**Gates:**\n`;
|
|
2399
|
+
response += `- MockProof: ${summary.gates.mockproof.verdict === "pass" ? "✅" : "❌"} (${summary.gates.mockproof.violations} violations)\n`;
|
|
2400
|
+
response += `- Badge: ${summary.gates.badge.verdict === "pass" ? "✅" : summary.gates.badge.verdict === "fail" ? "❌" : "⏭️"} (${summary.gates.badge.score}/100)\n`;
|
|
2401
|
+
response += `- Reality: ${summary.gates.reality.verdict}\n\n`;
|
|
2402
|
+
|
|
2403
|
+
if (summary.blockers?.length > 0) {
|
|
2404
|
+
response += `**Blockers:**\n`;
|
|
2405
|
+
summary.blockers.slice(0, 5).forEach((b) => {
|
|
2406
|
+
response += `- ❌ ${b}\n`;
|
|
2407
|
+
});
|
|
2408
|
+
response += "\n";
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
if (runId) {
|
|
2413
|
+
response += `**Run ID:** \`${runId}\`\n`;
|
|
2414
|
+
response += `**Artifacts:**\n`;
|
|
2415
|
+
response += `- Report: \`${path.relative(projectPath, artifacts.report)}\`\n`;
|
|
2416
|
+
response += `- SARIF: \`${path.relative(projectPath, artifacts.sarif)}\`\n`;
|
|
2417
|
+
response += `- Replay: \`${path.relative(projectPath, artifacts.replay)}\`\n`;
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
// Return structured data for MCP clients
|
|
2421
|
+
return {
|
|
2422
|
+
content: [
|
|
2423
|
+
{ type: "text", text: response },
|
|
2424
|
+
{
|
|
2425
|
+
type: "text",
|
|
2426
|
+
text: JSON.stringify(
|
|
2427
|
+
{
|
|
2428
|
+
verdict: passed ? "ship" : "no-ship",
|
|
2429
|
+
runId,
|
|
2430
|
+
score: summary?.score || 0,
|
|
2431
|
+
exitCode: summary?.exitCode || exitCode,
|
|
2432
|
+
blockers: summary?.blockers || [],
|
|
2433
|
+
artifacts: runId
|
|
2434
|
+
? {
|
|
2435
|
+
runDir: artifacts.runDir,
|
|
2436
|
+
report: artifacts.report,
|
|
2437
|
+
sarif: artifacts.sarif,
|
|
2438
|
+
replay: artifacts.replay,
|
|
2439
|
+
}
|
|
2440
|
+
: null,
|
|
2441
|
+
},
|
|
2442
|
+
null,
|
|
2443
|
+
2,
|
|
2444
|
+
),
|
|
2445
|
+
mimeType: "application/json",
|
|
2446
|
+
},
|
|
2447
|
+
],
|
|
2448
|
+
};
|
|
2449
|
+
} catch (error) {
|
|
2450
|
+
return {
|
|
2451
|
+
content: [
|
|
2452
|
+
{
|
|
2453
|
+
type: "text",
|
|
2454
|
+
text: `❌ Ship check failed: ${error.message}\n\nTip: Run \`GUARDRAIL ship\` to see detailed output.`,
|
|
2455
|
+
},
|
|
2456
|
+
],
|
|
2457
|
+
isError: true,
|
|
2458
|
+
};
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
async getDeployVerdict(projectPath) {
|
|
2463
|
+
try {
|
|
2464
|
+
// Find the latest run
|
|
2465
|
+
const runsDir = path.join(projectPath, ".GUARDRAIL", "runs");
|
|
2466
|
+
let latestRun = null;
|
|
2467
|
+
let summary = null;
|
|
2468
|
+
let metadata = null;
|
|
2469
|
+
|
|
2470
|
+
try {
|
|
2471
|
+
const runs = await fs.readdir(runsDir);
|
|
2472
|
+
if (runs.length > 0) {
|
|
2473
|
+
runs.sort().reverse();
|
|
2474
|
+
latestRun = runs[0];
|
|
2475
|
+
const runDir = path.join(runsDir, latestRun);
|
|
2476
|
+
|
|
2477
|
+
const summaryPath = path.join(runDir, "summary.json");
|
|
2478
|
+
const metadataPath = path.join(runDir, "metadata.json");
|
|
2479
|
+
|
|
2480
|
+
if (
|
|
2481
|
+
await fs
|
|
2482
|
+
.access(summaryPath)
|
|
2483
|
+
.then(() => true)
|
|
2484
|
+
.catch(() => false)
|
|
2485
|
+
) {
|
|
2486
|
+
summary = JSON.parse(await fs.readFile(summaryPath, "utf-8"));
|
|
2487
|
+
}
|
|
2488
|
+
if (
|
|
2489
|
+
await fs
|
|
2490
|
+
.access(metadataPath)
|
|
2491
|
+
.then(() => true)
|
|
2492
|
+
.catch(() => false)
|
|
2493
|
+
) {
|
|
2494
|
+
metadata = JSON.parse(await fs.readFile(metadataPath, "utf-8"));
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
} catch {
|
|
2498
|
+
// No runs available
|
|
2499
|
+
}
|
|
2500
|
+
|
|
2501
|
+
// If no runs, prompt user to run ship first
|
|
2502
|
+
if (!summary) {
|
|
2503
|
+
return {
|
|
2504
|
+
content: [
|
|
2505
|
+
{
|
|
2506
|
+
type: "text",
|
|
2507
|
+
text:
|
|
2508
|
+
`⚠️ **No Ship Run Found**\n\nRun \`GUARDRAIL ship\` first to generate a deploy verdict.\n\n` +
|
|
2509
|
+
`This will:\n` +
|
|
2510
|
+
`1. Scan for mock data (MockProof)\n` +
|
|
2511
|
+
`2. Generate ship badge\n` +
|
|
2512
|
+
`3. Create Reality Mode test\n` +
|
|
2513
|
+
`4. Produce a deploy verdict`,
|
|
2514
|
+
},
|
|
2515
|
+
],
|
|
2516
|
+
};
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
const allowed = summary.verdict === "ship";
|
|
2520
|
+
const icon = allowed ? "🚀" : "🛑";
|
|
2521
|
+
|
|
2522
|
+
let response = `${icon} **Deploy Verdict: ${allowed ? "SHIP" : "NO SHIP"}**\n\n`;
|
|
2523
|
+
|
|
2524
|
+
response += `**Run:** \`${latestRun}\`\n`;
|
|
2525
|
+
response += `**Score:** ${summary.score}/100\n`;
|
|
2526
|
+
response += `**Duration:** ${summary.duration}ms\n\n`;
|
|
2527
|
+
|
|
2528
|
+
if (metadata) {
|
|
2529
|
+
response += `**Context:**\n`;
|
|
2530
|
+
response += `- Commit: \`${metadata.commitSha?.substring(0, 8) || "N/A"}\`\n`;
|
|
2531
|
+
response += `- Branch: \`${metadata.branch || "N/A"}\`\n`;
|
|
2532
|
+
response += `- Policy: \`${metadata.policyHash}\`\n\n`;
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
response += `**Gates:**\n`;
|
|
2536
|
+
response += `- MockProof: ${summary.gates.mockproof.verdict === "pass" ? "✅" : "❌"} (${summary.gates.mockproof.violations} violations)\n`;
|
|
2537
|
+
response += `- Badge: ${summary.gates.badge.verdict === "pass" ? "✅" : summary.gates.badge.verdict === "fail" ? "❌" : "⏭️"} (${summary.gates.badge.score}/100)\n`;
|
|
2538
|
+
response += `- Reality: ${summary.gates.reality.verdict}\n\n`;
|
|
2539
|
+
|
|
2540
|
+
if (!allowed && summary.blockers?.length > 0) {
|
|
2541
|
+
response += `**Blockers:**\n`;
|
|
2542
|
+
summary.blockers.forEach((b) => {
|
|
2543
|
+
response += `- ❌ ${b}\n`;
|
|
2544
|
+
});
|
|
2545
|
+
response += "\n";
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
if (allowed) {
|
|
2549
|
+
response += `✅ **Ready to deploy!** All checks passed.\n\n`;
|
|
2550
|
+
response += `Add to README:\n\`\`\`markdown\n[](https://GUARDRAIL.dev)\n\`\`\`\n`;
|
|
2551
|
+
} else {
|
|
2552
|
+
response += `⚠️ **Fix blockers before deploying.**\n\n`;
|
|
2553
|
+
response += `Run \`GUARDRAIL ship\` locally for detailed fix suggestions.\n`;
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
// Return structured data
|
|
2557
|
+
return {
|
|
2558
|
+
content: [
|
|
2559
|
+
{ type: "text", text: response },
|
|
2560
|
+
{
|
|
2561
|
+
type: "text",
|
|
2562
|
+
text: JSON.stringify(
|
|
2563
|
+
{
|
|
2564
|
+
verdict: summary.verdict,
|
|
2565
|
+
runId: latestRun,
|
|
2566
|
+
score: summary.score,
|
|
2567
|
+
exitCode: summary.exitCode,
|
|
2568
|
+
gates: summary.gates,
|
|
2569
|
+
blockers: summary.blockers,
|
|
2570
|
+
metadata: metadata
|
|
2571
|
+
? {
|
|
2572
|
+
commitSha: metadata.commitSha,
|
|
2573
|
+
branch: metadata.branch,
|
|
2574
|
+
policyHash: metadata.policyHash,
|
|
2575
|
+
timestamp: metadata.timestamp,
|
|
2576
|
+
}
|
|
2577
|
+
: null,
|
|
2578
|
+
},
|
|
2579
|
+
null,
|
|
2580
|
+
2,
|
|
2581
|
+
),
|
|
2582
|
+
mimeType: "application/json",
|
|
2583
|
+
},
|
|
2584
|
+
],
|
|
2585
|
+
};
|
|
2586
|
+
} catch (error) {
|
|
2587
|
+
return {
|
|
2588
|
+
content: [
|
|
2589
|
+
{
|
|
2590
|
+
type: "text",
|
|
2591
|
+
text: `❌ Could not get deploy verdict: ${error.message}\n\nTip: Run \`GUARDRAIL ship\` first.`,
|
|
2592
|
+
},
|
|
2593
|
+
],
|
|
2594
|
+
isError: true,
|
|
2595
|
+
};
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2599
|
+
async realityCheck(projectPath, mode = "full", file = null) {
|
|
2600
|
+
try {
|
|
2601
|
+
this.logger.info(
|
|
2602
|
+
`🔮 Reality Check starting: mode=${mode}, path=${projectPath}`,
|
|
2603
|
+
);
|
|
2604
|
+
|
|
2605
|
+
// Code-only mode: analyze specific file for self-deception
|
|
2606
|
+
if (mode === "code-only" && file) {
|
|
2607
|
+
return await this.realityCheckCodeOnly(projectPath, file);
|
|
2608
|
+
}
|
|
2609
|
+
|
|
2610
|
+
// Full or Quick mode: run production integrity suite
|
|
2611
|
+
const { auditProductionIntegrity, formatProductionResults } = require(
|
|
2612
|
+
path.join(__dirname, "..", "scripts", "audit-production-integrity.js"),
|
|
2613
|
+
);
|
|
2614
|
+
|
|
2615
|
+
const { results, integrity } =
|
|
2616
|
+
await auditProductionIntegrity(projectPath);
|
|
2617
|
+
|
|
2618
|
+
// Build the Reality Check header
|
|
2619
|
+
let output = `# 🔮 REALITY CHECK\n`;
|
|
2620
|
+
output += `## "Where Your Code Lies To You"\n\n`;
|
|
2621
|
+
output += `**Project:** ${results.projectPath}\n`;
|
|
2622
|
+
output += `**Timestamp:** ${new Date().toISOString()}\n\n`;
|
|
2623
|
+
|
|
2624
|
+
// Big verdict box
|
|
2625
|
+
output += `\`\`\`\n`;
|
|
2626
|
+
output += `┌────────────────────────────────────────────┐\n`;
|
|
2627
|
+
output += `│ │\n`;
|
|
2628
|
+
output += `│ REALITY SCORE: ${String(integrity.score).padStart(3)} │\n`;
|
|
2629
|
+
output += `│ GRADE: ${integrity.grade.padStart(2)} │\n`;
|
|
2630
|
+
output += `│ │\n`;
|
|
2631
|
+
output += `│ ${integrity.canShip ? "✅ CLEAR TO SHIP " : "🚫 NOT READY TO SHIP "} │\n`;
|
|
2632
|
+
output += `│ │\n`;
|
|
2633
|
+
output += `└────────────────────────────────────────────┘\n`;
|
|
2634
|
+
output += `\`\`\`\n\n`;
|
|
2635
|
+
|
|
2636
|
+
// The Reality
|
|
2637
|
+
output += `## 🎭 The Reality\n\n`;
|
|
2638
|
+
output += `| What You Think | The Truth | Gap |\n`;
|
|
2639
|
+
output += `|----------------|-----------|-----|\n`;
|
|
2640
|
+
|
|
2641
|
+
const apiConnected = results.api?.summary?.connected || 0;
|
|
2642
|
+
const apiMissing = results.api?.summary?.missingBackend || 0;
|
|
2643
|
+
const apiTotal = apiConnected + apiMissing;
|
|
2644
|
+
if (apiTotal > 0) {
|
|
2645
|
+
output += `| "All APIs work" | ${apiMissing} endpoints don't exist | ${apiMissing > 0 ? "🔴" : "✅"} |\n`;
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
const authProtected = results.auth?.analysis?.protected?.length || 0;
|
|
2649
|
+
const authExposed =
|
|
2650
|
+
(results.auth?.analysis?.adminExposed?.length || 0) +
|
|
2651
|
+
(results.auth?.analysis?.sensitiveUnprotected?.length || 0);
|
|
2652
|
+
if (authProtected > 0 || authExposed > 0) {
|
|
2653
|
+
output += `| "App is secure" | ${authExposed} sensitive endpoints exposed | ${authExposed > 0 ? "🔴" : "✅"} |\n`;
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
const criticalSecrets =
|
|
2657
|
+
results.env?.secrets?.filter((s) => s.severity === "critical").length ||
|
|
2658
|
+
0;
|
|
2659
|
+
output += `| "Secrets are safe" | ${criticalSecrets} hardcoded in code | ${criticalSecrets > 0 ? "🔴" : "✅"} |\n`;
|
|
2660
|
+
|
|
2661
|
+
const deadLinks = results.routes?.integrity?.deadLinks?.length || 0;
|
|
2662
|
+
output += `| "All pages work" | ${deadLinks} links go to 404 | ${deadLinks > 0 ? "🔴" : "✅"} |\n`;
|
|
2663
|
+
|
|
2664
|
+
const mockCritical = (results.mocks?.issues || []).filter(
|
|
2665
|
+
(i) => i.severity === "critical",
|
|
2666
|
+
).length;
|
|
2667
|
+
const mockHigh = (results.mocks?.issues || []).filter(
|
|
2668
|
+
(i) => i.severity === "high",
|
|
2669
|
+
).length;
|
|
2670
|
+
output += `| "No test code in prod" | ${mockCritical + mockHigh} mock/test issues | ${mockCritical + mockHigh > 0 ? "🔴" : "✅"} |\n`;
|
|
2671
|
+
|
|
2672
|
+
output += `\n`;
|
|
2673
|
+
|
|
2674
|
+
// Quick mode stops here with summary
|
|
2675
|
+
if (mode === "quick") {
|
|
2676
|
+
output += `## 📊 Quick Summary\n\n`;
|
|
2677
|
+
output += `- **API Wiring:** ${apiConnected} connected, ${apiMissing} missing\n`;
|
|
2678
|
+
output += `- **Auth:** ${authProtected} protected, ${authExposed} exposed\n`;
|
|
2679
|
+
output += `- **Secrets:** ${criticalSecrets} critical issues\n`;
|
|
2680
|
+
output += `- **Routes:** ${deadLinks} dead links\n`;
|
|
2681
|
+
output += `- **Mock Code:** ${mockCritical + mockHigh} blocking issues\n\n`;
|
|
2682
|
+
output += `_Run with mode="full" for complete details._\n`;
|
|
2683
|
+
|
|
2684
|
+
return {
|
|
2685
|
+
content: [{ type: "text", text: output }],
|
|
2686
|
+
};
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
// Full mode: add complete production integrity report
|
|
2690
|
+
const fullReport = formatProductionResults({ results, integrity });
|
|
2691
|
+
|
|
2692
|
+
// Extract the detailed findings section from the full report
|
|
2693
|
+
const detailedStart = fullReport.indexOf("# 📋 Detailed Findings");
|
|
2694
|
+
if (detailedStart > 0) {
|
|
2695
|
+
output += fullReport.substring(detailedStart);
|
|
2696
|
+
} else {
|
|
2697
|
+
output += fullReport;
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
// Add JSON summary at the end
|
|
2701
|
+
const summary = {
|
|
2702
|
+
score: integrity.score,
|
|
2703
|
+
grade: integrity.grade,
|
|
2704
|
+
canShip: integrity.canShip,
|
|
2705
|
+
verdict: integrity.canShip ? "SHIP" : "NO_SHIP",
|
|
2706
|
+
counts: {
|
|
2707
|
+
api: { connected: apiConnected, missing: apiMissing },
|
|
2708
|
+
auth: { protected: authProtected, exposed: authExposed },
|
|
2709
|
+
secrets: { critical: criticalSecrets },
|
|
2710
|
+
routes: { deadLinks },
|
|
2711
|
+
mocks: { critical: mockCritical, high: mockHigh },
|
|
2712
|
+
},
|
|
2713
|
+
timestamp: new Date().toISOString(),
|
|
2714
|
+
};
|
|
2715
|
+
|
|
2716
|
+
output += `\n---\n\n`;
|
|
2717
|
+
output += `## 📦 JSON Summary\n\n`;
|
|
2718
|
+
output += `\`\`\`json\n${JSON.stringify(summary, null, 2)}\n\`\`\`\n`;
|
|
2719
|
+
|
|
2720
|
+
// Context attribution
|
|
2721
|
+
output += `\n---\n_Context Enhanced by Guardrail AI_\n`;
|
|
2722
|
+
|
|
2723
|
+
this.logger.info(
|
|
2724
|
+
`Reality Check complete: Score ${integrity.score}, Grade ${integrity.grade}`,
|
|
2725
|
+
);
|
|
2726
|
+
|
|
2727
|
+
return {
|
|
2728
|
+
content: [{ type: "text", text: output }],
|
|
2729
|
+
};
|
|
2730
|
+
} catch (error) {
|
|
2731
|
+
this.logger.error("Reality check failed", {
|
|
2732
|
+
error: error.message,
|
|
2733
|
+
stack: error.stack,
|
|
2734
|
+
});
|
|
2735
|
+
return {
|
|
2736
|
+
content: [
|
|
2737
|
+
{
|
|
2738
|
+
type: "text",
|
|
2739
|
+
text:
|
|
2740
|
+
`❌ Reality Check failed: ${error.message}\n\n` +
|
|
2741
|
+
`**Troubleshooting:**\n` +
|
|
2742
|
+
`- Ensure the project path exists and is accessible\n` +
|
|
2743
|
+
`- For code-only mode, provide a valid file path\n` +
|
|
2744
|
+
`- Try: \`node scripts/audit-production-integrity.js "${projectPath}"\``,
|
|
2745
|
+
},
|
|
2746
|
+
],
|
|
2747
|
+
isError: true,
|
|
2748
|
+
};
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2752
|
+
async realityCheckCodeOnly(projectPath, file) {
|
|
2753
|
+
try {
|
|
2754
|
+
const filePath = path.join(projectPath, file);
|
|
2755
|
+
const code = await fs.readFile(filePath, "utf8");
|
|
2756
|
+
|
|
2757
|
+
// Import and run the reality check service for code analysis
|
|
2758
|
+
const { realityCheck } =
|
|
2759
|
+
await import("../src/lib/reality-check-service.js");
|
|
2760
|
+
const result = await realityCheck.check(code, file);
|
|
2761
|
+
|
|
2762
|
+
// Format the output
|
|
2763
|
+
let output = `🔮 **Reality Check - Code Analysis**\n\n`;
|
|
2764
|
+
output += `📁 File: ${result.file}\n`;
|
|
2765
|
+
output += `📊 Reality Score: ${result.overallScore}/100\n`;
|
|
2766
|
+
output += `⏰ Analyzed: ${result.timestamp}\n\n`;
|
|
2767
|
+
|
|
2768
|
+
output += `## Summary\n`;
|
|
2769
|
+
output += `- 🔴 Critical: ${result.summary.critical}\n`;
|
|
2770
|
+
output += `- 🟡 Warnings: ${result.summary.warnings}\n`;
|
|
2771
|
+
output += `- 🔵 Suggestions: ${result.summary.suggestions}\n\n`;
|
|
2772
|
+
|
|
2773
|
+
if (result.findings.length === 0) {
|
|
2774
|
+
output += `✅ **No self-deception detected!** Your code does what you think it does.\n`;
|
|
2775
|
+
} else {
|
|
2776
|
+
output += `## Findings\n\n`;
|
|
2777
|
+
for (const finding of result.findings) {
|
|
2778
|
+
const icon =
|
|
2779
|
+
finding.type === "critical"
|
|
2780
|
+
? "❌"
|
|
2781
|
+
: finding.type === "warning"
|
|
2782
|
+
? "⚠️"
|
|
2783
|
+
: "💡";
|
|
2784
|
+
output += `### ${icon} ${finding.category.replace(/-/g, " ").toUpperCase()}\n`;
|
|
2785
|
+
if (finding.line) output += `📍 Line ${finding.line}\n`;
|
|
2786
|
+
output += `\`\`\`\n${finding.code}\n\`\`\`\n`;
|
|
2787
|
+
output += `**You think:** ${finding.intent}\n`;
|
|
2788
|
+
output += `**Reality:** ${finding.reality}\n`;
|
|
2789
|
+
output += `**Why it matters:** ${finding.explanation}\n`;
|
|
2790
|
+
output += `_Confidence: ${Math.round(finding.confidence * 100)}%_\n\n`;
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
output += `\n---\n_Context Enhanced by Guardrail AI_\n`;
|
|
2795
|
+
|
|
2796
|
+
return {
|
|
2797
|
+
content: [
|
|
2798
|
+
{ type: "text", text: output },
|
|
2799
|
+
{
|
|
2800
|
+
type: "text",
|
|
2801
|
+
text: JSON.stringify(result, null, 2),
|
|
2802
|
+
mimeType: "application/json",
|
|
2803
|
+
},
|
|
2804
|
+
],
|
|
2805
|
+
};
|
|
2806
|
+
} catch (error) {
|
|
2807
|
+
return {
|
|
2808
|
+
content: [
|
|
2809
|
+
{
|
|
2810
|
+
type: "text",
|
|
2811
|
+
text: `❌ Code analysis failed: ${error.message}\n\nMake sure the file exists and contains valid code.`,
|
|
2812
|
+
},
|
|
2813
|
+
],
|
|
2814
|
+
isError: true,
|
|
2815
|
+
};
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
async realityCheckDeep(
|
|
2820
|
+
projectPath,
|
|
2821
|
+
file,
|
|
2822
|
+
includeCallGraph = true,
|
|
2823
|
+
includeAsyncAnalysis = true,
|
|
2824
|
+
) {
|
|
2825
|
+
try {
|
|
2826
|
+
if (!file) {
|
|
2827
|
+
return {
|
|
2828
|
+
content: [
|
|
2829
|
+
{
|
|
2830
|
+
type: "text",
|
|
2831
|
+
text: "❌ Deep Reality Check requires a file path.",
|
|
2832
|
+
},
|
|
2833
|
+
],
|
|
2834
|
+
isError: true,
|
|
2835
|
+
};
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2838
|
+
const filePath = path.join(projectPath, file);
|
|
2839
|
+
const code = await fs.readFile(filePath, "utf8");
|
|
2840
|
+
|
|
2841
|
+
// Run basic reality check first
|
|
2842
|
+
const { realityCheck } =
|
|
2843
|
+
await import("../src/lib/reality-check-service.js");
|
|
2844
|
+
const basicResult = await realityCheck.check(code, file);
|
|
2845
|
+
|
|
2846
|
+
// Deep analysis additions
|
|
2847
|
+
let output = `🔮 **Deep Reality Check Results** (Pro)\n\n`;
|
|
2848
|
+
output += `📁 File: ${file}\n`;
|
|
2849
|
+
output += `📊 Reality Score: ${basicResult.overallScore}/100\n\n`;
|
|
2850
|
+
|
|
2851
|
+
output += `## Basic Analysis\n`;
|
|
2852
|
+
output += `- 🔴 Critical: ${basicResult.summary.critical}\n`;
|
|
2853
|
+
output += `- 🟡 Warnings: ${basicResult.summary.warnings}\n`;
|
|
2854
|
+
output += `- 🔵 Suggestions: ${basicResult.summary.suggestions}\n\n`;
|
|
2855
|
+
|
|
2856
|
+
if (includeCallGraph) {
|
|
2857
|
+
output += `## 📊 Call Graph Analysis\n`;
|
|
2858
|
+
output += `_Cross-file dependency tracking identifies assumptions about external code._\n\n`;
|
|
2859
|
+
// This would be enhanced with actual call graph analysis
|
|
2860
|
+
const imports = code.match(/import\s+.*from\s+['"]([^'"]+)['"]/g) || [];
|
|
2861
|
+
const calls = code.match(/\b(\w+)\s*\(/g) || [];
|
|
2862
|
+
output += `- Imports: ${imports.length}\n`;
|
|
2863
|
+
output += `- Function calls: ${[...new Set(calls)].length}\n\n`;
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
if (includeAsyncAnalysis) {
|
|
2867
|
+
output += `## ⏳ Async Lifecycle Analysis\n`;
|
|
2868
|
+
output += `_Timing assumptions are a major source of bugs._\n\n`;
|
|
2869
|
+
const asyncFns = (code.match(/async\s+function|\basync\s*\(/g) || [])
|
|
2870
|
+
.length;
|
|
2871
|
+
const awaits = (code.match(/\bawait\s+/g) || []).length;
|
|
2872
|
+
const promises = (code.match(/new\s+Promise|\.then\(|\.catch\(/g) || [])
|
|
2873
|
+
.length;
|
|
2874
|
+
output += `- Async functions: ${asyncFns}\n`;
|
|
2875
|
+
output += `- Await expressions: ${awaits}\n`;
|
|
2876
|
+
output += `- Promise patterns: ${promises}\n`;
|
|
2877
|
+
|
|
2878
|
+
if (asyncFns > 0 && awaits === 0) {
|
|
2879
|
+
output += `\n⚠️ **Found async functions with no awaits** - these may not need to be async.\n`;
|
|
2880
|
+
}
|
|
2881
|
+
output += `\n`;
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
output += `## All Findings\n\n`;
|
|
2885
|
+
for (const finding of basicResult.findings) {
|
|
2886
|
+
const icon =
|
|
2887
|
+
finding.type === "critical"
|
|
2888
|
+
? "❌"
|
|
2889
|
+
: finding.type === "warning"
|
|
2890
|
+
? "⚠️"
|
|
2891
|
+
: "💡";
|
|
2892
|
+
output += `### ${icon} ${finding.category.replace(/-/g, " ").toUpperCase()}\n`;
|
|
2893
|
+
if (finding.line) output += `📍 Line ${finding.line}\n`;
|
|
2894
|
+
output += `\`\`\`\n${finding.code}\n\`\`\`\n`;
|
|
2895
|
+
output += `**You think:** ${finding.intent}\n`;
|
|
2896
|
+
output += `**Reality:** ${finding.reality}\n`;
|
|
2897
|
+
output += `**Why it matters:** ${finding.explanation}\n\n`;
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2900
|
+
return {
|
|
2901
|
+
content: [{ type: "text", text: output }],
|
|
2902
|
+
};
|
|
2903
|
+
} catch (error) {
|
|
2904
|
+
return {
|
|
2905
|
+
content: [
|
|
2906
|
+
{
|
|
2907
|
+
type: "text",
|
|
2908
|
+
text: `❌ Deep Reality Check failed: ${error.message}`,
|
|
2909
|
+
},
|
|
2910
|
+
],
|
|
2911
|
+
isError: true,
|
|
2912
|
+
};
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
async auditApiEndpoints(projectPath, showDetails = false) {
|
|
2917
|
+
try {
|
|
2918
|
+
const {
|
|
2919
|
+
auditApiEndpoints,
|
|
2920
|
+
} = require("../scripts/audit-api-endpoints.js");
|
|
2921
|
+
const results = await auditApiEndpoints(projectPath);
|
|
2922
|
+
|
|
2923
|
+
// Format summary
|
|
2924
|
+
let response = `🔗 **API Endpoint Audit Report**\n\n`;
|
|
2925
|
+
response += `📊 **Summary:**\n`;
|
|
2926
|
+
response += `| Metric | Count |\n`;
|
|
2927
|
+
response += `|--------|-------|\n`;
|
|
2928
|
+
response += `| Backend Endpoints | ${results.summary.totalBackendEndpoints} |\n`;
|
|
2929
|
+
response += `| Frontend API Calls | ${results.summary.totalFrontendCalls} |\n`;
|
|
2930
|
+
response += `| ✅ Connected | ${results.summary.connectedEndpoints} |\n`;
|
|
2931
|
+
response += `| ⚠️ Unused Backend | ${results.summary.unusedBackendEndpoints} |\n`;
|
|
2932
|
+
response += `| ❌ Missing Backend | ${results.summary.missingBackendEndpoints} |\n\n`;
|
|
2933
|
+
|
|
2934
|
+
// Missing backend implementations (priority)
|
|
2935
|
+
if (results.missing.length > 0) {
|
|
2936
|
+
response += `## ❌ Missing Backend Implementations\n\n`;
|
|
2937
|
+
response += `These frontend API calls have **no backend endpoint**:\n\n`;
|
|
2938
|
+
results.missing.slice(0, 15).forEach((item) => {
|
|
2939
|
+
response += `- **${item.method} ${item.path}**\n`;
|
|
2940
|
+
response += ` - File: \`${item.file}\`\n`;
|
|
2941
|
+
response += ` - 💡 ${item.suggestion}\n\n`;
|
|
2942
|
+
});
|
|
2943
|
+
if (results.missing.length > 15) {
|
|
2944
|
+
response += `... and ${results.missing.length - 15} more\n\n`;
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
|
|
2948
|
+
// Unused backend endpoints
|
|
2949
|
+
if (results.unused.length > 0) {
|
|
2950
|
+
response += `## ⚠️ Unused Backend Endpoints\n\n`;
|
|
2951
|
+
response += `These backend endpoints have **no frontend calls**:\n\n`;
|
|
2952
|
+
results.unused.slice(0, 15).forEach((item) => {
|
|
2953
|
+
response += `- **${item.method} ${item.path}** (\`${item.file}\`)\n`;
|
|
2954
|
+
});
|
|
2955
|
+
if (results.unused.length > 15) {
|
|
2956
|
+
response += `... and ${results.unused.length - 15} more\n\n`;
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
// Connected (if showDetails)
|
|
2961
|
+
if (showDetails && results.connected.length > 0) {
|
|
2962
|
+
response += `## ✅ Connected Endpoints\n\n`;
|
|
2963
|
+
results.connected.forEach((item) => {
|
|
2964
|
+
response += `- ${item.method} ${item.path}\n`;
|
|
2965
|
+
});
|
|
2966
|
+
response += "\n";
|
|
2967
|
+
} else if (results.connected.length > 0) {
|
|
2968
|
+
response += `✅ **${results.connected.length} endpoints properly connected**\n\n`;
|
|
2969
|
+
}
|
|
2970
|
+
|
|
2971
|
+
// Recommendations
|
|
2972
|
+
response += `## 💡 Recommendations\n\n`;
|
|
2973
|
+
if (results.missing.length > 0) {
|
|
2974
|
+
response += `1. **Implement missing backend routes** - ${results.missing.length} frontend calls need backend endpoints\n`;
|
|
2975
|
+
}
|
|
2976
|
+
if (results.unused.length > 0) {
|
|
2977
|
+
response += `2. **Review unused endpoints** - ${results.unused.length} backend routes may be dead code or need frontend integration\n`;
|
|
2978
|
+
}
|
|
2979
|
+
if (results.missing.length === 0 && results.unused.length === 0) {
|
|
2980
|
+
response += `🎉 All endpoints are properly connected!\n`;
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
return {
|
|
2984
|
+
content: [
|
|
2985
|
+
{ type: "text", text: response },
|
|
2986
|
+
{
|
|
2987
|
+
type: "text",
|
|
2988
|
+
text: JSON.stringify(
|
|
2989
|
+
{
|
|
2990
|
+
summary: results.summary,
|
|
2991
|
+
missing: results.missing,
|
|
2992
|
+
unused: results.unused,
|
|
2993
|
+
connected: results.connected.length,
|
|
2994
|
+
},
|
|
2995
|
+
null,
|
|
2996
|
+
2,
|
|
2997
|
+
),
|
|
2998
|
+
mimeType: "application/json",
|
|
2999
|
+
},
|
|
3000
|
+
],
|
|
3001
|
+
};
|
|
3002
|
+
} catch (error) {
|
|
3003
|
+
this.logger.error("API audit failed", {
|
|
3004
|
+
error: error.message,
|
|
3005
|
+
projectPath,
|
|
3006
|
+
});
|
|
3007
|
+
return {
|
|
3008
|
+
content: [
|
|
3009
|
+
{
|
|
3010
|
+
type: "text",
|
|
3011
|
+
text:
|
|
3012
|
+
`❌ API endpoint audit failed: ${error.message}\n\n` +
|
|
3013
|
+
`Tip: Ensure you're in a project with:\n` +
|
|
3014
|
+
`- Backend routes in \`apps/api/src/routes/\` or \`server/routes/\`\n` +
|
|
3015
|
+
`- Frontend code in \`apps/web-ui/src/\` or \`src/\``,
|
|
3016
|
+
},
|
|
3017
|
+
],
|
|
3018
|
+
isError: true,
|
|
3019
|
+
};
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
|
|
3023
|
+
async productionIntegrityCheck(projectPath) {
|
|
3024
|
+
try {
|
|
3025
|
+
const { auditProductionIntegrity, formatProductionResults } = require(
|
|
3026
|
+
path.join(__dirname, "..", "scripts", "audit-production-integrity.js"),
|
|
3027
|
+
);
|
|
3028
|
+
|
|
3029
|
+
this.logger.info(
|
|
3030
|
+
`🛡️ Running Production Integrity Check on: ${projectPath}`,
|
|
3031
|
+
);
|
|
3032
|
+
|
|
3033
|
+
const { results, integrity } =
|
|
3034
|
+
await auditProductionIntegrity(projectPath);
|
|
3035
|
+
const report = formatProductionResults({ results, integrity });
|
|
3036
|
+
|
|
3037
|
+
// Build comprehensive JSON summary
|
|
3038
|
+
const summary = {
|
|
3039
|
+
score: integrity.score,
|
|
3040
|
+
grade: integrity.grade,
|
|
3041
|
+
canShip: integrity.canShip,
|
|
3042
|
+
verdict: integrity.canShip ? "SHIP" : "NO_SHIP",
|
|
3043
|
+
deductions: integrity.deductions,
|
|
3044
|
+
counts: {
|
|
3045
|
+
api: {
|
|
3046
|
+
connected: results.api?.summary?.connected || 0,
|
|
3047
|
+
missing: results.api?.summary?.missingBackend || 0,
|
|
3048
|
+
unused: results.api?.summary?.unusedBackend || 0,
|
|
3049
|
+
},
|
|
3050
|
+
auth: {
|
|
3051
|
+
protected: results.auth?.analysis?.protected?.length || 0,
|
|
3052
|
+
unprotected: results.auth?.analysis?.unprotected?.length || 0,
|
|
3053
|
+
adminExposed: results.auth?.analysis?.adminExposed?.length || 0,
|
|
3054
|
+
},
|
|
3055
|
+
secrets: {
|
|
3056
|
+
critical:
|
|
3057
|
+
results.env?.secrets?.filter((s) => s.severity === "critical")
|
|
3058
|
+
.length || 0,
|
|
3059
|
+
high:
|
|
3060
|
+
results.env?.secrets?.filter((s) => s.severity === "high")
|
|
3061
|
+
.length || 0,
|
|
3062
|
+
devUrls: results.env?.devUrls?.length || 0,
|
|
3063
|
+
envVars: results.env?.envExample?.variables?.length || 0,
|
|
3064
|
+
},
|
|
3065
|
+
routes: {
|
|
3066
|
+
pages: results.routes?.pages?.length || 0,
|
|
3067
|
+
deadLinks: results.routes?.integrity?.deadLinks?.length || 0,
|
|
3068
|
+
placeholders: results.routes?.placeholders?.length || 0,
|
|
3069
|
+
},
|
|
3070
|
+
mocks: {
|
|
3071
|
+
critical: (results.mocks?.issues || []).filter(
|
|
3072
|
+
(i) => i.severity === "critical",
|
|
3073
|
+
).length,
|
|
3074
|
+
high: (results.mocks?.issues || []).filter(
|
|
3075
|
+
(i) => i.severity === "high",
|
|
3076
|
+
).length,
|
|
3077
|
+
consoleLogs: (results.mocks?.issues || []).filter(
|
|
3078
|
+
(i) => i.name === "console.log",
|
|
3079
|
+
).length,
|
|
3080
|
+
},
|
|
3081
|
+
},
|
|
3082
|
+
timestamp: new Date().toISOString(),
|
|
3083
|
+
projectPath: results.projectPath,
|
|
3084
|
+
};
|
|
3085
|
+
|
|
3086
|
+
this.logger.info(
|
|
3087
|
+
`Production integrity check complete: Score ${integrity.score}, Grade ${integrity.grade}`,
|
|
3088
|
+
);
|
|
3089
|
+
|
|
3090
|
+
return {
|
|
3091
|
+
content: [
|
|
3092
|
+
{ type: "text", text: report },
|
|
3093
|
+
{
|
|
3094
|
+
type: "text",
|
|
3095
|
+
text: `\n---\n**JSON Summary:**\n\`\`\`json\n${JSON.stringify(summary, null, 2)}\n\`\`\``,
|
|
3096
|
+
},
|
|
3097
|
+
],
|
|
3098
|
+
};
|
|
3099
|
+
} catch (error) {
|
|
3100
|
+
this.logger.error("Production integrity check failed", {
|
|
3101
|
+
error: error.message,
|
|
3102
|
+
stack: error.stack,
|
|
3103
|
+
});
|
|
3104
|
+
return {
|
|
3105
|
+
content: [
|
|
3106
|
+
{
|
|
3107
|
+
type: "text",
|
|
3108
|
+
text:
|
|
3109
|
+
`❌ Production integrity check failed: ${error.message}\n\n` +
|
|
3110
|
+
`**Troubleshooting:**\n` +
|
|
3111
|
+
`- Ensure the project path exists\n` +
|
|
3112
|
+
`- Check that Node.js can access the directory\n` +
|
|
3113
|
+
`- Try running: \`node scripts/audit-production-integrity.js "${projectPath}"\``,
|
|
3114
|
+
},
|
|
3115
|
+
],
|
|
3116
|
+
isError: true,
|
|
3117
|
+
};
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
async auditAuthCoverage(projectPath) {
|
|
3122
|
+
try {
|
|
3123
|
+
const { auditAuthCoverage, formatAuthResults } = require(
|
|
3124
|
+
path.join(__dirname, "..", "scripts", "audit-auth-coverage.js"),
|
|
3125
|
+
);
|
|
3126
|
+
|
|
3127
|
+
const results = await auditAuthCoverage(projectPath);
|
|
3128
|
+
const report = formatAuthResults(results);
|
|
3129
|
+
|
|
3130
|
+
return {
|
|
3131
|
+
content: [{ type: "text", text: report }],
|
|
3132
|
+
};
|
|
3133
|
+
} catch (error) {
|
|
3134
|
+
this.logger.error("Auth coverage audit failed", { error: error.message });
|
|
3135
|
+
return {
|
|
3136
|
+
content: [
|
|
3137
|
+
{
|
|
3138
|
+
type: "text",
|
|
3139
|
+
text: `❌ Auth coverage audit failed: ${error.message}`,
|
|
3140
|
+
},
|
|
3141
|
+
],
|
|
3142
|
+
isError: true,
|
|
3143
|
+
};
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
|
|
3147
|
+
async auditEnvSecrets(projectPath) {
|
|
3148
|
+
try {
|
|
3149
|
+
const { auditEnvSecrets, formatEnvResults } = require(
|
|
3150
|
+
path.join(__dirname, "..", "scripts", "audit-env-secrets.js"),
|
|
3151
|
+
);
|
|
3152
|
+
|
|
3153
|
+
const results = await auditEnvSecrets(projectPath);
|
|
3154
|
+
const report = formatEnvResults(results);
|
|
3155
|
+
|
|
3156
|
+
return {
|
|
3157
|
+
content: [{ type: "text", text: report }],
|
|
3158
|
+
};
|
|
3159
|
+
} catch (error) {
|
|
3160
|
+
this.logger.error("Env secrets audit failed", { error: error.message });
|
|
3161
|
+
return {
|
|
3162
|
+
content: [
|
|
3163
|
+
{
|
|
3164
|
+
type: "text",
|
|
3165
|
+
text: `❌ Env secrets audit failed: ${error.message}`,
|
|
3166
|
+
},
|
|
3167
|
+
],
|
|
3168
|
+
isError: true,
|
|
3169
|
+
};
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
|
|
3173
|
+
async auditRouteIntegrity(projectPath) {
|
|
3174
|
+
try {
|
|
3175
|
+
const { auditRouteIntegrity, formatRouteResults } = require(
|
|
3176
|
+
path.join(__dirname, "..", "scripts", "audit-route-integrity.js"),
|
|
3177
|
+
);
|
|
3178
|
+
|
|
3179
|
+
const results = await auditRouteIntegrity(projectPath);
|
|
3180
|
+
const report = formatRouteResults(results);
|
|
3181
|
+
|
|
3182
|
+
return {
|
|
3183
|
+
content: [{ type: "text", text: report }],
|
|
3184
|
+
};
|
|
3185
|
+
} catch (error) {
|
|
3186
|
+
this.logger.error("Route integrity audit failed", {
|
|
3187
|
+
error: error.message,
|
|
3188
|
+
});
|
|
3189
|
+
return {
|
|
3190
|
+
content: [
|
|
3191
|
+
{
|
|
3192
|
+
type: "text",
|
|
3193
|
+
text: `❌ Route integrity audit failed: ${error.message}`,
|
|
3194
|
+
},
|
|
3195
|
+
],
|
|
3196
|
+
isError: true,
|
|
3197
|
+
};
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
async auditMockBlocker(projectPath) {
|
|
3202
|
+
try {
|
|
3203
|
+
const { auditMockBlocker, formatMockResults } = require(
|
|
3204
|
+
path.join(__dirname, "..", "scripts", "audit-mock-blocker.js"),
|
|
3205
|
+
);
|
|
3206
|
+
|
|
3207
|
+
const results = await auditMockBlocker(projectPath);
|
|
3208
|
+
const report = formatMockResults(results);
|
|
3209
|
+
|
|
3210
|
+
return {
|
|
3211
|
+
content: [{ type: "text", text: report }],
|
|
3212
|
+
};
|
|
3213
|
+
} catch (error) {
|
|
3214
|
+
this.logger.error("Mock blocker audit failed", { error: error.message });
|
|
3215
|
+
return {
|
|
3216
|
+
content: [
|
|
3217
|
+
{
|
|
3218
|
+
type: "text",
|
|
3219
|
+
text: `❌ Mock blocker audit failed: ${error.message}`,
|
|
3220
|
+
},
|
|
3221
|
+
],
|
|
3222
|
+
isError: true,
|
|
3223
|
+
};
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
|
|
3227
|
+
// ==================== REPO HYGIENE + DEBT RADAR ====================
|
|
3228
|
+
|
|
3229
|
+
async repoHygieneScan(projectPath, mode = "report", saveArtifacts = true) {
|
|
3230
|
+
try {
|
|
3231
|
+
this.logger.info(`🧹 Running repo hygiene scan on: ${projectPath}`);
|
|
3232
|
+
const result = await hygieneFullScan({
|
|
3233
|
+
projectPath,
|
|
3234
|
+
mode,
|
|
3235
|
+
saveArtifacts,
|
|
3236
|
+
});
|
|
3237
|
+
|
|
3238
|
+
let response = `# 🧹 Repo Hygiene + Debt Radar\n\n`;
|
|
3239
|
+
response += `**Score:** ${result.score.score}/100 (Grade: ${result.score.grade})\n`;
|
|
3240
|
+
response += `**Status:** ${result.score.status}\n\n`;
|
|
3241
|
+
|
|
3242
|
+
response += `## Summary\n\n`;
|
|
3243
|
+
response += `| Category | Count |\n|----------|-------|\n`;
|
|
3244
|
+
response += `| Exact Duplicates | ${result.summary.duplicates.exact} |\n`;
|
|
3245
|
+
response += `| Near-Duplicates | ${result.summary.duplicates.near} |\n`;
|
|
3246
|
+
response += `| Copy-Paste Blocks | ${result.summary.duplicates.copyPaste} |\n`;
|
|
3247
|
+
response += `| Definitely Unused | ${result.summary.unused.definitelyUnused} |\n`;
|
|
3248
|
+
response += `| Probably Unused | ${result.summary.unused.probablyUnused} |\n`;
|
|
3249
|
+
response += `| Lint/Type Errors | ${result.summary.errors.total} |\n`;
|
|
3250
|
+
response += `| Junk Files | ${result.summary.rootCleanup.junkFiles} |\n\n`;
|
|
3251
|
+
|
|
3252
|
+
if (saveArtifacts) {
|
|
3253
|
+
response += `📄 **Reports saved to:** .guardrail/\n`;
|
|
3254
|
+
response += result.artifacts.map((a) => `- ${a}`).join("\n");
|
|
3255
|
+
}
|
|
3256
|
+
|
|
3257
|
+
return { content: [{ type: "text", text: response }] };
|
|
3258
|
+
} catch (error) {
|
|
3259
|
+
this.logger.error("Repo hygiene scan failed", { error: error.message });
|
|
3260
|
+
return {
|
|
3261
|
+
content: [
|
|
3262
|
+
{
|
|
3263
|
+
type: "text",
|
|
3264
|
+
text: `❌ Repo hygiene scan failed: ${error.message}`,
|
|
3265
|
+
},
|
|
3266
|
+
],
|
|
3267
|
+
isError: true,
|
|
3268
|
+
};
|
|
3269
|
+
}
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
async repoHygieneDuplicates(projectPath, threshold = 0.85) {
|
|
3273
|
+
try {
|
|
3274
|
+
const result = await hygieneDuplicates({ projectPath, threshold });
|
|
3275
|
+
|
|
3276
|
+
let response = `# 📋 Duplicate File Analysis\n\n`;
|
|
3277
|
+
response += `**Exact Duplicates:** ${result.summary.exactCount}\n`;
|
|
3278
|
+
response += `**Near-Duplicates:** ${result.summary.nearCount}\n`;
|
|
3279
|
+
response += `**Copy-Paste Blocks:** ${result.summary.copyPasteCount}\n`;
|
|
3280
|
+
response += `**Total Wasted Bytes:** ${result.summary.totalWastedBytes}\n\n`;
|
|
3281
|
+
|
|
3282
|
+
if (result.exact.length > 0) {
|
|
3283
|
+
response += `## Exact Duplicates\n\n`;
|
|
3284
|
+
for (const group of result.exact.slice(0, 10)) {
|
|
3285
|
+
response += `**Hash:** \`${group.hash}\`\n`;
|
|
3286
|
+
group.files.forEach((f) => (response += `- \`${f.path}\`\n`));
|
|
3287
|
+
response += `\n`;
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
|
|
3291
|
+
if (result.near.length > 0) {
|
|
3292
|
+
response += `## Near-Duplicates (≥${threshold * 100}% similar)\n\n`;
|
|
3293
|
+
for (const group of result.near.slice(0, 10)) {
|
|
3294
|
+
response += `**Similarity:** ${group.similarity}%\n`;
|
|
3295
|
+
group.files.forEach((f) => (response += `- \`${f}\`\n`));
|
|
3296
|
+
response += `\n`;
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3300
|
+
return { content: [{ type: "text", text: response }] };
|
|
3301
|
+
} catch (error) {
|
|
3302
|
+
return {
|
|
3303
|
+
content: [
|
|
3304
|
+
{ type: "text", text: `❌ Duplicate scan failed: ${error.message}` },
|
|
3305
|
+
],
|
|
3306
|
+
isError: true,
|
|
3307
|
+
};
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
|
|
3311
|
+
async repoHygieneUnused(projectPath, scope = "all") {
|
|
3312
|
+
try {
|
|
3313
|
+
const result = await hygieneUnused({ projectPath, scope });
|
|
3314
|
+
|
|
3315
|
+
let response = `# 📦 Unused File Analysis\n\n`;
|
|
3316
|
+
response += `**Total Files:** ${result.stats.totalFiles}\n`;
|
|
3317
|
+
response += `**Entrypoints:** ${result.stats.entrypoints}\n`;
|
|
3318
|
+
response += `**Reachable:** ${result.stats.reachable}\n`;
|
|
3319
|
+
response += `**Unreachable:** ${result.stats.unreachable}\n\n`;
|
|
3320
|
+
|
|
3321
|
+
if (result.safeToDelete.length > 0) {
|
|
3322
|
+
response += `## ✅ Safe to Delete (${result.safeToDelete.length} files)\n\n`;
|
|
3323
|
+
result.safeToDelete
|
|
3324
|
+
.slice(0, 20)
|
|
3325
|
+
.forEach((f) => (response += `- \`${f}\`\n`));
|
|
3326
|
+
if (result.safeToDelete.length > 20)
|
|
3327
|
+
response += `- ... and ${result.safeToDelete.length - 20} more\n`;
|
|
3328
|
+
response += `\n`;
|
|
3329
|
+
}
|
|
3330
|
+
|
|
3331
|
+
if (result.reviewFirst.length > 0) {
|
|
3332
|
+
response += `## 🟡 Review First (${result.reviewFirst.length} files)\n\n`;
|
|
3333
|
+
result.reviewFirst
|
|
3334
|
+
.slice(0, 10)
|
|
3335
|
+
.forEach((f) => (response += `- \`${f}\`\n`));
|
|
3336
|
+
response += `\n`;
|
|
3337
|
+
}
|
|
3338
|
+
|
|
3339
|
+
return { content: [{ type: "text", text: response }] };
|
|
3340
|
+
} catch (error) {
|
|
3341
|
+
return {
|
|
3342
|
+
content: [
|
|
3343
|
+
{
|
|
3344
|
+
type: "text",
|
|
3345
|
+
text: `❌ Unused file scan failed: ${error.message}`,
|
|
3346
|
+
},
|
|
3347
|
+
],
|
|
3348
|
+
isError: true,
|
|
3349
|
+
};
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3353
|
+
async repoHygieneErrors(projectPath, options = {}) {
|
|
3354
|
+
try {
|
|
3355
|
+
const result = await hygieneErrors({ projectPath, ...options });
|
|
3356
|
+
|
|
3357
|
+
let response = `# 🔴 Lint/Type/Import Errors\n\n`;
|
|
3358
|
+
response += `**Total:** ${result.summary.total}\n`;
|
|
3359
|
+
response += `**Errors:** ${result.summary.bySeverity.error}\n`;
|
|
3360
|
+
response += `**Warnings:** ${result.summary.bySeverity.warning}\n`;
|
|
3361
|
+
response += `**Auto-fixable:** ${result.summary.autoFixable}\n\n`;
|
|
3362
|
+
|
|
3363
|
+
response += `## By Category\n\n`;
|
|
3364
|
+
response += `| Category | Count |\n|----------|-------|\n`;
|
|
3365
|
+
Object.entries(result.summary.byCategory).forEach(([cat, count]) => {
|
|
3366
|
+
if (count > 0) response += `| ${cat} | ${count} |\n`;
|
|
3367
|
+
});
|
|
3368
|
+
response += `\n`;
|
|
3369
|
+
|
|
3370
|
+
if (result.topOffenders.length > 0) {
|
|
3371
|
+
response += `## Top Offending Files\n\n`;
|
|
3372
|
+
result.topOffenders
|
|
3373
|
+
.slice(0, 15)
|
|
3374
|
+
.forEach((o) => (response += `- \`${o.file}\`: ${o.count} errors\n`));
|
|
3375
|
+
}
|
|
3376
|
+
|
|
3377
|
+
return { content: [{ type: "text", text: response }] };
|
|
3378
|
+
} catch (error) {
|
|
3379
|
+
return {
|
|
3380
|
+
content: [
|
|
3381
|
+
{
|
|
3382
|
+
type: "text",
|
|
3383
|
+
text: `❌ Error collection failed: ${error.message}`,
|
|
3384
|
+
},
|
|
3385
|
+
],
|
|
3386
|
+
isError: true,
|
|
3387
|
+
};
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
|
|
3391
|
+
async repoHygieneRootCleanup(projectPath) {
|
|
3392
|
+
try {
|
|
3393
|
+
const result = await hygieneRootCleanup({ projectPath });
|
|
3394
|
+
|
|
3395
|
+
let response = `# 🏠 Root Directory Cleanup\n\n`;
|
|
3396
|
+
|
|
3397
|
+
if (result.junkFiles.length > 0) {
|
|
3398
|
+
response += `## Junk Files (${result.junkFiles.length})\n\n`;
|
|
3399
|
+
result.junkFiles.forEach(
|
|
3400
|
+
(j) => (response += `- \`${j.file}\` - ${j.reason}\n`),
|
|
3401
|
+
);
|
|
3402
|
+
response += `\n`;
|
|
3403
|
+
}
|
|
3404
|
+
|
|
3405
|
+
if (result.missingStandards.length > 0) {
|
|
3406
|
+
response += `## Missing Standards\n\n`;
|
|
3407
|
+
result.missingStandards.forEach((s) => {
|
|
3408
|
+
const icon = s.importance === "required" ? "🔴" : "🟡";
|
|
3409
|
+
response += `- ${icon} ${s.suggestion}\n`;
|
|
3410
|
+
});
|
|
3411
|
+
response += `\n`;
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3414
|
+
if (result.duplicateConfigs.length > 0) {
|
|
3415
|
+
response += `## Duplicate Configs\n\n`;
|
|
3416
|
+
result.duplicateConfigs.forEach(
|
|
3417
|
+
(d) => (response += `- **${d.type}:** ${d.files.join(", ")}\n`),
|
|
3418
|
+
);
|
|
3419
|
+
response += `\n`;
|
|
3420
|
+
}
|
|
3421
|
+
|
|
3422
|
+
response += `\n---\n${result.cleanupPlan}`;
|
|
3423
|
+
|
|
3424
|
+
return { content: [{ type: "text", text: response }] };
|
|
3425
|
+
} catch (error) {
|
|
3426
|
+
return {
|
|
3427
|
+
content: [
|
|
3428
|
+
{
|
|
3429
|
+
type: "text",
|
|
3430
|
+
text: `❌ Root cleanup analysis failed: ${error.message}`,
|
|
3431
|
+
},
|
|
3432
|
+
],
|
|
3433
|
+
isError: true,
|
|
3434
|
+
};
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
|
|
3438
|
+
async repoHygieneDeletionPlan(projectPath, includeReview = false) {
|
|
3439
|
+
try {
|
|
3440
|
+
const result = await hygieneDeletionPlan({ projectPath, includeReview });
|
|
3441
|
+
|
|
3442
|
+
let response = `# 🗑️ Safe Deletion Plan\n\n`;
|
|
3443
|
+
response += `**Safe to Delete:** ${result.summary.safeCount} files\n`;
|
|
3444
|
+
response += `**Needs Review:** ${result.summary.reviewCount} files\n\n`;
|
|
3445
|
+
|
|
3446
|
+
if (result.safeToDelete.length > 0) {
|
|
3447
|
+
response += `## ✅ Safe to Delete Now\n\n`;
|
|
3448
|
+
response += `| File | Reason | Category |\n|------|--------|----------|\n`;
|
|
3449
|
+
result.safeToDelete.slice(0, 30).forEach((f) => {
|
|
3450
|
+
response += `| \`${f.file}\` | ${f.reason} | ${f.category} |\n`;
|
|
3451
|
+
});
|
|
3452
|
+
if (result.safeToDelete.length > 30)
|
|
3453
|
+
response += `| ... | ${result.safeToDelete.length - 30} more | |\n`;
|
|
3454
|
+
response += `\n`;
|
|
3455
|
+
}
|
|
3456
|
+
|
|
3457
|
+
if (includeReview && result.reviewFirst.length > 0) {
|
|
3458
|
+
response += `## 🟡 Review Before Deleting\n\n`;
|
|
3459
|
+
result.reviewFirst
|
|
3460
|
+
.slice(0, 20)
|
|
3461
|
+
.forEach((f) => (response += `- \`${f.file}\` - ${f.reason}\n`));
|
|
3462
|
+
}
|
|
3463
|
+
|
|
3464
|
+
response += `\n⚠️ **Note:** This tool never auto-deletes. Review and delete manually.\n`;
|
|
3465
|
+
|
|
3466
|
+
return { content: [{ type: "text", text: response }] };
|
|
3467
|
+
} catch (error) {
|
|
3468
|
+
return {
|
|
3469
|
+
content: [
|
|
3470
|
+
{ type: "text", text: `❌ Deletion plan failed: ${error.message}` },
|
|
3471
|
+
],
|
|
3472
|
+
isError: true,
|
|
3473
|
+
};
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3476
|
+
|
|
3477
|
+
// 🤖 AI-Enhanced Production Integrity Methods
|
|
3478
|
+
async aiProductionIntegrity(projectPath, enableAI = true) {
|
|
3479
|
+
try {
|
|
3480
|
+
const { auditProductionIntegrity } = require(
|
|
3481
|
+
path.join(__dirname, "..", "scripts", "audit-production-integrity.js"),
|
|
3482
|
+
);
|
|
3483
|
+
|
|
3484
|
+
this.logger.info(
|
|
3485
|
+
`🤖 Running AI-Enhanced Production Integrity on: ${projectPath}`,
|
|
3486
|
+
);
|
|
3487
|
+
|
|
3488
|
+
const { results, integrity } =
|
|
3489
|
+
await auditProductionIntegrity(projectPath);
|
|
3490
|
+
|
|
3491
|
+
// Convert to AI-friendly format
|
|
3492
|
+
const findings = this.convertToAIFindings(results);
|
|
3493
|
+
|
|
3494
|
+
// Generate AI insights if enabled
|
|
3495
|
+
let aiInsights;
|
|
3496
|
+
if (enableAI && process.env.OPENAI_API_KEY) {
|
|
3497
|
+
aiInsights = await this.generateAIInsights(findings);
|
|
3498
|
+
} else {
|
|
3499
|
+
aiInsights = this.generateLocalInsights(findings);
|
|
3500
|
+
}
|
|
3501
|
+
|
|
3502
|
+
const report = this.formatAIProductionReport(
|
|
3503
|
+
findings,
|
|
3504
|
+
integrity,
|
|
3505
|
+
aiInsights,
|
|
3506
|
+
);
|
|
3507
|
+
|
|
3508
|
+
return {
|
|
3509
|
+
content: [
|
|
3510
|
+
{ type: "text", text: report },
|
|
3511
|
+
{
|
|
3512
|
+
type: "text",
|
|
3513
|
+
text: `\n---\n**AI Analysis JSON:**\n\`\`\`json\n${JSON.stringify(
|
|
3514
|
+
{
|
|
3515
|
+
score: integrity.score,
|
|
3516
|
+
grade: integrity.grade,
|
|
3517
|
+
canShip: integrity.canShip,
|
|
3518
|
+
aiInsights,
|
|
3519
|
+
findingsCount: findings.length,
|
|
3520
|
+
},
|
|
3521
|
+
null,
|
|
3522
|
+
2,
|
|
3523
|
+
)}\n\`\`\``,
|
|
3524
|
+
},
|
|
3525
|
+
],
|
|
3526
|
+
};
|
|
3527
|
+
} catch (error) {
|
|
3528
|
+
this.logger.error("AI Production integrity failed", {
|
|
3529
|
+
error: error.message,
|
|
3530
|
+
});
|
|
3531
|
+
return {
|
|
3532
|
+
content: [
|
|
3533
|
+
{
|
|
3534
|
+
type: "text",
|
|
3535
|
+
text: `❌ AI Production integrity failed: ${error.message}`,
|
|
3536
|
+
},
|
|
3537
|
+
],
|
|
3538
|
+
isError: true,
|
|
3539
|
+
};
|
|
3540
|
+
}
|
|
3541
|
+
}
|
|
3542
|
+
|
|
3543
|
+
convertToAIFindings(results) {
|
|
3544
|
+
const findings = [];
|
|
3545
|
+
let id = 1;
|
|
3546
|
+
|
|
3547
|
+
// Auth findings
|
|
3548
|
+
if (results.auth?.analysis) {
|
|
3549
|
+
for (const e of results.auth.analysis.adminExposed || []) {
|
|
3550
|
+
findings.push({
|
|
3551
|
+
id: `auth-${id++}`,
|
|
3552
|
+
category: "auth",
|
|
3553
|
+
severity: "critical",
|
|
3554
|
+
title: "Admin Endpoint Exposed",
|
|
3555
|
+
description: `${e.method || "GET"} ${e.path} lacks authentication`,
|
|
3556
|
+
file: e.file,
|
|
3557
|
+
});
|
|
3558
|
+
}
|
|
3559
|
+
}
|
|
3560
|
+
|
|
3561
|
+
// Secret findings
|
|
3562
|
+
if (results.env?.secrets) {
|
|
3563
|
+
for (const s of results.env.secrets) {
|
|
3564
|
+
findings.push({
|
|
3565
|
+
id: `secret-${id++}`,
|
|
3566
|
+
category: "secrets",
|
|
3567
|
+
severity: s.severity,
|
|
3568
|
+
title: `Hardcoded ${s.type}`,
|
|
3569
|
+
description: `Found hardcoded ${s.type} in code`,
|
|
3570
|
+
file: s.file,
|
|
3571
|
+
line: s.line,
|
|
3572
|
+
});
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
|
|
3576
|
+
// Mock findings
|
|
3577
|
+
if (results.mocks?.issues) {
|
|
3578
|
+
for (const m of results.mocks.issues) {
|
|
3579
|
+
findings.push({
|
|
3580
|
+
id: `mock-${id++}`,
|
|
3581
|
+
category: "mocks",
|
|
3582
|
+
severity: m.severity || "medium",
|
|
3583
|
+
title: `Mock Code: ${m.name}`,
|
|
3584
|
+
description: m.reason || `Found ${m.name} in production code`,
|
|
3585
|
+
file: m.file,
|
|
3586
|
+
});
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
|
|
3590
|
+
return findings;
|
|
3591
|
+
}
|
|
3592
|
+
|
|
3593
|
+
async generateAIInsights(findings) {
|
|
3594
|
+
const apiKey = process.env.OPENAI_API_KEY;
|
|
3595
|
+
if (!apiKey) return this.generateLocalInsights(findings);
|
|
3596
|
+
|
|
3597
|
+
const summary = findings
|
|
3598
|
+
.slice(0, 15)
|
|
3599
|
+
.map((f) => `[${f.severity}] ${f.title}: ${f.description}`)
|
|
3600
|
+
.join("\n");
|
|
3601
|
+
|
|
3602
|
+
const prompt = `Analyze these security findings and provide insights:
|
|
3603
|
+
|
|
3604
|
+
${summary}
|
|
3605
|
+
|
|
3606
|
+
Respond with JSON:
|
|
3607
|
+
{
|
|
3608
|
+
"overallAssessment": "1-2 sentence assessment",
|
|
3609
|
+
"topRisks": ["Top 3 risks"],
|
|
3610
|
+
"quickWins": ["3-5 easy fixes"],
|
|
3611
|
+
"securityPosture": "strong|moderate|weak|critical",
|
|
3612
|
+
"estimatedFixTime": "e.g. '2-4 hours'",
|
|
3613
|
+
"prioritizedActions": [{"priority": 1, "action": "...", "reason": "...", "effort": "low|medium|high", "impact": "low|medium|high"}]
|
|
3614
|
+
}`;
|
|
3615
|
+
|
|
3616
|
+
try {
|
|
3617
|
+
const response = await fetch(
|
|
3618
|
+
"https://api.openai.com/v1/chat/completions",
|
|
3619
|
+
{
|
|
3620
|
+
method: "POST",
|
|
3621
|
+
headers: {
|
|
3622
|
+
"Content-Type": "application/json",
|
|
3623
|
+
Authorization: `Bearer ${apiKey}`,
|
|
3624
|
+
},
|
|
3625
|
+
body: JSON.stringify({
|
|
3626
|
+
model: "gpt-4o-mini",
|
|
3627
|
+
messages: [
|
|
3628
|
+
{
|
|
3629
|
+
role: "system",
|
|
3630
|
+
content:
|
|
3631
|
+
"You are an expert security auditor. Respond only with valid JSON.",
|
|
3632
|
+
},
|
|
3633
|
+
{ role: "user", content: prompt },
|
|
3634
|
+
],
|
|
3635
|
+
temperature: 0.3,
|
|
3636
|
+
max_tokens: 2000,
|
|
3637
|
+
response_format: { type: "json_object" },
|
|
3638
|
+
}),
|
|
3639
|
+
},
|
|
3640
|
+
);
|
|
3641
|
+
|
|
3642
|
+
if (!response.ok) throw new Error("AI request failed");
|
|
3643
|
+
const data = await response.json();
|
|
3644
|
+
return JSON.parse(data.choices?.[0]?.message?.content || "{}");
|
|
3645
|
+
} catch (error) {
|
|
3646
|
+
this.logger.warn("AI insights generation failed, using local", {
|
|
3647
|
+
error: error.message,
|
|
3648
|
+
});
|
|
3649
|
+
return this.generateLocalInsights(findings);
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3652
|
+
|
|
3653
|
+
generateLocalInsights(findings) {
|
|
3654
|
+
const critical = findings.filter((f) => f.severity === "critical");
|
|
3655
|
+
const high = findings.filter((f) => f.severity === "high");
|
|
3656
|
+
|
|
3657
|
+
return {
|
|
3658
|
+
overallAssessment:
|
|
3659
|
+
critical.length > 0
|
|
3660
|
+
? `Found ${critical.length} critical issues requiring immediate attention.`
|
|
3661
|
+
: "No critical issues. Review high-priority items before shipping.",
|
|
3662
|
+
topRisks: [
|
|
3663
|
+
critical.length > 0
|
|
3664
|
+
? "Unauthenticated endpoints may allow unauthorized access"
|
|
3665
|
+
: null,
|
|
3666
|
+
findings.some((f) => f.category === "secrets")
|
|
3667
|
+
? "Hardcoded secrets risk credential exposure"
|
|
3668
|
+
: null,
|
|
3669
|
+
findings.some((f) => f.category === "mocks")
|
|
3670
|
+
? "Mock code may bypass security controls"
|
|
3671
|
+
: null,
|
|
3672
|
+
].filter(Boolean),
|
|
3673
|
+
quickWins: [
|
|
3674
|
+
findings.some((f) => f.title.includes("console"))
|
|
3675
|
+
? "Remove console.log statements"
|
|
3676
|
+
: null,
|
|
3677
|
+
findings.some((f) => f.category === "secrets")
|
|
3678
|
+
? "Move secrets to environment variables"
|
|
3679
|
+
: null,
|
|
3680
|
+
].filter(Boolean),
|
|
3681
|
+
securityPosture:
|
|
3682
|
+
critical.length > 3
|
|
3683
|
+
? "critical"
|
|
3684
|
+
: critical.length > 0
|
|
3685
|
+
? "weak"
|
|
3686
|
+
: high.length > 5
|
|
3687
|
+
? "moderate"
|
|
3688
|
+
: "strong",
|
|
3689
|
+
estimatedFixTime:
|
|
3690
|
+
critical.length > 5
|
|
3691
|
+
? "4-8 hours"
|
|
3692
|
+
: critical.length > 0
|
|
3693
|
+
? "1-2 hours"
|
|
3694
|
+
: "< 1 hour",
|
|
3695
|
+
prioritizedActions: [],
|
|
3696
|
+
};
|
|
3697
|
+
}
|
|
3698
|
+
|
|
3699
|
+
formatAIProductionReport(findings, integrity, aiInsights) {
|
|
3700
|
+
const lines = [];
|
|
3701
|
+
lines.push("# 🤖 AI-Enhanced Production Integrity Report\n");
|
|
3702
|
+
lines.push(`**Score:** ${integrity.score}/100 (${integrity.grade})`);
|
|
3703
|
+
lines.push(
|
|
3704
|
+
`**Ship Decision:** ${integrity.canShip ? "✅ APPROVED" : "🚫 BLOCKED"}\n`,
|
|
3705
|
+
);
|
|
3706
|
+
|
|
3707
|
+
lines.push("## 🧠 AI Assessment\n");
|
|
3708
|
+
lines.push(`${aiInsights.overallAssessment}\n`);
|
|
3709
|
+
lines.push(
|
|
3710
|
+
`**Security Posture:** ${aiInsights.securityPosture?.toUpperCase()}`,
|
|
3711
|
+
);
|
|
3712
|
+
lines.push(`**Estimated Fix Time:** ${aiInsights.estimatedFixTime}\n`);
|
|
3713
|
+
|
|
3714
|
+
if (aiInsights.topRisks?.length > 0) {
|
|
3715
|
+
lines.push("### 🚨 Top Risks\n");
|
|
3716
|
+
for (const risk of aiInsights.topRisks) {
|
|
3717
|
+
lines.push(`- ${risk}`);
|
|
3718
|
+
}
|
|
3719
|
+
lines.push("");
|
|
3720
|
+
}
|
|
3721
|
+
|
|
3722
|
+
if (aiInsights.quickWins?.length > 0) {
|
|
3723
|
+
lines.push("### ⚡ Quick Wins\n");
|
|
3724
|
+
for (const win of aiInsights.quickWins) {
|
|
3725
|
+
lines.push(`- ${win}`);
|
|
3726
|
+
}
|
|
3727
|
+
lines.push("");
|
|
3728
|
+
}
|
|
3729
|
+
|
|
3730
|
+
lines.push("## 📋 Findings Summary\n");
|
|
3731
|
+
lines.push(`| Severity | Count |`);
|
|
3732
|
+
lines.push(`|----------|-------|`);
|
|
3733
|
+
lines.push(
|
|
3734
|
+
`| Critical | ${findings.filter((f) => f.severity === "critical").length} |`,
|
|
3735
|
+
);
|
|
3736
|
+
lines.push(
|
|
3737
|
+
`| High | ${findings.filter((f) => f.severity === "high").length} |`,
|
|
3738
|
+
);
|
|
3739
|
+
lines.push(
|
|
3740
|
+
`| Medium | ${findings.filter((f) => f.severity === "medium").length} |`,
|
|
3741
|
+
);
|
|
3742
|
+
lines.push("");
|
|
3743
|
+
|
|
3744
|
+
lines.push("---\n_Context Enhanced by Guardrail AI_");
|
|
3745
|
+
|
|
3746
|
+
return lines.join("\n");
|
|
3747
|
+
}
|
|
3748
|
+
|
|
3749
|
+
async aiExplainFinding(finding, context) {
|
|
3750
|
+
if (!finding) {
|
|
3751
|
+
return {
|
|
3752
|
+
content: [{ type: "text", text: "❌ No finding provided to explain" }],
|
|
3753
|
+
isError: true,
|
|
3754
|
+
};
|
|
3755
|
+
}
|
|
3756
|
+
|
|
3757
|
+
const apiKey = process.env.OPENAI_API_KEY;
|
|
3758
|
+
|
|
3759
|
+
if (apiKey) {
|
|
3760
|
+
try {
|
|
3761
|
+
const prompt = `Explain this security finding for a developer:
|
|
3762
|
+
|
|
3763
|
+
Finding: ${finding.title}
|
|
3764
|
+
Category: ${finding.category}
|
|
3765
|
+
Severity: ${finding.severity}
|
|
3766
|
+
Description: ${finding.description}
|
|
3767
|
+
${context ? `Context: ${context}` : ""}
|
|
3768
|
+
|
|
3769
|
+
Provide:
|
|
3770
|
+
1. Plain English explanation of why this matters
|
|
3771
|
+
2. A realistic attack scenario
|
|
3772
|
+
3. Which compliance frameworks this affects (GDPR, SOC2, HIPAA, etc.)
|
|
3773
|
+
4. Step-by-step fix instructions
|
|
3774
|
+
5. Code example showing the fix
|
|
3775
|
+
|
|
3776
|
+
Be specific and actionable.`;
|
|
3777
|
+
|
|
3778
|
+
const response = await fetch(
|
|
3779
|
+
"https://api.openai.com/v1/chat/completions",
|
|
3780
|
+
{
|
|
3781
|
+
method: "POST",
|
|
3782
|
+
headers: {
|
|
3783
|
+
"Content-Type": "application/json",
|
|
3784
|
+
Authorization: `Bearer ${apiKey}`,
|
|
3785
|
+
},
|
|
3786
|
+
body: JSON.stringify({
|
|
3787
|
+
model: "gpt-4o-mini",
|
|
3788
|
+
messages: [{ role: "user", content: prompt }],
|
|
3789
|
+
temperature: 0.3,
|
|
3790
|
+
max_tokens: 1500,
|
|
3791
|
+
}),
|
|
3792
|
+
},
|
|
3793
|
+
);
|
|
3794
|
+
|
|
3795
|
+
if (response.ok) {
|
|
3796
|
+
const data = await response.json();
|
|
3797
|
+
const explanation =
|
|
3798
|
+
data.choices?.[0]?.message?.content ||
|
|
3799
|
+
"Unable to generate explanation";
|
|
3800
|
+
|
|
3801
|
+
return {
|
|
3802
|
+
content: [
|
|
3803
|
+
{
|
|
3804
|
+
type: "text",
|
|
3805
|
+
text: `# 🧠 AI Explanation: ${finding.title}\n\n${explanation}\n\n---\n_Context Enhanced by Guardrail AI_`,
|
|
3806
|
+
},
|
|
3807
|
+
],
|
|
3808
|
+
};
|
|
3809
|
+
}
|
|
3810
|
+
} catch (error) {
|
|
3811
|
+
this.logger.warn("AI explanation failed", { error: error.message });
|
|
3812
|
+
}
|
|
3813
|
+
}
|
|
3814
|
+
|
|
3815
|
+
// Local fallback
|
|
3816
|
+
const localExplanation = this.getLocalExplanation(finding);
|
|
3817
|
+
return {
|
|
3818
|
+
content: [
|
|
3819
|
+
{
|
|
3820
|
+
type: "text",
|
|
3821
|
+
text: `# 🧠 Explanation: ${finding.title}\n\n${localExplanation}\n\n_Note: For detailed AI-powered explanations, set OPENAI_API_KEY_`,
|
|
3822
|
+
},
|
|
3823
|
+
],
|
|
3824
|
+
};
|
|
3825
|
+
}
|
|
3826
|
+
|
|
3827
|
+
getLocalExplanation(finding) {
|
|
3828
|
+
const explanations = {
|
|
3829
|
+
auth: `**Why it matters:** This endpoint lacks authentication, allowing anyone to access it.\n\n**Risk:** Unauthorized users could access or modify sensitive data.\n\n**Fix:** Add authentication middleware to verify user identity before processing requests.`,
|
|
3830
|
+
secrets: `**Why it matters:** Hardcoded secrets in code can be exposed if the repository is leaked.\n\n**Risk:** Attackers could use these credentials to access external services.\n\n**Fix:** Move secrets to environment variables and rotate the exposed credential.`,
|
|
3831
|
+
mocks: `**Why it matters:** Test/mock code in production may bypass security controls.\n\n**Risk:** Users might see fake data or bypass authentication.\n\n**Fix:** Remove mock imports and ensure only production code is deployed.`,
|
|
3832
|
+
};
|
|
3833
|
+
return (
|
|
3834
|
+
explanations[finding.category] ||
|
|
3835
|
+
"This finding indicates a potential security or quality issue that should be reviewed."
|
|
3836
|
+
);
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3839
|
+
async aiGenerateFix(finding) {
|
|
3840
|
+
if (!finding) {
|
|
3841
|
+
return {
|
|
3842
|
+
content: [{ type: "text", text: "❌ No finding provided" }],
|
|
3843
|
+
isError: true,
|
|
3844
|
+
};
|
|
3845
|
+
}
|
|
3846
|
+
|
|
3847
|
+
const apiKey = process.env.OPENAI_API_KEY;
|
|
3848
|
+
|
|
3849
|
+
if (apiKey) {
|
|
3850
|
+
try {
|
|
3851
|
+
const prompt = `Generate a specific fix for this security issue:
|
|
3852
|
+
|
|
3853
|
+
Category: ${finding.category}
|
|
3854
|
+
Severity: ${finding.severity}
|
|
3855
|
+
Title: ${finding.title}
|
|
3856
|
+
Description: ${finding.description}
|
|
3857
|
+
File: ${finding.file || "unknown"}
|
|
3858
|
+
Code: ${finding.code || "N/A"}
|
|
3859
|
+
|
|
3860
|
+
Provide:
|
|
3861
|
+
1. Step-by-step instructions to fix this
|
|
3862
|
+
2. A complete code example showing the fix
|
|
3863
|
+
3. How to verify the fix works
|
|
3864
|
+
|
|
3865
|
+
Be specific and provide working code.`;
|
|
3866
|
+
|
|
3867
|
+
const response = await fetch(
|
|
3868
|
+
"https://api.openai.com/v1/chat/completions",
|
|
3869
|
+
{
|
|
3870
|
+
method: "POST",
|
|
3871
|
+
headers: {
|
|
3872
|
+
"Content-Type": "application/json",
|
|
3873
|
+
Authorization: `Bearer ${apiKey}`,
|
|
3874
|
+
},
|
|
3875
|
+
body: JSON.stringify({
|
|
3876
|
+
model: "gpt-4o-mini",
|
|
3877
|
+
messages: [{ role: "user", content: prompt }],
|
|
3878
|
+
temperature: 0.3,
|
|
3879
|
+
max_tokens: 1500,
|
|
3880
|
+
}),
|
|
3881
|
+
},
|
|
3882
|
+
);
|
|
3883
|
+
|
|
3884
|
+
if (response.ok) {
|
|
3885
|
+
const data = await response.json();
|
|
3886
|
+
const fix =
|
|
3887
|
+
data.choices?.[0]?.message?.content || "Unable to generate fix";
|
|
3888
|
+
|
|
3889
|
+
return {
|
|
3890
|
+
content: [
|
|
3891
|
+
{
|
|
3892
|
+
type: "text",
|
|
3893
|
+
text: `# 🔧 AI-Generated Fix: ${finding.title}\n\n${fix}\n\n---\n_Context Enhanced by Guardrail AI_`,
|
|
3894
|
+
},
|
|
3895
|
+
],
|
|
3896
|
+
};
|
|
3897
|
+
}
|
|
3898
|
+
} catch (error) {
|
|
3899
|
+
this.logger.warn("AI fix generation failed", { error: error.message });
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
|
|
3903
|
+
// Local fallback
|
|
3904
|
+
const localFix = this.getLocalFix(finding);
|
|
3905
|
+
return {
|
|
3906
|
+
content: [
|
|
3907
|
+
{
|
|
3908
|
+
type: "text",
|
|
3909
|
+
text: `# 🔧 Fix: ${finding.title}\n\n${localFix.fix}\n\n**Code Example:**\n\`\`\`javascript\n${localFix.code || "// No code example available"}\n\`\`\`\n\n_Note: For detailed AI-powered fixes, set OPENAI_API_KEY_`,
|
|
3910
|
+
},
|
|
3911
|
+
],
|
|
3912
|
+
};
|
|
3913
|
+
}
|
|
3914
|
+
|
|
3915
|
+
getLocalFix(finding) {
|
|
3916
|
+
const fixes = {
|
|
3917
|
+
auth: {
|
|
3918
|
+
fix: "1. Add authentication middleware to the route\n2. Verify user session/token\n3. Check required permissions\n4. Return 401/403 for unauthorized access",
|
|
3919
|
+
code: `// Add auth middleware
|
|
3920
|
+
router.use(requireAuth);
|
|
3921
|
+
|
|
3922
|
+
router.get('/admin/users', async (req, res) => {
|
|
3923
|
+
if (!req.user?.isAdmin) {
|
|
3924
|
+
return res.status(403).json({ error: 'Forbidden' });
|
|
3925
|
+
}
|
|
3926
|
+
// ... handler
|
|
3927
|
+
});`,
|
|
3928
|
+
},
|
|
3929
|
+
secrets: {
|
|
3930
|
+
fix: "1. Remove the hardcoded secret\n2. Add to .env file\n3. Use process.env.VAR\n4. Rotate the exposed credential",
|
|
3931
|
+
code: `// Before (bad)
|
|
3932
|
+
const apiKey = 'sk-abc123...';
|
|
3933
|
+
|
|
3934
|
+
// After (good)
|
|
3935
|
+
const apiKey = process.env.API_KEY;
|
|
3936
|
+
if (!apiKey) {
|
|
3937
|
+
throw new Error('API_KEY required');
|
|
3938
|
+
}`,
|
|
3939
|
+
},
|
|
3940
|
+
mocks: {
|
|
3941
|
+
fix: "1. Remove mock/test imports\n2. Move test code to test directories\n3. Remove console.log statements",
|
|
3942
|
+
code: `// Remove these from production
|
|
3943
|
+
// import { mockData } from './test-utils';
|
|
3944
|
+
// console.log('debug:', data);`,
|
|
3945
|
+
},
|
|
3946
|
+
};
|
|
3947
|
+
return (
|
|
3948
|
+
fixes[finding.category] || {
|
|
3949
|
+
fix: "Review and apply appropriate fix.",
|
|
3950
|
+
code: null,
|
|
3951
|
+
}
|
|
3952
|
+
);
|
|
3953
|
+
}
|
|
3954
|
+
|
|
3955
|
+
async aiSecurityAssessment(
|
|
3956
|
+
projectPath,
|
|
3957
|
+
complianceFrameworks = ["SOC2", "GDPR"],
|
|
3958
|
+
) {
|
|
3959
|
+
try {
|
|
3960
|
+
const { auditProductionIntegrity } = require(
|
|
3961
|
+
path.join(__dirname, "..", "scripts", "audit-production-integrity.js"),
|
|
3962
|
+
);
|
|
3963
|
+
|
|
3964
|
+
const { results, integrity } =
|
|
3965
|
+
await auditProductionIntegrity(projectPath);
|
|
3966
|
+
const findings = this.convertToAIFindings(results);
|
|
3967
|
+
|
|
3968
|
+
const apiKey = process.env.OPENAI_API_KEY;
|
|
3969
|
+
|
|
3970
|
+
let assessment;
|
|
3971
|
+
if (apiKey) {
|
|
3972
|
+
const prompt = `You are a security consultant. Assess this codebase for ${complianceFrameworks.join(", ")} compliance.
|
|
3973
|
+
|
|
3974
|
+
Findings Summary:
|
|
3975
|
+
- Critical: ${findings.filter((f) => f.severity === "critical").length}
|
|
3976
|
+
- High: ${findings.filter((f) => f.severity === "high").length}
|
|
3977
|
+
- Categories: auth (${findings.filter((f) => f.category === "auth").length}), secrets (${findings.filter((f) => f.category === "secrets").length}), mocks (${findings.filter((f) => f.category === "mocks").length})
|
|
3978
|
+
|
|
3979
|
+
Top Issues:
|
|
3980
|
+
${findings
|
|
3981
|
+
.slice(0, 10)
|
|
3982
|
+
.map((f) => `- [${f.severity}] ${f.title}`)
|
|
3983
|
+
.join("\n")}
|
|
3984
|
+
|
|
3985
|
+
Provide a comprehensive security assessment including:
|
|
3986
|
+
1. Overall security posture rating
|
|
3987
|
+
2. Compliance gaps for each framework
|
|
3988
|
+
3. Risk matrix (likelihood vs impact)
|
|
3989
|
+
4. Remediation roadmap with timeline
|
|
3990
|
+
5. Quick wins vs long-term improvements`;
|
|
3991
|
+
|
|
3992
|
+
try {
|
|
3993
|
+
const response = await fetch(
|
|
3994
|
+
"https://api.openai.com/v1/chat/completions",
|
|
3995
|
+
{
|
|
3996
|
+
method: "POST",
|
|
3997
|
+
headers: {
|
|
3998
|
+
"Content-Type": "application/json",
|
|
3999
|
+
Authorization: `Bearer ${apiKey}`,
|
|
4000
|
+
},
|
|
4001
|
+
body: JSON.stringify({
|
|
4002
|
+
model: "gpt-4o-mini",
|
|
4003
|
+
messages: [{ role: "user", content: prompt }],
|
|
4004
|
+
temperature: 0.3,
|
|
4005
|
+
max_tokens: 2500,
|
|
4006
|
+
}),
|
|
4007
|
+
},
|
|
4008
|
+
);
|
|
4009
|
+
|
|
4010
|
+
if (response.ok) {
|
|
4011
|
+
const data = await response.json();
|
|
4012
|
+
assessment = data.choices?.[0]?.message?.content;
|
|
4013
|
+
}
|
|
4014
|
+
} catch (error) {
|
|
4015
|
+
this.logger.warn("AI assessment failed", { error: error.message });
|
|
4016
|
+
}
|
|
4017
|
+
}
|
|
4018
|
+
|
|
4019
|
+
if (!assessment) {
|
|
4020
|
+
assessment = this.generateLocalAssessment(
|
|
4021
|
+
findings,
|
|
4022
|
+
integrity,
|
|
4023
|
+
complianceFrameworks,
|
|
4024
|
+
);
|
|
4025
|
+
}
|
|
4026
|
+
|
|
4027
|
+
return {
|
|
4028
|
+
content: [
|
|
4029
|
+
{
|
|
4030
|
+
type: "text",
|
|
4031
|
+
text: `# 🛡️ AI Security Assessment\n\n**Frameworks:** ${complianceFrameworks.join(", ")}\n**Score:** ${integrity.score}/100\n\n${assessment}\n\n---\n_Context Enhanced by Guardrail AI_`,
|
|
4032
|
+
},
|
|
4033
|
+
],
|
|
4034
|
+
};
|
|
4035
|
+
} catch (error) {
|
|
4036
|
+
return {
|
|
4037
|
+
content: [
|
|
4038
|
+
{
|
|
4039
|
+
type: "text",
|
|
4040
|
+
text: `❌ Security assessment failed: ${error.message}`,
|
|
4041
|
+
},
|
|
4042
|
+
],
|
|
4043
|
+
isError: true,
|
|
4044
|
+
};
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
4047
|
+
|
|
4048
|
+
generateLocalAssessment(findings, integrity, frameworks) {
|
|
4049
|
+
const critical = findings.filter((f) => f.severity === "critical").length;
|
|
4050
|
+
const high = findings.filter((f) => f.severity === "high").length;
|
|
4051
|
+
|
|
4052
|
+
let posture = "Moderate";
|
|
4053
|
+
if (critical > 3) posture = "Critical";
|
|
4054
|
+
else if (critical > 0) posture = "Weak";
|
|
4055
|
+
else if (high === 0) posture = "Strong";
|
|
4056
|
+
|
|
4057
|
+
return `## Security Posture: ${posture}
|
|
4058
|
+
|
|
4059
|
+
### Findings Summary
|
|
4060
|
+
- Critical Issues: ${critical}
|
|
4061
|
+
- High Priority: ${high}
|
|
4062
|
+
- Total Findings: ${findings.length}
|
|
4063
|
+
|
|
4064
|
+
### Compliance Gaps
|
|
4065
|
+
${frameworks.map((f) => `- **${f}**: ${critical > 0 ? "Gaps detected - review required" : "No critical gaps"}`).join("\n")}
|
|
4066
|
+
|
|
4067
|
+
### Recommendations
|
|
4068
|
+
1. ${critical > 0 ? "Address critical authentication issues immediately" : "Continue monitoring security posture"}
|
|
4069
|
+
2. ${findings.some((f) => f.category === "secrets") ? "Rotate exposed credentials and move to environment variables" : "Maintain secret management practices"}
|
|
4070
|
+
3. Regular security audits recommended
|
|
4071
|
+
|
|
4072
|
+
_For detailed AI-powered assessment, set OPENAI_API_KEY_`;
|
|
4073
|
+
}
|
|
4074
|
+
|
|
4075
|
+
setupErrorHandling() {
|
|
4076
|
+
// Enhanced error handling with detailed logging
|
|
4077
|
+
this.server.onerror = (error) => {
|
|
4078
|
+
this.logger.error("MCP Server Error", {
|
|
4079
|
+
message: error.message,
|
|
4080
|
+
stack: error.stack,
|
|
4081
|
+
timestamp: new Date().toISOString(),
|
|
4082
|
+
});
|
|
4083
|
+
};
|
|
4084
|
+
|
|
4085
|
+
// Graceful shutdown
|
|
4086
|
+
process.on("SIGINT", async () => {
|
|
4087
|
+
this.logger.info("Received SIGINT, shutting down gracefully...");
|
|
4088
|
+
try {
|
|
4089
|
+
await this.server.close();
|
|
4090
|
+
this.logger.info("Server closed successfully");
|
|
4091
|
+
process.exit(0);
|
|
4092
|
+
} catch (error) {
|
|
4093
|
+
this.logger.error("Error during shutdown", error);
|
|
4094
|
+
process.exit(1);
|
|
4095
|
+
}
|
|
4096
|
+
});
|
|
4097
|
+
|
|
4098
|
+
process.on("SIGTERM", async () => {
|
|
4099
|
+
this.logger.info("Received SIGTERM, shutting down gracefully...");
|
|
4100
|
+
try {
|
|
4101
|
+
await this.server.close();
|
|
4102
|
+
this.logger.info("Server closed successfully");
|
|
4103
|
+
process.exit(0);
|
|
4104
|
+
} catch (error) {
|
|
4105
|
+
this.logger.error("Error during shutdown", error);
|
|
4106
|
+
process.exit(1);
|
|
4107
|
+
}
|
|
4108
|
+
});
|
|
4109
|
+
|
|
4110
|
+
// Unhandled promise rejections
|
|
4111
|
+
process.on("unhandledRejection", (reason, promise) => {
|
|
4112
|
+
this.logger.error("Unhandled Promise Rejection", {
|
|
4113
|
+
reason: reason.toString(),
|
|
4114
|
+
promise: promise.toString(),
|
|
4115
|
+
});
|
|
4116
|
+
});
|
|
4117
|
+
|
|
4118
|
+
// Uncaught exceptions
|
|
4119
|
+
process.on("uncaughtException", (error) => {
|
|
4120
|
+
this.logger.error("Uncaught Exception", {
|
|
4121
|
+
message: error.message,
|
|
4122
|
+
stack: error.stack,
|
|
4123
|
+
});
|
|
4124
|
+
process.exit(1);
|
|
4125
|
+
});
|
|
4126
|
+
}
|
|
4127
|
+
|
|
4128
|
+
async run() {
|
|
4129
|
+
const transport = new StdioServerTransport();
|
|
4130
|
+
await this.server.connect(transport);
|
|
4131
|
+
this.logger.info("GUARDRAIL AI MCP Server started successfully");
|
|
4132
|
+
console.error("GUARDRAIL AI MCP Server running on stdio");
|
|
4133
|
+
}
|
|
4134
|
+
}
|
|
4135
|
+
|
|
4136
|
+
const server = new GuardrailsMCPServer();
|
|
4137
|
+
server.run().catch(console.error);
|