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,707 @@
1
+ /**
2
+ * Architect Agent MCP Tools
3
+ *
4
+ * Tools that let AI agents consult the Architect before writing code:
5
+ *
6
+ * 1. guardrail_architect_review - Review code against architecture patterns
7
+ * 2. guardrail_architect_suggest - Get suggestions before writing code
8
+ * 3. guardrail_architect_learn - Learn patterns from existing code
9
+ * 4. guardrail_architect_patterns - List active patterns
10
+ */
11
+
12
+ import fs from "fs";
13
+ import path from "path";
14
+
15
+ // Pattern types and their validation rules
16
+ const ARCHITECTURE_PATTERNS = {
17
+ // React patterns
18
+ react_import_order: {
19
+ name: "React Import Order",
20
+ description: "React imports first, then external, then internal",
21
+ check: (code) => {
22
+ const imports = code.match(/^import .+ from ['"][^'"]+['"];?$/gm) || [];
23
+ if (imports.length < 2) return { pass: true };
24
+
25
+ const reactIndex = imports.findIndex(
26
+ (i) => i.includes("'react'") || i.includes('"react"'),
27
+ );
28
+ if (reactIndex > 0) {
29
+ return {
30
+ pass: false,
31
+ message: "React imports should be at the top",
32
+ fix: "Move React imports to the beginning of the import block",
33
+ };
34
+ }
35
+ return { pass: true };
36
+ },
37
+ },
38
+
39
+ component_pascal_case: {
40
+ name: "PascalCase Components",
41
+ description: "React components must use PascalCase naming",
42
+ check: (code, filePath) => {
43
+ if (!filePath.match(/\.(jsx|tsx)$/)) return { pass: true };
44
+
45
+ const match = code.match(
46
+ /(?:function|const)\s+([a-z][a-zA-Z]*)\s*(?:=\s*\(|:\s*(?:React\.)?FC|\()/,
47
+ );
48
+ if (match && /^[a-z]/.test(match[1])) {
49
+ if (code.includes("return") && code.includes("<")) {
50
+ return {
51
+ pass: false,
52
+ message: `Component "${match[1]}" should use PascalCase`,
53
+ fix: `Rename to "${match[1].charAt(0).toUpperCase() + match[1].slice(1)}"`,
54
+ };
55
+ }
56
+ }
57
+ return { pass: true };
58
+ },
59
+ },
60
+
61
+ hook_use_prefix: {
62
+ name: "Hook Naming",
63
+ description: 'Custom hooks must start with "use"',
64
+ check: (code, filePath) => {
65
+ if (!filePath.includes("hook") && !filePath.includes("use"))
66
+ return { pass: true };
67
+
68
+ const funcMatch = code.match(
69
+ /(?:function|const)\s+([a-zA-Z]+)\s*(?:=\s*\(|\()/,
70
+ );
71
+ if (funcMatch && !funcMatch[1].startsWith("use")) {
72
+ if (code.includes("useState") || code.includes("useEffect")) {
73
+ return {
74
+ pass: false,
75
+ message: `Custom hook "${funcMatch[1]}" must start with "use"`,
76
+ fix: `Rename to "use${funcMatch[1].charAt(0).toUpperCase() + funcMatch[1].slice(1)}"`,
77
+ };
78
+ }
79
+ }
80
+ return { pass: true };
81
+ },
82
+ },
83
+
84
+ // TypeScript patterns
85
+ no_any_type: {
86
+ name: "No Any Type",
87
+ description: 'Avoid using "any" type',
88
+ check: (code) => {
89
+ if (code.match(/:\s*any\b/) && !code.includes("@ts-")) {
90
+ return {
91
+ pass: false,
92
+ message: 'Avoid using "any" type - use specific types',
93
+ fix: 'Replace "any" with a specific type or "unknown" for truly unknown types',
94
+ };
95
+ }
96
+ return { pass: true };
97
+ },
98
+ },
99
+
100
+ explicit_return_types: {
101
+ name: "Explicit Return Types",
102
+ description: "Functions should have explicit return types",
103
+ check: (code) => {
104
+ const asyncFuncs =
105
+ code.match(/(?:async\s+)?function\s+\w+\s*\([^)]*\)\s*\{/g) || [];
106
+ for (const func of asyncFuncs) {
107
+ if (!func.includes(":") && !func.includes("constructor")) {
108
+ return {
109
+ pass: false,
110
+ message: "Function missing return type annotation",
111
+ fix: "Add return type: function name(): ReturnType { ... }",
112
+ };
113
+ }
114
+ }
115
+ return { pass: true };
116
+ },
117
+ },
118
+
119
+ // API patterns
120
+ api_error_handling: {
121
+ name: "API Error Handling",
122
+ description: "API routes must have try/catch",
123
+ check: (code, filePath) => {
124
+ if (!filePath.includes("api") && !filePath.includes("route"))
125
+ return { pass: true };
126
+
127
+ if (
128
+ code.includes("async") &&
129
+ (code.includes("req") || code.includes("request"))
130
+ ) {
131
+ if (!code.includes("try") || !code.includes("catch")) {
132
+ return {
133
+ pass: false,
134
+ message: "API handler missing error handling",
135
+ fix: "Wrap handler logic in try/catch block",
136
+ };
137
+ }
138
+ }
139
+ return { pass: true };
140
+ },
141
+ },
142
+
143
+ // General patterns
144
+ no_console_in_production: {
145
+ name: "No Console Logs",
146
+ description: "Remove console.log from production code",
147
+ check: (code, filePath) => {
148
+ if (filePath.includes("test") || filePath.includes("spec"))
149
+ return { pass: true };
150
+
151
+ if (code.includes("console.log")) {
152
+ return {
153
+ pass: false,
154
+ message: "console.log found in production code",
155
+ fix: "Remove console.log or use a proper logging service",
156
+ };
157
+ }
158
+ return { pass: true };
159
+ },
160
+ },
161
+
162
+ no_hardcoded_secrets: {
163
+ name: "No Hardcoded Secrets",
164
+ description: "API keys and secrets should use env vars",
165
+ check: (code) => {
166
+ const patterns = [
167
+ /['"]sk-[a-zA-Z0-9]{20,}['"]/, // OpenAI
168
+ /['"]ghp_[a-zA-Z0-9]{20,}['"]/, // GitHub
169
+ /['"]AKIA[A-Z0-9]{16}['"]/, // AWS
170
+ ];
171
+
172
+ for (const pattern of patterns) {
173
+ if (pattern.test(code)) {
174
+ return {
175
+ pass: false,
176
+ message: "Hardcoded secret/API key detected",
177
+ fix: "Move secret to environment variable: process.env.SECRET_NAME",
178
+ };
179
+ }
180
+ }
181
+ return { pass: true };
182
+ },
183
+ },
184
+
185
+ service_singleton: {
186
+ name: "Service Singleton",
187
+ description: "Services should export singleton instances",
188
+ check: (code, filePath) => {
189
+ if (!filePath.includes("service")) return { pass: true };
190
+
191
+ if (code.includes("class") && code.includes("Service")) {
192
+ if (!code.match(/export\s+(?:const|let)\s+\w+\s*=\s*new/)) {
193
+ return {
194
+ pass: false,
195
+ message: "Service should export a singleton instance",
196
+ fix: "Add: export const serviceName = new ServiceClass();",
197
+ };
198
+ }
199
+ }
200
+ return { pass: true };
201
+ },
202
+ },
203
+ };
204
+
205
+ // File type detection
206
+ function detectFileType(filePath, content) {
207
+ const fileName = path.basename(filePath);
208
+ const dirName = path.dirname(filePath);
209
+
210
+ if (fileName.includes(".test.") || fileName.includes(".spec.")) return "test";
211
+ if (fileName.includes("hook") || fileName.startsWith("use")) return "hook";
212
+ if (
213
+ dirName.includes("components") ||
214
+ (content.includes("return") && content.includes("<"))
215
+ )
216
+ return "component";
217
+ if (dirName.includes("api") || dirName.includes("routes")) return "api";
218
+ if (dirName.includes("services") || fileName.includes("service"))
219
+ return "service";
220
+ if (dirName.includes("utils") || dirName.includes("lib")) return "util";
221
+ if (fileName.includes(".d.ts") || dirName.includes("types")) return "type";
222
+
223
+ return "unknown";
224
+ }
225
+
226
+ // Framework detection
227
+ function detectFramework(content) {
228
+ if (content.includes("from 'react'") || content.includes('from "react"'))
229
+ return "react";
230
+ if (content.includes("from 'vue'")) return "vue";
231
+ if (content.includes("from 'svelte'")) return "svelte";
232
+ if (content.includes("from 'express'")) return "express";
233
+ if (content.includes("from 'next'")) return "next";
234
+ return null;
235
+ }
236
+
237
+ // Current project state
238
+ let projectPatterns = {};
239
+ let learnedPatterns = [];
240
+ let strictnessLevel = "standard"; // relaxed, standard, strict
241
+
242
+ /**
243
+ * Review code against architecture patterns
244
+ */
245
+ function reviewCode(filePath, content) {
246
+ const fileType = detectFileType(filePath, content);
247
+ const framework = detectFramework(content);
248
+ const violations = [];
249
+ const suggestions = [];
250
+
251
+ // Check all patterns
252
+ for (const [id, pattern] of Object.entries(ARCHITECTURE_PATTERNS)) {
253
+ const result = pattern.check(content, filePath);
254
+ if (!result.pass) {
255
+ violations.push({
256
+ pattern: id,
257
+ name: pattern.name,
258
+ message: result.message,
259
+ fix: result.fix,
260
+ severity:
261
+ id.includes("secret") || id.includes("error") ? "error" : "warning",
262
+ });
263
+ }
264
+ }
265
+
266
+ // Generate suggestions based on file type
267
+ if (fileType === "component" && framework === "react") {
268
+ if (
269
+ !content.includes("memo") &&
270
+ content.includes(".map(") &&
271
+ content.includes(".filter(")
272
+ ) {
273
+ suggestions.push({
274
+ type: "performance",
275
+ title: "Consider useMemo",
276
+ description: "Complex computations could benefit from memoization",
277
+ code: "const processed = useMemo(() => data.filter(...).map(...), [data]);",
278
+ });
279
+ }
280
+
281
+ if (
282
+ content.includes("await") &&
283
+ !content.includes("loading") &&
284
+ !content.includes("isLoading")
285
+ ) {
286
+ suggestions.push({
287
+ type: "ux",
288
+ title: "Add Loading State",
289
+ description: "Async operations should show loading state",
290
+ code: "const [isLoading, setIsLoading] = useState(false);",
291
+ });
292
+ }
293
+ }
294
+
295
+ if (fileType === "api") {
296
+ if (
297
+ !content.includes("validate") &&
298
+ !content.includes("zod") &&
299
+ !content.includes("yup")
300
+ ) {
301
+ suggestions.push({
302
+ type: "security",
303
+ title: "Add Input Validation",
304
+ description: "API routes should validate input data",
305
+ code: "import { z } from 'zod';\nconst schema = z.object({ ... });",
306
+ });
307
+ }
308
+ }
309
+
310
+ // Calculate score
311
+ let score = 100;
312
+ for (const v of violations) {
313
+ score -= v.severity === "error" ? 20 : 10;
314
+ }
315
+ score -= suggestions.length * 2;
316
+ score = Math.max(0, Math.min(100, score));
317
+
318
+ const approved =
319
+ strictnessLevel === "strict"
320
+ ? violations.length === 0
321
+ : violations.filter((v) => v.severity === "error").length === 0;
322
+
323
+ return {
324
+ approved,
325
+ fileType,
326
+ framework,
327
+ score,
328
+ violations,
329
+ suggestions,
330
+ summary: approved
331
+ ? `āœ… Approved (${score}/100) - ${violations.length} warnings, ${suggestions.length} suggestions`
332
+ : `šŸ›‘ BLOCKED (${score}/100) - ${violations.filter((v) => v.severity === "error").length} errors must be fixed`,
333
+ };
334
+ }
335
+
336
+ /**
337
+ * Get suggestions before writing code
338
+ */
339
+ function getSuggestions(intent, context) {
340
+ const suggestions = [];
341
+
342
+ // Based on intent (what they want to create)
343
+ if (intent.includes("component") || intent.includes("ui")) {
344
+ suggestions.push({
345
+ pattern: "React Component",
346
+ template: `interface Props {
347
+ // Define props here
348
+ }
349
+
350
+ export function ComponentName({ prop }: Props) {
351
+ // 1. Hooks first
352
+ const [state, setState] = useState();
353
+
354
+ // 2. Event handlers
355
+ const handleClick = () => {};
356
+
357
+ // 3. Render
358
+ return (
359
+ <div>
360
+ {/* JSX */}
361
+ </div>
362
+ );
363
+ }`,
364
+ rules: [
365
+ "Use PascalCase for component name",
366
+ "Define Props interface",
367
+ "Hooks before handlers before return",
368
+ "Use named export for components",
369
+ ],
370
+ });
371
+ }
372
+
373
+ if (intent.includes("hook") || intent.includes("use")) {
374
+ suggestions.push({
375
+ pattern: "Custom Hook",
376
+ template: `export function useHookName(param: Type) {
377
+ // 1. Internal state
378
+ const [state, setState] = useState();
379
+
380
+ // 2. Effects
381
+ useEffect(() => {
382
+ // Effect logic
383
+ }, [dependencies]);
384
+
385
+ // 3. Return value
386
+ return { state, actions };
387
+ }`,
388
+ rules: [
389
+ 'Name must start with "use"',
390
+ "Return object or tuple",
391
+ "Document dependencies clearly",
392
+ ],
393
+ });
394
+ }
395
+
396
+ if (
397
+ intent.includes("api") ||
398
+ intent.includes("endpoint") ||
399
+ intent.includes("route")
400
+ ) {
401
+ suggestions.push({
402
+ pattern: "API Route",
403
+ template: `export async function handler(req: Request, res: Response) {
404
+ try {
405
+ // 1. Validate input
406
+ const data = schema.parse(req.body);
407
+
408
+ // 2. Business logic
409
+ const result = await service.process(data);
410
+
411
+ // 3. Return response
412
+ return res.json({ success: true, data: result });
413
+
414
+ } catch (error) {
415
+ console.error('API Error:', error);
416
+ return res.status(500).json({ error: 'Internal server error' });
417
+ }
418
+ }`,
419
+ rules: [
420
+ "Always wrap in try/catch",
421
+ "Validate input data",
422
+ "Return consistent response shape",
423
+ "Log errors properly",
424
+ ],
425
+ });
426
+ }
427
+
428
+ if (intent.includes("service") || intent.includes("class")) {
429
+ suggestions.push({
430
+ pattern: "Service Class",
431
+ template: `class ServiceName {
432
+ private dependency: Dependency;
433
+
434
+ constructor(dependency: Dependency) {
435
+ this.dependency = dependency;
436
+ }
437
+
438
+ async method(param: Type): Promise<Result> {
439
+ // Implementation
440
+ }
441
+ }
442
+
443
+ // Export singleton
444
+ export const serviceName = new ServiceName(dependency);`,
445
+ rules: [
446
+ "Use dependency injection",
447
+ "Export singleton instance",
448
+ "Add explicit return types",
449
+ "Keep methods focused",
450
+ ],
451
+ });
452
+ }
453
+
454
+ return suggestions;
455
+ }
456
+
457
+ /**
458
+ * MCP Tool Definitions
459
+ */
460
+ const ARCHITECT_TOOLS = [
461
+ {
462
+ name: "guardrail_architect_review",
463
+ description: `šŸ›ļø ARCHITECT REVIEW - Review code against architecture patterns BEFORE committing.
464
+
465
+ Call this tool to check if code follows project architecture:
466
+ - Import ordering and structure
467
+ - Naming conventions (PascalCase, camelCase, use*)
468
+ - Error handling patterns
469
+ - TypeScript best practices
470
+ - Security patterns (no secrets, validation)
471
+
472
+ Returns: approval status, score, violations, and suggestions.
473
+ If blocked, you MUST fix the violations before proceeding.`,
474
+ inputSchema: {
475
+ type: "object",
476
+ properties: {
477
+ file_path: {
478
+ type: "string",
479
+ description: "Path to the file being reviewed",
480
+ },
481
+ content: {
482
+ type: "string",
483
+ description: "The code content to review",
484
+ },
485
+ },
486
+ required: ["file_path", "content"],
487
+ },
488
+ },
489
+
490
+ {
491
+ name: "guardrail_architect_suggest",
492
+ description: `šŸ’” ARCHITECT SUGGEST - Get architectural guidance BEFORE writing code.
493
+
494
+ Call this FIRST when you're about to create:
495
+ - A new component
496
+ - A new hook
497
+ - An API route
498
+ - A service class
499
+ - Any significant new code
500
+
501
+ Returns: recommended patterns, templates, and rules to follow.`,
502
+ inputSchema: {
503
+ type: "object",
504
+ properties: {
505
+ intent: {
506
+ type: "string",
507
+ description:
508
+ 'What you want to create (e.g., "user profile component", "authentication hook", "payment API endpoint")',
509
+ },
510
+ context: {
511
+ type: "string",
512
+ description: "Additional context about the feature",
513
+ },
514
+ },
515
+ required: ["intent"],
516
+ },
517
+ },
518
+
519
+ {
520
+ name: "guardrail_architect_patterns",
521
+ description: `šŸ“‹ List all active architecture patterns and their rules.`,
522
+ inputSchema: {
523
+ type: "object",
524
+ properties: {
525
+ category: {
526
+ type: "string",
527
+ enum: ["all", "react", "typescript", "api", "security"],
528
+ description: "Filter patterns by category",
529
+ },
530
+ },
531
+ },
532
+ },
533
+
534
+ {
535
+ name: "guardrail_architect_set_strictness",
536
+ description: `āš™ļø Set architect strictness level:
537
+ - relaxed: Only block on critical issues
538
+ - standard: Block on errors, warn on issues
539
+ - strict: Block on any violation`,
540
+ inputSchema: {
541
+ type: "object",
542
+ properties: {
543
+ level: {
544
+ type: "string",
545
+ enum: ["relaxed", "standard", "strict"],
546
+ },
547
+ },
548
+ required: ["level"],
549
+ },
550
+ },
551
+ ];
552
+
553
+ /**
554
+ * Handle MCP tool calls
555
+ */
556
+ async function handleArchitectTool(name, args) {
557
+ switch (name) {
558
+ case "guardrail_architect_review": {
559
+ const { file_path, content } = args;
560
+ const result = reviewCode(file_path, content);
561
+
562
+ let output = "\nšŸ›ļø ARCHITECT REVIEW\n";
563
+ output += "═".repeat(50) + "\n";
564
+ output += `File: ${file_path}\n`;
565
+ output += `Type: ${result.fileType} | Framework: ${result.framework || "none"}\n`;
566
+ output += `Score: ${result.score}/100\n\n`;
567
+
568
+ if (result.violations.length > 0) {
569
+ output += "āŒ VIOLATIONS:\n";
570
+ for (const v of result.violations) {
571
+ const icon = v.severity === "error" ? "🚫" : "āš ļø";
572
+ output += `${icon} [${v.name}] ${v.message}\n`;
573
+ output += ` Fix: ${v.fix}\n\n`;
574
+ }
575
+ }
576
+
577
+ if (result.suggestions.length > 0) {
578
+ output += "šŸ’” SUGGESTIONS:\n";
579
+ for (const s of result.suggestions) {
580
+ output += `• [${s.type}] ${s.title}\n`;
581
+ output += ` ${s.description}\n`;
582
+ if (s.code) {
583
+ output += ` Example: ${s.code}\n`;
584
+ }
585
+ output += "\n";
586
+ }
587
+ }
588
+
589
+ output += "═".repeat(50) + "\n";
590
+ output += result.summary + "\n";
591
+
592
+ return {
593
+ content: [{ type: "text", text: output }],
594
+ isError: !result.approved,
595
+ };
596
+ }
597
+
598
+ case "guardrail_architect_suggest": {
599
+ const { intent, context } = args;
600
+ const suggestions = getSuggestions(intent, context || "");
601
+
602
+ let output = "\nšŸ’” ARCHITECT SUGGESTIONS\n";
603
+ output += "═".repeat(50) + "\n";
604
+ output += `For: "${intent}"\n\n`;
605
+
606
+ for (const s of suggestions) {
607
+ output += `šŸ“‹ ${s.pattern}\n\n`;
608
+ output += `Template:\n\`\`\`typescript\n${s.template}\n\`\`\`\n\n`;
609
+ output += `Rules to follow:\n`;
610
+ for (const rule of s.rules) {
611
+ output += ` āœ“ ${rule}\n`;
612
+ }
613
+ output += "\n";
614
+ }
615
+
616
+ if (suggestions.length === 0) {
617
+ output += "No specific pattern suggestions for this intent.\n";
618
+ output += "General rules:\n";
619
+ output += " āœ“ Use TypeScript with explicit types\n";
620
+ output += " āœ“ Handle errors properly\n";
621
+ output += " āœ“ Follow project naming conventions\n";
622
+ }
623
+
624
+ output += "═".repeat(50) + "\n";
625
+
626
+ return {
627
+ content: [{ type: "text", text: output }],
628
+ };
629
+ }
630
+
631
+ case "guardrail_architect_patterns": {
632
+ const { category } = args;
633
+
634
+ let output = "\nšŸ“‹ ARCHITECTURE PATTERNS\n";
635
+ output += "═".repeat(50) + "\n";
636
+ output += `Strictness: ${strictnessLevel.toUpperCase()}\n\n`;
637
+
638
+ for (const [id, pattern] of Object.entries(ARCHITECTURE_PATTERNS)) {
639
+ if (category && category !== "all") {
640
+ if (
641
+ category === "react" &&
642
+ !id.includes("react") &&
643
+ !id.includes("component") &&
644
+ !id.includes("hook")
645
+ )
646
+ continue;
647
+ if (
648
+ category === "typescript" &&
649
+ !id.includes("type") &&
650
+ !id.includes("any")
651
+ )
652
+ continue;
653
+ if (category === "api" && !id.includes("api")) continue;
654
+ if (
655
+ category === "security" &&
656
+ !id.includes("secret") &&
657
+ !id.includes("console")
658
+ )
659
+ continue;
660
+ }
661
+
662
+ output += `• ${pattern.name}\n`;
663
+ output += ` ${pattern.description}\n\n`;
664
+ }
665
+
666
+ output += "═".repeat(50) + "\n";
667
+
668
+ return {
669
+ content: [{ type: "text", text: output }],
670
+ };
671
+ }
672
+
673
+ case "guardrail_architect_set_strictness": {
674
+ const { level } = args;
675
+ strictnessLevel = level;
676
+
677
+ return {
678
+ content: [
679
+ {
680
+ type: "text",
681
+ text:
682
+ `šŸ›ļø Architect strictness set to: ${level.toUpperCase()}\n\n` +
683
+ (level === "relaxed"
684
+ ? "Only critical issues will block."
685
+ : level === "standard"
686
+ ? "Errors block, warnings are reported."
687
+ : "All violations will block."),
688
+ },
689
+ ],
690
+ };
691
+ }
692
+
693
+ default:
694
+ return {
695
+ content: [{ type: "text", text: `Unknown tool: ${name}` }],
696
+ isError: true,
697
+ };
698
+ }
699
+ }
700
+
701
+ export {
702
+ ARCHITECT_TOOLS,
703
+ handleArchitectTool,
704
+ reviewCode,
705
+ getSuggestions,
706
+ ARCHITECTURE_PATTERNS,
707
+ };