vibecheck-mcp-server 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +191 -0
- package/agent-checkpoint.js +364 -0
- package/architect-tools.js +707 -0
- package/audit-mcp.js +206 -0
- package/codebase-architect-tools.js +838 -0
- package/guardrail-2.0-tools.js +748 -0
- package/guardrail-tools.js +1075 -0
- package/hygiene-tools.js +428 -0
- package/index-v1.js +698 -0
- package/index.js +1409 -0
- package/index.old.js +4137 -0
- package/intelligence-tools.js +664 -0
- package/intent-drift-tools.js +873 -0
- package/mdc-generator.js +298 -0
- package/package.json +47 -0
- package/premium-tools.js +1275 -0
- package/test-mcp.js +108 -0
- package/test-tools.js +36 -0
- package/tier-auth.js +147 -0
|
@@ -0,0 +1,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
|
+
};
|