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,748 @@
1
+ /**
2
+ * Guardrail 2.0 MCP Tools - Consolidated to 6 Tools
3
+ *
4
+ * Tools:
5
+ * checkpoint() - Pre/post write enforcement, block AI until fixed
6
+ * check() - Verify code is real, wired, honest
7
+ * ship() - Go/No-Go decision (GO / WARN / NO-GO)
8
+ * fix() - Fix blocking issues safely
9
+ * status() - Health + version info
10
+ * set_strictness() - Set checkpoint strictness level
11
+ *
12
+ * Server-side enforcement:
13
+ * - Fix-Only Mode: Rejects patches outside allowedFiles when blocked
14
+ * - Intent Lock: Prevents scope creep when enabled
15
+ */
16
+
17
+ import { execSync } from "child_process";
18
+ import fs from "fs/promises";
19
+ import path from "path";
20
+
21
+ // Strictness levels for checkpoint
22
+ const STRICTNESS_LEVELS = ["chill", "standard", "strict", "paranoid"];
23
+
24
+ // Current strictness (in-memory, should be persisted per-project)
25
+ let currentStrictness = "standard";
26
+
27
+ // Blocked state tracking (in-memory, should be persisted)
28
+ const blockedState = new Map();
29
+
30
+ // ============================================================================
31
+ // TOOL DEFINITIONS - 6 TOOLS ONLY
32
+ // ============================================================================
33
+
34
+ const GUARDRAIL_2_TOOLS = [
35
+ // 1. CHECKPOINT - Block AI agents until issues are fixed
36
+ {
37
+ name: "checkpoint",
38
+ description: `🛡️ Block AI agents until issues are fixed. Call BEFORE and AFTER every code write.
39
+
40
+ Returns:
41
+ - PASS: Continue writing code
42
+ - BLOCKED: Stop and fix issues first. Response includes:
43
+ - blockedFiles: Files with violations
44
+ - violations: List of issues with ruleId, message, file, line
45
+ - fixOnly: { allowedFiles, forbiddenActions } - Fix-Only Mode restrictions
46
+
47
+ If BLOCKED, you MUST:
48
+ 1. Enter Fix-Only Mode
49
+ 2. Only modify files in allowedFiles
50
+ 3. Do NOT add features, refactors, or new files
51
+ 4. Call checkpoint again after fixes`,
52
+ inputSchema: {
53
+ type: "object",
54
+ properties: {
55
+ file_path: {
56
+ type: "string",
57
+ description: "Path to file being written",
58
+ },
59
+ content: {
60
+ type: "string",
61
+ description: "Content to validate (for pre-write check)",
62
+ },
63
+ action: {
64
+ type: "string",
65
+ enum: ["pre_write", "post_write", "status"],
66
+ description: "Checkpoint action type",
67
+ default: "pre_write",
68
+ },
69
+ projectPath: {
70
+ type: "string",
71
+ default: ".",
72
+ },
73
+ },
74
+ required: ["file_path"],
75
+ },
76
+ },
77
+
78
+ // 2. CHECK - Verify code is real, wired, honest
79
+ {
80
+ name: "check",
81
+ description: `🔍 Verify code is real, wired, and honest.
82
+
83
+ Consolidates: scan, validate, mockproof
84
+ Returns: PASS | WARN | FAIL with findings list
85
+
86
+ Use cases:
87
+ - Quick code verification
88
+ - Mock/placeholder detection (--mocks)
89
+ - Security scanning (--only=security)
90
+ - AI code validation (--validate)`,
91
+ inputSchema: {
92
+ type: "object",
93
+ properties: {
94
+ projectPath: {
95
+ type: "string",
96
+ default: ".",
97
+ },
98
+ mocks: {
99
+ type: "boolean",
100
+ description: "Focus on mock/placeholder detection",
101
+ default: false,
102
+ },
103
+ validate: {
104
+ type: "boolean",
105
+ description: "Include AI code validation",
106
+ default: false,
107
+ },
108
+ only: {
109
+ type: "array",
110
+ items: { type: "string" },
111
+ description:
112
+ "Run specific checks: secrets, auth, routes, mocks, hygiene, security",
113
+ },
114
+ json: {
115
+ type: "boolean",
116
+ description: "Return machine-readable JSON",
117
+ default: true,
118
+ },
119
+ },
120
+ },
121
+ },
122
+
123
+ // 3. SHIP - Go/No-Go decision
124
+ {
125
+ name: "ship",
126
+ description: `🚀 Decide if this can ship. Returns GO | WARN | NO-GO.
127
+
128
+ Consolidates: ship, gate, realityproof, ai-test, badge, evidence
129
+ Flags:
130
+ - --ci: CI/CD hard fail mode
131
+ - --runtime: Runtime verification (Playwright)
132
+ - --badge: Generate ship badge
133
+ - --report: Generate full report
134
+ - --evidence: Enterprise audit evidence pack`,
135
+ inputSchema: {
136
+ type: "object",
137
+ properties: {
138
+ projectPath: {
139
+ type: "string",
140
+ default: ".",
141
+ },
142
+ ci: {
143
+ type: "boolean",
144
+ description: "CI mode - hard fail on issues",
145
+ default: false,
146
+ },
147
+ runtime: {
148
+ type: "boolean",
149
+ description: "Run runtime verification",
150
+ default: false,
151
+ },
152
+ url: {
153
+ type: "string",
154
+ description: "URL for runtime testing (required if runtime=true)",
155
+ },
156
+ badge: {
157
+ type: "boolean",
158
+ description: "Generate ship badge",
159
+ default: false,
160
+ },
161
+ report: {
162
+ type: "boolean",
163
+ description: "Generate detailed report",
164
+ default: true,
165
+ },
166
+ evidence: {
167
+ type: "boolean",
168
+ description: "Generate audit evidence pack (Enterprise)",
169
+ default: false,
170
+ },
171
+ },
172
+ },
173
+ },
174
+
175
+ // 4. FIX - Fix blocking issues safely
176
+ {
177
+ name: "fix",
178
+ description: `🔧 Fix blocking issues safely.
179
+
180
+ Options:
181
+ - --plan: Show fix plan without applying (default)
182
+ - --apply: Apply the fixes
183
+ - --pr: Open a pull request with fixes
184
+
185
+ Respects Fix-Only Mode restrictions when checkpoint is BLOCKED.`,
186
+ inputSchema: {
187
+ type: "object",
188
+ properties: {
189
+ projectPath: {
190
+ type: "string",
191
+ default: ".",
192
+ },
193
+ plan: {
194
+ type: "boolean",
195
+ description: "Show fix plan without applying",
196
+ default: true,
197
+ },
198
+ apply: {
199
+ type: "boolean",
200
+ description: "Apply fixes",
201
+ default: false,
202
+ },
203
+ pr: {
204
+ type: "boolean",
205
+ description: "Open PR with fixes",
206
+ default: false,
207
+ },
208
+ scope: {
209
+ type: "string",
210
+ enum: ["all", "secrets", "auth", "mocks", "routes"],
211
+ default: "all",
212
+ },
213
+ },
214
+ },
215
+ },
216
+
217
+ // 5. STATUS - Health + version info
218
+ {
219
+ name: "status",
220
+ description: `📊 Get Guardrail status - health, version, config, checkpoint state.
221
+
222
+ Returns:
223
+ - healthy: boolean
224
+ - version: string
225
+ - checkpoint: { strictness, blockedFiles, fixOnlyMode }
226
+ - lastRun: timestamp and results`,
227
+ inputSchema: {
228
+ type: "object",
229
+ properties: {
230
+ projectPath: {
231
+ type: "string",
232
+ default: ".",
233
+ },
234
+ },
235
+ },
236
+ },
237
+
238
+ // 6. SET_STRICTNESS - Set checkpoint strictness level
239
+ {
240
+ name: "set_strictness",
241
+ description: `⚙️ Set checkpoint strictness level.
242
+
243
+ Levels:
244
+ - chill: TODOs, FIXMEs, mock data, placeholders
245
+ - standard: + console.log, debugger, localhost URLs
246
+ - strict: + any types, @ts-ignore, eslint-disable
247
+ - paranoid: + nested ternaries, inline styles, JSDoc requirements`,
248
+ inputSchema: {
249
+ type: "object",
250
+ properties: {
251
+ level: {
252
+ type: "string",
253
+ enum: ["chill", "standard", "strict", "paranoid"],
254
+ description: "Strictness level",
255
+ },
256
+ projectPath: {
257
+ type: "string",
258
+ default: ".",
259
+ },
260
+ },
261
+ required: ["level"],
262
+ },
263
+ },
264
+ ];
265
+
266
+ // ============================================================================
267
+ // CHECKPOINT RULES BY STRICTNESS
268
+ // ============================================================================
269
+
270
+ const CHECKPOINT_RULES = {
271
+ chill: [
272
+ {
273
+ id: "no-todo",
274
+ pattern: /\/\/\s*TODO[:\s].*$/gim,
275
+ message: "TODO comment found",
276
+ block: true,
277
+ },
278
+ {
279
+ id: "no-fixme",
280
+ pattern: /\/\/\s*FIXME[:\s].*$/gim,
281
+ message: "FIXME comment found",
282
+ block: true,
283
+ },
284
+ {
285
+ id: "no-mock-data",
286
+ pattern: /['"]mock['"]/gi,
287
+ message: "Mock data reference",
288
+ block: true,
289
+ },
290
+ {
291
+ id: "no-placeholder",
292
+ pattern: /placeholder|lorem ipsum/gi,
293
+ message: "Placeholder content",
294
+ block: true,
295
+ },
296
+ ],
297
+ standard: [
298
+ {
299
+ id: "no-console-log",
300
+ pattern: /console\.log\s*\(/g,
301
+ message: "console.log found",
302
+ block: true,
303
+ },
304
+ {
305
+ id: "no-debugger",
306
+ pattern: /\bdebugger\b/g,
307
+ message: "debugger statement",
308
+ block: true,
309
+ },
310
+ {
311
+ id: "no-localhost",
312
+ pattern: /localhost:\d+/g,
313
+ message: "localhost URL",
314
+ block: true,
315
+ },
316
+ {
317
+ id: "no-empty-catch",
318
+ pattern: /catch\s*\([^)]*\)\s*\{\s*\}/g,
319
+ message: "Empty catch block",
320
+ block: true,
321
+ },
322
+ ],
323
+ strict: [
324
+ {
325
+ id: "no-any",
326
+ pattern: /:\s*any\b/g,
327
+ message: "any type used",
328
+ block: true,
329
+ },
330
+ {
331
+ id: "no-ts-ignore",
332
+ pattern: /@ts-ignore/g,
333
+ message: "@ts-ignore directive",
334
+ block: true,
335
+ },
336
+ {
337
+ id: "no-ts-nocheck",
338
+ pattern: /@ts-nocheck/g,
339
+ message: "@ts-nocheck directive",
340
+ block: true,
341
+ },
342
+ {
343
+ id: "no-eslint-disable",
344
+ pattern: /eslint-disable/g,
345
+ message: "eslint-disable directive",
346
+ block: true,
347
+ },
348
+ ],
349
+ paranoid: [
350
+ {
351
+ id: "no-nested-ternary",
352
+ pattern: /\?[^:]+\?/g,
353
+ message: "Nested ternary",
354
+ block: true,
355
+ },
356
+ {
357
+ id: "no-inline-style",
358
+ pattern: /style\s*=\s*\{/g,
359
+ message: "Inline style object",
360
+ block: true,
361
+ },
362
+ ],
363
+ };
364
+
365
+ // ============================================================================
366
+ // TOOL HANDLERS
367
+ // ============================================================================
368
+
369
+ /**
370
+ * Handle checkpoint tool - server-side enforcement
371
+ */
372
+ async function handleCheckpoint(args, dirname) {
373
+ const { file_path, content, action = "pre_write", projectPath = "." } = args;
374
+ const fullPath = path.resolve(projectPath, file_path);
375
+
376
+ // Get current blocked state for this project
377
+ const projectKey = path.resolve(projectPath);
378
+ const blocked = blockedState.get(projectKey) || { files: [], violations: [] };
379
+
380
+ if (action === "status") {
381
+ return {
382
+ content: [
383
+ {
384
+ type: "text",
385
+ text: JSON.stringify(
386
+ {
387
+ status: blocked.files.length > 0 ? "BLOCKED" : "PASS",
388
+ strictness: currentStrictness,
389
+ blockedFiles: blocked.files,
390
+ violations: blocked.violations,
391
+ fixOnly:
392
+ blocked.files.length > 0
393
+ ? {
394
+ allowedFiles: blocked.files,
395
+ forbiddenActions: [
396
+ "add_file",
397
+ "add_feature",
398
+ "refactor",
399
+ "change_scope",
400
+ ],
401
+ }
402
+ : null,
403
+ },
404
+ null,
405
+ 2,
406
+ ),
407
+ },
408
+ ],
409
+ };
410
+ }
411
+
412
+ // Validate content against checkpoint rules
413
+ const violations = [];
414
+ const contentToCheck = content || "";
415
+
416
+ // Get rules for current strictness and below
417
+ const strictnessIndex = STRICTNESS_LEVELS.indexOf(currentStrictness);
418
+ const applicableLevels = STRICTNESS_LEVELS.slice(0, strictnessIndex + 1);
419
+
420
+ for (const level of applicableLevels) {
421
+ const rules = CHECKPOINT_RULES[level] || [];
422
+ for (const rule of rules) {
423
+ const matches = contentToCheck.match(rule.pattern);
424
+ if (matches) {
425
+ for (const match of matches) {
426
+ const lineNum = contentToCheck
427
+ .substring(0, contentToCheck.indexOf(match))
428
+ .split("\n").length;
429
+ violations.push({
430
+ ruleId: rule.id,
431
+ message: rule.message,
432
+ file: file_path,
433
+ line: lineNum,
434
+ match: match.substring(0, 50),
435
+ });
436
+ }
437
+ }
438
+ }
439
+ }
440
+
441
+ if (violations.length > 0) {
442
+ // Update blocked state
443
+ if (!blocked.files.includes(file_path)) {
444
+ blocked.files.push(file_path);
445
+ }
446
+ blocked.violations = [...blocked.violations, ...violations];
447
+ blockedState.set(projectKey, blocked);
448
+
449
+ return {
450
+ isError: true,
451
+ content: [
452
+ {
453
+ type: "text",
454
+ text: JSON.stringify(
455
+ {
456
+ status: "BLOCKED",
457
+ message: `🛑 CHECKPOINT BLOCKED: ${violations.length} violation(s) found`,
458
+ blockedFiles: blocked.files,
459
+ violations: violations,
460
+ fixOnly: {
461
+ allowedFiles: blocked.files,
462
+ forbiddenActions: [
463
+ "add_file",
464
+ "add_feature",
465
+ "refactor",
466
+ "change_scope",
467
+ ],
468
+ },
469
+ instruction:
470
+ "Enter Fix-Only Mode. Only modify files in allowedFiles. Do NOT add features or new files.",
471
+ },
472
+ null,
473
+ 2,
474
+ ),
475
+ },
476
+ ],
477
+ };
478
+ }
479
+
480
+ // Clear blocked state for this file if it was blocked
481
+ if (blocked.files.includes(file_path)) {
482
+ blocked.files = blocked.files.filter((f) => f !== file_path);
483
+ blocked.violations = blocked.violations.filter((v) => v.file !== file_path);
484
+ blockedState.set(projectKey, blocked);
485
+ }
486
+
487
+ return {
488
+ content: [
489
+ {
490
+ type: "text",
491
+ text: JSON.stringify(
492
+ {
493
+ status: "PASS",
494
+ message: "✅ Checkpoint passed",
495
+ blockedFiles: blocked.files,
496
+ },
497
+ null,
498
+ 2,
499
+ ),
500
+ },
501
+ ],
502
+ };
503
+ }
504
+
505
+ /**
506
+ * Handle check tool - unified code verification
507
+ */
508
+ async function handleCheck(args, dirname) {
509
+ const { projectPath = ".", mocks, validate, only, json = true } = args;
510
+
511
+ let output = "# 🔍 Guardrail Check\n\n";
512
+
513
+ try {
514
+ let cmd = `node "${path.join(dirname, "..", "bin", "guardrail.js")}" check`;
515
+ if (mocks) cmd += " --mocks";
516
+ if (validate) cmd += " --validate";
517
+ if (only?.length) cmd += ` --only=${only.join(",")}`;
518
+ if (json) cmd += " --json";
519
+
520
+ const result = execSync(cmd, {
521
+ cwd: path.resolve(projectPath),
522
+ encoding: "utf8",
523
+ maxBuffer: 10 * 1024 * 1024,
524
+ });
525
+
526
+ if (json) {
527
+ return { content: [{ type: "text", text: result }] };
528
+ }
529
+
530
+ output += result;
531
+ } catch (err) {
532
+ output += `⚠️ Check error: ${err.message}\n`;
533
+ }
534
+
535
+ return { content: [{ type: "text", text: output }] };
536
+ }
537
+
538
+ /**
539
+ * Handle ship tool - Go/No-Go decision
540
+ */
541
+ async function handleShip(args, dirname) {
542
+ const { projectPath = ".", ci, runtime, url, badge, report, evidence } = args;
543
+
544
+ let output = "# 🚀 Guardrail Ship\n\n";
545
+
546
+ try {
547
+ let cmd = `node "${path.join(dirname, "..", "bin", "guardrail.js")}" ship`;
548
+ if (ci) cmd += " --ci";
549
+ if (runtime && url) cmd += ` --runtime --url="${url}"`;
550
+ if (badge) cmd += " --badge";
551
+ if (evidence) cmd += " --evidence";
552
+ cmd += " --json";
553
+
554
+ const result = execSync(cmd, {
555
+ cwd: path.resolve(projectPath),
556
+ encoding: "utf8",
557
+ maxBuffer: 10 * 1024 * 1024,
558
+ });
559
+
560
+ return { content: [{ type: "text", text: result }] };
561
+ } catch (err) {
562
+ // Ship command exits non-zero for NO-GO
563
+ output += `## 🚫 NO-GO\n\n`;
564
+ output += `${err.message}\n`;
565
+ output += `\nRun \`guardrail fix --plan\` to see fixes.\n`;
566
+ }
567
+
568
+ return { content: [{ type: "text", text: output }] };
569
+ }
570
+
571
+ /**
572
+ * Handle fix tool
573
+ */
574
+ async function handleFix(args, dirname) {
575
+ const { projectPath = ".", plan = true, apply, pr, scope = "all" } = args;
576
+
577
+ // Check Fix-Only Mode restrictions
578
+ const projectKey = path.resolve(projectPath);
579
+ const blocked = blockedState.get(projectKey);
580
+
581
+ if (blocked?.files.length > 0) {
582
+ return {
583
+ content: [
584
+ {
585
+ type: "text",
586
+ text: JSON.stringify(
587
+ {
588
+ fixOnlyMode: true,
589
+ message:
590
+ "⚠️ Fix-Only Mode active. Fixes restricted to blocked files.",
591
+ allowedFiles: blocked.files,
592
+ scope: scope,
593
+ },
594
+ null,
595
+ 2,
596
+ ),
597
+ },
598
+ ],
599
+ };
600
+ }
601
+
602
+ let output = "# 🔧 Guardrail Fix\n\n";
603
+
604
+ try {
605
+ let cmd = `node "${path.join(dirname, "..", "bin", "guardrail.js")}" fix`;
606
+ if (plan && !apply) cmd += " --plan";
607
+ if (apply) cmd += " --apply";
608
+ if (pr) cmd += " --pr";
609
+ cmd += ` --scope=${scope}`;
610
+
611
+ const result = execSync(cmd, {
612
+ cwd: path.resolve(projectPath),
613
+ encoding: "utf8",
614
+ maxBuffer: 10 * 1024 * 1024,
615
+ });
616
+
617
+ output += result;
618
+ } catch (err) {
619
+ output += `⚠️ Fix error: ${err.message}\n`;
620
+ }
621
+
622
+ return { content: [{ type: "text", text: output }] };
623
+ }
624
+
625
+ /**
626
+ * Handle status tool
627
+ */
628
+ async function handleStatus(args, dirname) {
629
+ const { projectPath = "." } = args;
630
+ const projectKey = path.resolve(projectPath);
631
+ const blocked = blockedState.get(projectKey) || { files: [], violations: [] };
632
+
633
+ const status = {
634
+ healthy: true,
635
+ version: "2.0.0",
636
+ checkpoint: {
637
+ strictness: currentStrictness,
638
+ blockedFiles: blocked.files,
639
+ fixOnlyMode: blocked.files.length > 0,
640
+ violations: blocked.violations.length,
641
+ },
642
+ lastRun: null,
643
+ };
644
+
645
+ // Try to read last run info
646
+ try {
647
+ const summaryPath = path.join(projectPath, ".guardrail", "summary.json");
648
+ const summary = JSON.parse(await fs.readFile(summaryPath, "utf-8"));
649
+ status.lastRun = {
650
+ timestamp: summary.timestamp,
651
+ score: summary.score,
652
+ canShip: summary.canShip,
653
+ };
654
+ } catch {
655
+ // No last run
656
+ }
657
+
658
+ return {
659
+ content: [
660
+ {
661
+ type: "text",
662
+ text: JSON.stringify(status, null, 2),
663
+ },
664
+ ],
665
+ };
666
+ }
667
+
668
+ /**
669
+ * Handle set_strictness tool
670
+ */
671
+ function handleSetStrictness(args) {
672
+ const { level, projectPath = "." } = args;
673
+
674
+ if (!STRICTNESS_LEVELS.includes(level)) {
675
+ return {
676
+ isError: true,
677
+ content: [
678
+ {
679
+ type: "text",
680
+ text: `Invalid strictness level: ${level}. Valid: ${STRICTNESS_LEVELS.join(", ")}`,
681
+ },
682
+ ],
683
+ };
684
+ }
685
+
686
+ const previousLevel = currentStrictness;
687
+ currentStrictness = level;
688
+
689
+ // Clear blocked state when changing strictness (they'll need to re-check)
690
+ const projectKey = path.resolve(projectPath);
691
+ blockedState.delete(projectKey);
692
+
693
+ return {
694
+ content: [
695
+ {
696
+ type: "text",
697
+ text: JSON.stringify(
698
+ {
699
+ success: true,
700
+ previousLevel,
701
+ currentLevel: currentStrictness,
702
+ message: `Strictness changed from ${previousLevel} to ${currentStrictness}. Blocked state cleared.`,
703
+ },
704
+ null,
705
+ 2,
706
+ ),
707
+ },
708
+ ],
709
+ };
710
+ }
711
+
712
+ /**
713
+ * Route tool calls to handlers
714
+ */
715
+ async function handleGuardrail2Tool(name, args, dirname) {
716
+ switch (name) {
717
+ case "checkpoint":
718
+ return await handleCheckpoint(args, dirname);
719
+ case "check":
720
+ return await handleCheck(args, dirname);
721
+ case "ship":
722
+ return await handleShip(args, dirname);
723
+ case "fix":
724
+ return await handleFix(args, dirname);
725
+ case "status":
726
+ return await handleStatus(args, dirname);
727
+ case "set_strictness":
728
+ return handleSetStrictness(args);
729
+ default:
730
+ return {
731
+ isError: true,
732
+ content: [{ type: "text", text: `Unknown tool: ${name}` }],
733
+ };
734
+ }
735
+ }
736
+
737
+ export {
738
+ GUARDRAIL_2_TOOLS,
739
+ handleGuardrail2Tool,
740
+ handleCheckpoint,
741
+ handleCheck,
742
+ handleShip,
743
+ handleFix,
744
+ handleStatus,
745
+ handleSetStrictness,
746
+ CHECKPOINT_RULES,
747
+ STRICTNESS_LEVELS,
748
+ };