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.
@@ -0,0 +1,838 @@
1
+ /**
2
+ * Codebase-Aware Architect MCP Tools
3
+ *
4
+ * These tools give AI agents access to deep codebase knowledge:
5
+ * 1. guardrail_architect_context - Get full codebase context
6
+ * 2. guardrail_architect_guide - Get guidance for creating/modifying code
7
+ * 3. guardrail_architect_validate - Validate code against codebase patterns
8
+ * 4. guardrail_architect_patterns - Get specific patterns from codebase
9
+ * 5. guardrail_architect_dependencies - Understand file relationships
10
+ */
11
+
12
+ import fs from "fs";
13
+ import path from "path";
14
+
15
+ // Cache for loaded context
16
+ let contextCache = null;
17
+ let contextLoadedAt = null;
18
+ const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
19
+
20
+ /**
21
+ * Load context from .guardrail/ directory
22
+ */
23
+ async function loadCodebaseContext(projectPath) {
24
+ const guardrailDir = path.join(projectPath, ".guardrail");
25
+
26
+ // Check cache
27
+ if (
28
+ contextCache &&
29
+ contextLoadedAt &&
30
+ Date.now() - contextLoadedAt < CACHE_TTL_MS
31
+ ) {
32
+ return contextCache;
33
+ }
34
+
35
+ const context = {
36
+ projectSummary: await loadJsonFile(guardrailDir, "project-summary.json"),
37
+ dependencyGraph: await loadJsonFile(guardrailDir, "dependency-graph.json"),
38
+ apiContracts: await loadJsonFile(guardrailDir, "api-contracts.json"),
39
+ teamConventions: await loadJsonFile(guardrailDir, "team-conventions.json"),
40
+ gitContext: await loadJsonFile(guardrailDir, "git-context.json"),
41
+ patterns: await loadJsonFile(guardrailDir, "patterns.json"),
42
+ // Also load generated rules files
43
+ cursorRules: await loadTextFile(projectPath, ".cursorrules"),
44
+ windsurfRules: await loadTextFile(
45
+ path.join(projectPath, ".windsurf", "rules"),
46
+ "rules.md",
47
+ ),
48
+ };
49
+
50
+ // If no context files exist, analyze the codebase
51
+ if (!context.projectSummary) {
52
+ context.projectSummary = await analyzeProject(projectPath);
53
+ context.teamConventions = await analyzeConventions(projectPath);
54
+ context.patterns = await analyzePatterns(projectPath);
55
+ }
56
+
57
+ contextCache = context;
58
+ contextLoadedAt = Date.now();
59
+
60
+ return context;
61
+ }
62
+
63
+ /**
64
+ * Load a JSON file safely
65
+ */
66
+ async function loadJsonFile(dir, filename) {
67
+ const filePath = path.join(dir, filename);
68
+ try {
69
+ if (fs.existsSync(filePath)) {
70
+ return JSON.parse(await fs.promises.readFile(filePath, "utf-8"));
71
+ }
72
+ } catch (e) {
73
+ console.warn(`Could not load ${filename}:`, e.message);
74
+ }
75
+ return null;
76
+ }
77
+
78
+ /**
79
+ * Load a text file safely
80
+ */
81
+ async function loadTextFile(dir, filename) {
82
+ const filePath = path.join(dir, filename);
83
+ try {
84
+ if (fs.existsSync(filePath)) {
85
+ return await fs.promises.readFile(filePath, "utf-8");
86
+ }
87
+ } catch (e) {
88
+ // Ignore errors
89
+ }
90
+ return null;
91
+ }
92
+
93
+ /**
94
+ * Analyze project basics
95
+ */
96
+ async function analyzeProject(projectPath) {
97
+ const pkgPath = path.join(projectPath, "package.json");
98
+ let pkg = {};
99
+
100
+ try {
101
+ if (fs.existsSync(pkgPath)) {
102
+ pkg = JSON.parse(await fs.promises.readFile(pkgPath, "utf-8"));
103
+ }
104
+ } catch {}
105
+
106
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
107
+
108
+ // Detect tech stack
109
+ const techStack = {
110
+ languages: ["javascript"],
111
+ frameworks: [],
112
+ databases: [],
113
+ tools: [],
114
+ };
115
+
116
+ if (deps.typescript) techStack.languages.push("typescript");
117
+ if (deps.react) techStack.frameworks.push("react");
118
+ if (deps.next) techStack.frameworks.push("next.js");
119
+ if (deps.vue) techStack.frameworks.push("vue");
120
+ if (deps.express) techStack.frameworks.push("express");
121
+ if (deps.fastify) techStack.frameworks.push("fastify");
122
+ if (deps.prisma) techStack.databases.push("prisma");
123
+ if (deps.jest) techStack.tools.push("jest");
124
+ if (deps.vitest) techStack.tools.push("vitest");
125
+
126
+ // Detect project type
127
+ let type = "unknown";
128
+ if (fs.existsSync(path.join(projectPath, "pnpm-workspace.yaml")))
129
+ type = "monorepo";
130
+ else if (pkg.bin) type = "cli";
131
+ else if (techStack.frameworks.includes("next.js")) type = "next-app";
132
+ else if (techStack.frameworks.includes("react")) type = "react-app";
133
+
134
+ return {
135
+ name: pkg.name || path.basename(projectPath),
136
+ description: pkg.description || "",
137
+ type,
138
+ techStack,
139
+ structure: {
140
+ srcDir: fs.existsSync(path.join(projectPath, "src")) ? "src" : ".",
141
+ hasTests:
142
+ fs.existsSync(path.join(projectPath, "tests")) ||
143
+ fs.existsSync(path.join(projectPath, "__tests__")),
144
+ },
145
+ };
146
+ }
147
+
148
+ /**
149
+ * Analyze coding conventions
150
+ */
151
+ async function analyzeConventions(projectPath) {
152
+ // Check prettier config
153
+ let codeStyle = {
154
+ quotes: "single",
155
+ semicolons: true,
156
+ indentSize: 2,
157
+ };
158
+
159
+ const prettierRc = path.join(projectPath, ".prettierrc");
160
+ if (fs.existsSync(prettierRc)) {
161
+ try {
162
+ const config = JSON.parse(
163
+ await fs.promises.readFile(prettierRc, "utf-8"),
164
+ );
165
+ if (config.singleQuote === false) codeStyle.quotes = "double";
166
+ if (config.semi === false) codeStyle.semicolons = false;
167
+ if (config.tabWidth) codeStyle.indentSize = config.tabWidth;
168
+ } catch {}
169
+ }
170
+
171
+ // Detect file naming convention
172
+ const files = await findSourceFiles(projectPath, 30);
173
+ let kebab = 0,
174
+ camel = 0,
175
+ pascal = 0;
176
+
177
+ for (const file of files) {
178
+ const name = path.basename(file, path.extname(file));
179
+ if (name.includes("-")) kebab++;
180
+ else if (name[0] === name[0]?.toUpperCase()) pascal++;
181
+ else camel++;
182
+ }
183
+
184
+ const fileNaming =
185
+ kebab > camel && kebab > pascal
186
+ ? "kebab-case"
187
+ : pascal > camel
188
+ ? "PascalCase"
189
+ : "camelCase";
190
+
191
+ return {
192
+ namingConventions: {
193
+ files: fileNaming,
194
+ components: "PascalCase",
195
+ functions: "camelCase",
196
+ types: "PascalCase",
197
+ },
198
+ codeStyle,
199
+ };
200
+ }
201
+
202
+ /**
203
+ * Analyze code patterns
204
+ */
205
+ async function analyzePatterns(projectPath) {
206
+ const files = await findSourceFiles(projectPath, 100);
207
+ const patterns = {
208
+ components: [],
209
+ hooks: [],
210
+ services: [],
211
+ api: [],
212
+ utilities: [],
213
+ };
214
+
215
+ // Find component patterns
216
+ const componentFiles = files.filter(
217
+ (f) =>
218
+ (f.includes("components") || f.includes("ui")) &&
219
+ (f.endsWith(".tsx") || f.endsWith(".jsx")),
220
+ );
221
+
222
+ if (componentFiles.length > 0) {
223
+ let template = "";
224
+ try {
225
+ const content = await fs.promises.readFile(componentFiles[0], "utf-8");
226
+ const match = content.match(/(export\s+)?(function|const)\s+\w+[^{]+\{/);
227
+ if (match) template = match[0] + "/* ... */ }";
228
+ } catch {}
229
+
230
+ patterns.components.push({
231
+ name: "React Component",
232
+ template:
233
+ template ||
234
+ "export function Component(props: Props) { return <div />; }",
235
+ examples: componentFiles
236
+ .slice(0, 3)
237
+ .map((f) => path.relative(projectPath, f)),
238
+ count: componentFiles.length,
239
+ });
240
+ }
241
+
242
+ // Find hook patterns
243
+ const hookFiles = files.filter((f) => f.includes("use") && f.endsWith(".ts"));
244
+ if (hookFiles.length > 0) {
245
+ patterns.hooks.push({
246
+ name: "Custom Hook",
247
+ template:
248
+ "export function useHook() { const [state, setState] = useState(); return { state }; }",
249
+ examples: hookFiles.slice(0, 3).map((f) => path.relative(projectPath, f)),
250
+ count: hookFiles.length,
251
+ });
252
+ }
253
+
254
+ // Find API patterns
255
+ const apiFiles = files.filter(
256
+ (f) => f.includes("api") || f.includes("route"),
257
+ );
258
+ if (apiFiles.length > 0) {
259
+ patterns.api.push({
260
+ name: "API Route",
261
+ template:
262
+ "export async function handler(req, res) { try { /* logic */ } catch (e) { res.status(500).json({ error }); } }",
263
+ examples: apiFiles.slice(0, 3).map((f) => path.relative(projectPath, f)),
264
+ count: apiFiles.length,
265
+ });
266
+ }
267
+
268
+ return patterns;
269
+ }
270
+
271
+ /**
272
+ * Find source files
273
+ */
274
+ async function findSourceFiles(projectPath, limit = 50) {
275
+ const files = [];
276
+ const extensions = [".ts", ".tsx", ".js", ".jsx"];
277
+ const ignoreDirs = [
278
+ "node_modules",
279
+ ".git",
280
+ "dist",
281
+ "build",
282
+ ".next",
283
+ "coverage",
284
+ ];
285
+
286
+ async function walk(dir, depth = 0) {
287
+ if (depth > 5 || files.length >= limit) return;
288
+
289
+ try {
290
+ const entries = await fs.promises.readdir(dir, { withFileTypes: true });
291
+ for (const entry of entries) {
292
+ if (files.length >= limit) break;
293
+
294
+ const fullPath = path.join(dir, entry.name);
295
+ if (entry.isDirectory()) {
296
+ if (!ignoreDirs.includes(entry.name)) {
297
+ await walk(fullPath, depth + 1);
298
+ }
299
+ } else if (extensions.some((ext) => entry.name.endsWith(ext))) {
300
+ files.push(fullPath);
301
+ }
302
+ }
303
+ } catch {}
304
+ }
305
+
306
+ await walk(projectPath);
307
+ return files;
308
+ }
309
+
310
+ /**
311
+ * Get guidance for creating new code
312
+ */
313
+ function getGuidance(context, intent, targetPath) {
314
+ const fileType = detectFileType(targetPath, intent);
315
+ const patterns = context.patterns?.[fileType] || [];
316
+ const conventions = context.teamConventions || {};
317
+
318
+ let guidance = `\n🏛️ ARCHITECT GUIDANCE\n`;
319
+ guidance += `${"═".repeat(50)}\n`;
320
+ guidance += `Intent: "${intent}"\n`;
321
+ guidance += `Target: ${targetPath}\n`;
322
+ guidance += `File Type: ${fileType}\n\n`;
323
+
324
+ // Project context
325
+ if (context.projectSummary) {
326
+ guidance += `📋 PROJECT CONTEXT\n`;
327
+ guidance += `Name: ${context.projectSummary.name}\n`;
328
+ guidance += `Type: ${context.projectSummary.type}\n`;
329
+ if (context.projectSummary.techStack?.frameworks?.length > 0) {
330
+ guidance += `Frameworks: ${context.projectSummary.techStack.frameworks.join(", ")}\n`;
331
+ }
332
+ guidance += `\n`;
333
+ }
334
+
335
+ // Conventions
336
+ if (conventions.namingConventions) {
337
+ guidance += `📝 CONVENTIONS TO FOLLOW\n`;
338
+ guidance += `- File naming: ${conventions.namingConventions.files}\n`;
339
+ guidance += `- Components: ${conventions.namingConventions.components}\n`;
340
+ guidance += `- Functions: ${conventions.namingConventions.functions}\n`;
341
+ if (conventions.codeStyle) {
342
+ guidance += `- Quotes: ${conventions.codeStyle.quotes}\n`;
343
+ guidance += `- Semicolons: ${conventions.codeStyle.semicolons ? "yes" : "no"}\n`;
344
+ }
345
+ guidance += `\n`;
346
+ }
347
+
348
+ // Patterns
349
+ if (patterns.length > 0) {
350
+ guidance += `📐 PATTERN TO FOLLOW\n`;
351
+ for (const pattern of patterns.slice(0, 1)) {
352
+ guidance += `${pattern.name}:\n`;
353
+ guidance += `\`\`\`typescript\n${pattern.template}\n\`\`\`\n`;
354
+ if (pattern.examples?.length > 0) {
355
+ guidance += `\nReference files:\n`;
356
+ for (const ex of pattern.examples.slice(0, 3)) {
357
+ guidance += ` - ${ex}\n`;
358
+ }
359
+ }
360
+ }
361
+ guidance += `\n`;
362
+ }
363
+
364
+ // Cursor/Windsurf rules
365
+ if (context.cursorRules) {
366
+ guidance += `📜 PROJECT RULES (from .cursorrules)\n`;
367
+ const rules = context.cursorRules.split("\n").slice(0, 10).join("\n");
368
+ guidance += `${rules}\n...\n\n`;
369
+ }
370
+
371
+ guidance += `${"═".repeat(50)}\n`;
372
+
373
+ return guidance;
374
+ }
375
+
376
+ /**
377
+ * Detect file type from path and intent
378
+ */
379
+ function detectFileType(filePath, intent) {
380
+ const lower = (filePath + " " + intent).toLowerCase();
381
+
382
+ if (lower.includes("component") || lower.includes("ui")) return "components";
383
+ if (lower.includes("hook") || lower.includes("use")) return "hooks";
384
+ if (lower.includes("service")) return "services";
385
+ if (lower.includes("api") || lower.includes("route")) return "api";
386
+ return "utilities";
387
+ }
388
+
389
+ /**
390
+ * Validate code against patterns
391
+ */
392
+ function validateCode(context, filePath, content) {
393
+ const issues = [];
394
+ const suggestions = [];
395
+ const conventions = context.teamConventions || {};
396
+
397
+ // Check naming convention
398
+ const basename = path.basename(filePath, path.extname(filePath));
399
+ if (conventions.namingConventions?.files === "kebab-case") {
400
+ if (
401
+ !basename.includes("-") &&
402
+ basename.length > 10 &&
403
+ /[A-Z]/.test(basename)
404
+ ) {
405
+ issues.push({
406
+ rule: "naming-convention",
407
+ message: `File should use kebab-case (e.g., ${toKebabCase(basename)})`,
408
+ severity: "warning",
409
+ });
410
+ }
411
+ }
412
+
413
+ // Check for console.log
414
+ if (content.includes("console.log") && !filePath.includes("test")) {
415
+ issues.push({
416
+ rule: "no-console",
417
+ message: "Remove console.log from production code",
418
+ severity: "warning",
419
+ });
420
+ }
421
+
422
+ // Check for any type
423
+ if (content.includes(": any") && !content.includes("@ts-")) {
424
+ issues.push({
425
+ rule: "no-any",
426
+ message: 'Avoid using "any" type',
427
+ severity: "warning",
428
+ });
429
+ }
430
+
431
+ // Check for TODO/FIXME
432
+ if (content.match(/\/\/\s*(TODO|FIXME)/i)) {
433
+ issues.push({
434
+ rule: "no-todo",
435
+ message: "Complete TODO/FIXME before committing",
436
+ severity: "warning",
437
+ });
438
+ }
439
+
440
+ // Check imports
441
+ const imports = content.match(/import .+ from ['"]([^'"]+)['"]/g) || [];
442
+ if (imports.some((i) => i.includes("../../../"))) {
443
+ suggestions.push("Consider using path aliases (@/) for deep imports");
444
+ }
445
+
446
+ return {
447
+ valid: issues.filter((i) => i.severity === "error").length === 0,
448
+ score: Math.max(0, 100 - issues.length * 10),
449
+ issues,
450
+ suggestions,
451
+ };
452
+ }
453
+
454
+ function toKebabCase(str) {
455
+ return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
456
+ }
457
+
458
+ // ============================================================================
459
+ // MCP TOOL DEFINITIONS
460
+ // ============================================================================
461
+
462
+ const CODEBASE_ARCHITECT_TOOLS = [
463
+ {
464
+ name: "guardrail_architect_context",
465
+ description: `🧠 GET CODEBASE CONTEXT - Load deep knowledge about this project.
466
+
467
+ Returns:
468
+ - Project summary (name, type, tech stack)
469
+ - Team coding conventions
470
+ - Code patterns and templates
471
+ - Dependency relationships
472
+ - Git context and recent changes
473
+
474
+ Call this FIRST before writing any code to understand the codebase.`,
475
+ inputSchema: {
476
+ type: "object",
477
+ properties: {
478
+ project_path: {
479
+ type: "string",
480
+ description: "Path to the project root (default: current directory)",
481
+ },
482
+ },
483
+ },
484
+ },
485
+
486
+ {
487
+ name: "guardrail_architect_guide",
488
+ description: `🏛️ GET ARCHITECT GUIDANCE - Get specific guidance for creating or modifying code.
489
+
490
+ Before creating a new file, call this to get:
491
+ - The pattern to follow
492
+ - Naming conventions
493
+ - Reference files to look at
494
+ - Project rules to follow
495
+
496
+ This ensures your code fits the existing codebase style.`,
497
+ inputSchema: {
498
+ type: "object",
499
+ properties: {
500
+ intent: {
501
+ type: "string",
502
+ description:
503
+ 'What you want to create (e.g., "user profile component", "auth hook", "API endpoint")',
504
+ },
505
+ target_path: {
506
+ type: "string",
507
+ description: "Path where the file will be created",
508
+ },
509
+ project_path: {
510
+ type: "string",
511
+ description: "Path to the project root (default: current directory)",
512
+ },
513
+ },
514
+ required: ["intent", "target_path"],
515
+ },
516
+ },
517
+
518
+ {
519
+ name: "guardrail_architect_validate",
520
+ description: `✅ VALIDATE CODE - Check if code follows codebase patterns and conventions.
521
+
522
+ Returns:
523
+ - Validation score (0-100)
524
+ - Issues found (naming, console.log, any types, etc.)
525
+ - Suggestions for improvement
526
+
527
+ Call this AFTER writing code to ensure it fits the codebase.`,
528
+ inputSchema: {
529
+ type: "object",
530
+ properties: {
531
+ file_path: {
532
+ type: "string",
533
+ description: "Path to the file being validated",
534
+ },
535
+ content: {
536
+ type: "string",
537
+ description: "The code content to validate",
538
+ },
539
+ project_path: {
540
+ type: "string",
541
+ description: "Path to the project root (default: current directory)",
542
+ },
543
+ },
544
+ required: ["file_path", "content"],
545
+ },
546
+ },
547
+
548
+ {
549
+ name: "guardrail_architect_patterns",
550
+ description: `📐 GET PATTERNS - Get specific code patterns from the codebase.
551
+
552
+ Returns templates and examples for:
553
+ - components
554
+ - hooks
555
+ - services
556
+ - api routes
557
+ - utilities
558
+
559
+ Use this to see exactly how similar code is structured in this project.`,
560
+ inputSchema: {
561
+ type: "object",
562
+ properties: {
563
+ pattern_type: {
564
+ type: "string",
565
+ enum: ["components", "hooks", "services", "api", "utilities", "all"],
566
+ description: "Type of pattern to retrieve",
567
+ },
568
+ project_path: {
569
+ type: "string",
570
+ description: "Path to the project root (default: current directory)",
571
+ },
572
+ },
573
+ required: ["pattern_type"],
574
+ },
575
+ },
576
+
577
+ {
578
+ name: "guardrail_architect_dependencies",
579
+ description: `🔗 GET DEPENDENCIES - Understand file relationships and impact.
580
+
581
+ Returns:
582
+ - Files that import this file
583
+ - Files this file imports
584
+ - Potential impact of changes
585
+ - Circular dependency warnings
586
+
587
+ Call this before modifying existing code to understand the ripple effects.`,
588
+ inputSchema: {
589
+ type: "object",
590
+ properties: {
591
+ file_path: {
592
+ type: "string",
593
+ description: "Path to the file to analyze",
594
+ },
595
+ project_path: {
596
+ type: "string",
597
+ description: "Path to the project root (default: current directory)",
598
+ },
599
+ },
600
+ required: ["file_path"],
601
+ },
602
+ },
603
+ ];
604
+
605
+ /**
606
+ * Handle MCP tool calls
607
+ */
608
+ async function handleCodebaseArchitectTool(name, args) {
609
+ const projectPath = args.project_path || process.cwd();
610
+
611
+ switch (name) {
612
+ case "guardrail_architect_context": {
613
+ const context = await loadCodebaseContext(projectPath);
614
+
615
+ let output = "\n🧠 CODEBASE CONTEXT\n";
616
+ output += "═".repeat(50) + "\n\n";
617
+
618
+ if (context.projectSummary) {
619
+ output += "📋 PROJECT SUMMARY\n";
620
+ output += `Name: ${context.projectSummary.name}\n`;
621
+ output += `Type: ${context.projectSummary.type}\n`;
622
+ output += `Description: ${context.projectSummary.description || "N/A"}\n`;
623
+ if (context.projectSummary.techStack) {
624
+ const ts = context.projectSummary.techStack;
625
+ output += `Languages: ${ts.languages?.join(", ") || "N/A"}\n`;
626
+ output += `Frameworks: ${ts.frameworks?.join(", ") || "N/A"}\n`;
627
+ output += `Databases: ${ts.databases?.join(", ") || "N/A"}\n`;
628
+ output += `Tools: ${ts.tools?.join(", ") || "N/A"}\n`;
629
+ }
630
+ output += "\n";
631
+ }
632
+
633
+ if (context.teamConventions) {
634
+ output += "📝 TEAM CONVENTIONS\n";
635
+ const tc = context.teamConventions;
636
+ if (tc.namingConventions) {
637
+ output += `File naming: ${tc.namingConventions.files}\n`;
638
+ output += `Components: ${tc.namingConventions.components}\n`;
639
+ output += `Functions: ${tc.namingConventions.functions}\n`;
640
+ }
641
+ if (tc.codeStyle) {
642
+ output += `Quotes: ${tc.codeStyle.quotes}\n`;
643
+ output += `Semicolons: ${tc.codeStyle.semicolons ? "yes" : "no"}\n`;
644
+ output += `Indent: ${tc.codeStyle.indentSize} spaces\n`;
645
+ }
646
+ output += "\n";
647
+ }
648
+
649
+ if (context.patterns) {
650
+ output += "📐 CODE PATTERNS FOUND\n";
651
+ for (const [type, patterns] of Object.entries(context.patterns)) {
652
+ if (Array.isArray(patterns) && patterns.length > 0) {
653
+ output += `${type}: ${patterns.length} pattern(s)\n`;
654
+ }
655
+ }
656
+ output += "\n";
657
+ }
658
+
659
+ if (context.cursorRules) {
660
+ output += "📜 PROJECT RULES LOADED\n";
661
+ output += `- .cursorrules: ${context.cursorRules.length} characters\n`;
662
+ }
663
+ if (context.windsurfRules) {
664
+ output += `- .windsurf/rules: ${context.windsurfRules.length} characters\n`;
665
+ }
666
+
667
+ output += "\n" + "═".repeat(50) + "\n";
668
+ output += "Use guardrail_architect_guide for specific guidance.\n";
669
+
670
+ return { content: [{ type: "text", text: output }] };
671
+ }
672
+
673
+ case "guardrail_architect_guide": {
674
+ const { intent, target_path } = args;
675
+ const context = await loadCodebaseContext(projectPath);
676
+ const guidance = getGuidance(context, intent, target_path);
677
+
678
+ return { content: [{ type: "text", text: guidance }] };
679
+ }
680
+
681
+ case "guardrail_architect_validate": {
682
+ const { file_path, content } = args;
683
+ const context = await loadCodebaseContext(projectPath);
684
+ const result = validateCode(context, file_path, content);
685
+
686
+ let output = "\n✅ VALIDATION RESULT\n";
687
+ output += "═".repeat(50) + "\n";
688
+ output += `File: ${file_path}\n`;
689
+ output += `Score: ${result.score}/100\n`;
690
+ output += `Status: ${result.valid ? "✅ PASSED" : "❌ ISSUES FOUND"}\n\n`;
691
+
692
+ if (result.issues.length > 0) {
693
+ output += "❌ ISSUES:\n";
694
+ for (const issue of result.issues) {
695
+ const icon = issue.severity === "error" ? "🚫" : "⚠️";
696
+ output += `${icon} [${issue.rule}] ${issue.message}\n`;
697
+ }
698
+ output += "\n";
699
+ }
700
+
701
+ if (result.suggestions.length > 0) {
702
+ output += "💡 SUGGESTIONS:\n";
703
+ for (const suggestion of result.suggestions) {
704
+ output += `• ${suggestion}\n`;
705
+ }
706
+ output += "\n";
707
+ }
708
+
709
+ if (result.valid && result.issues.length === 0) {
710
+ output += "✨ Code follows all codebase patterns!\n";
711
+ }
712
+
713
+ output += "═".repeat(50) + "\n";
714
+
715
+ return {
716
+ content: [{ type: "text", text: output }],
717
+ isError: !result.valid,
718
+ };
719
+ }
720
+
721
+ case "guardrail_architect_patterns": {
722
+ const { pattern_type } = args;
723
+ const context = await loadCodebaseContext(projectPath);
724
+
725
+ let output = "\n📐 CODE PATTERNS\n";
726
+ output += "═".repeat(50) + "\n\n";
727
+
728
+ const patterns = context.patterns || {};
729
+ const types =
730
+ pattern_type === "all" ? Object.keys(patterns) : [pattern_type];
731
+
732
+ for (const type of types) {
733
+ const typePatterns = patterns[type] || [];
734
+ if (typePatterns.length > 0) {
735
+ output += `📁 ${type.toUpperCase()}\n\n`;
736
+ for (const pattern of typePatterns) {
737
+ output += `${pattern.name}:\n`;
738
+ output += "```typescript\n" + pattern.template + "\n```\n\n";
739
+ if (pattern.examples?.length > 0) {
740
+ output += "Examples:\n";
741
+ for (const ex of pattern.examples) {
742
+ output += ` • ${ex}\n`;
743
+ }
744
+ }
745
+ output += `Found: ${pattern.count || 0} instances\n\n`;
746
+ }
747
+ }
748
+ }
749
+
750
+ if (
751
+ Object.keys(patterns).every((k) => (patterns[k]?.length || 0) === 0)
752
+ ) {
753
+ output +=
754
+ "No patterns found. Run `guardrail context` to generate codebase analysis.\n";
755
+ }
756
+
757
+ output += "═".repeat(50) + "\n";
758
+
759
+ return { content: [{ type: "text", text: output }] };
760
+ }
761
+
762
+ case "guardrail_architect_dependencies": {
763
+ const { file_path } = args;
764
+ const context = await loadCodebaseContext(projectPath);
765
+
766
+ let output = "\n🔗 DEPENDENCY ANALYSIS\n";
767
+ output += "═".repeat(50) + "\n";
768
+ output += `File: ${file_path}\n\n`;
769
+
770
+ const depGraph = context.dependencyGraph;
771
+
772
+ if (depGraph?.edges) {
773
+ const basename = path.basename(file_path);
774
+ const importedBy = depGraph.edges
775
+ .filter((e) => e.to.includes(basename))
776
+ .map((e) => e.from);
777
+ const imports = depGraph.edges
778
+ .filter((e) => e.from.includes(basename))
779
+ .map((e) => e.to);
780
+
781
+ output += "📥 IMPORTED BY:\n";
782
+ if (importedBy.length > 0) {
783
+ for (const f of importedBy.slice(0, 10)) {
784
+ output += ` • ${f}\n`;
785
+ }
786
+ if (importedBy.length > 10) {
787
+ output += ` ... and ${importedBy.length - 10} more\n`;
788
+ }
789
+ } else {
790
+ output += " (no dependents found)\n";
791
+ }
792
+
793
+ output += "\n📤 IMPORTS:\n";
794
+ if (imports.length > 0) {
795
+ for (const f of imports.slice(0, 10)) {
796
+ output += ` • ${f}\n`;
797
+ }
798
+ } else {
799
+ output += " (no imports found)\n";
800
+ }
801
+
802
+ output += `\n⚠️ IMPACT: Changes may affect ${importedBy.length} file(s)\n`;
803
+
804
+ // Check for circular dependencies
805
+ if (depGraph.circularDependencies?.length > 0) {
806
+ const relevant = depGraph.circularDependencies.filter((c) =>
807
+ c.includes(basename),
808
+ );
809
+ if (relevant.length > 0) {
810
+ output += "\n🔄 CIRCULAR DEPENDENCIES:\n";
811
+ for (const cycle of relevant) {
812
+ output += ` ${cycle.join(" → ")}\n`;
813
+ }
814
+ }
815
+ }
816
+ } else {
817
+ output += "No dependency data available.\n";
818
+ output += "Run `guardrail context` to generate dependency graph.\n";
819
+ }
820
+
821
+ output += "\n" + "═".repeat(50) + "\n";
822
+
823
+ return { content: [{ type: "text", text: output }] };
824
+ }
825
+
826
+ default:
827
+ return {
828
+ content: [{ type: "text", text: `Unknown tool: ${name}` }],
829
+ isError: true,
830
+ };
831
+ }
832
+ }
833
+
834
+ export {
835
+ CODEBASE_ARCHITECT_TOOLS,
836
+ handleCodebaseArchitectTool,
837
+ loadCodebaseContext,
838
+ };