@vibecheckai/cli 3.0.2 → 3.0.3
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/package.json +9 -1
- package/bin/cli-hygiene.js +0 -241
- package/bin/guardrail.js +0 -834
- package/bin/runners/cli-utils.js +0 -1070
- package/bin/runners/context/ai-task-decomposer.js +0 -337
- package/bin/runners/context/analyzer.js +0 -462
- package/bin/runners/context/api-contracts.js +0 -427
- package/bin/runners/context/context-diff.js +0 -342
- package/bin/runners/context/context-pruner.js +0 -291
- package/bin/runners/context/dependency-graph.js +0 -414
- package/bin/runners/context/generators/claude.js +0 -107
- package/bin/runners/context/generators/codex.js +0 -108
- package/bin/runners/context/generators/copilot.js +0 -119
- package/bin/runners/context/generators/cursor.js +0 -514
- package/bin/runners/context/generators/mcp.js +0 -151
- package/bin/runners/context/generators/windsurf.js +0 -180
- package/bin/runners/context/git-context.js +0 -302
- package/bin/runners/context/index.js +0 -1042
- package/bin/runners/context/insights.js +0 -173
- package/bin/runners/context/mcp-server/generate-rules.js +0 -337
- package/bin/runners/context/mcp-server/index.js +0 -1176
- package/bin/runners/context/mcp-server/package.json +0 -24
- package/bin/runners/context/memory.js +0 -200
- package/bin/runners/context/monorepo.js +0 -215
- package/bin/runners/context/multi-repo-federation.js +0 -404
- package/bin/runners/context/patterns.js +0 -253
- package/bin/runners/context/proof-context.js +0 -972
- package/bin/runners/context/security-scanner.js +0 -303
- package/bin/runners/context/semantic-search.js +0 -350
- package/bin/runners/context/shared.js +0 -264
- package/bin/runners/context/team-conventions.js +0 -310
- package/bin/runners/lib/ai-bridge.js +0 -416
- package/bin/runners/lib/analysis-core.js +0 -271
- package/bin/runners/lib/analyzers.js +0 -541
- package/bin/runners/lib/audit-bridge.js +0 -391
- package/bin/runners/lib/auth-truth.js +0 -193
- package/bin/runners/lib/auth.js +0 -215
- package/bin/runners/lib/backup.js +0 -62
- package/bin/runners/lib/billing.js +0 -107
- package/bin/runners/lib/claims.js +0 -118
- package/bin/runners/lib/cli-ui.js +0 -540
- package/bin/runners/lib/compliance-bridge-new.js +0 -0
- package/bin/runners/lib/compliance-bridge.js +0 -165
- package/bin/runners/lib/contracts/auth-contract.js +0 -194
- package/bin/runners/lib/contracts/env-contract.js +0 -178
- package/bin/runners/lib/contracts/external-contract.js +0 -198
- package/bin/runners/lib/contracts/guard.js +0 -168
- package/bin/runners/lib/contracts/index.js +0 -89
- package/bin/runners/lib/contracts/plan-validator.js +0 -311
- package/bin/runners/lib/contracts/route-contract.js +0 -192
- package/bin/runners/lib/detect.js +0 -89
- package/bin/runners/lib/doctor/autofix.js +0 -254
- package/bin/runners/lib/doctor/index.js +0 -37
- package/bin/runners/lib/doctor/modules/dependencies.js +0 -325
- package/bin/runners/lib/doctor/modules/index.js +0 -46
- package/bin/runners/lib/doctor/modules/network.js +0 -250
- package/bin/runners/lib/doctor/modules/project.js +0 -312
- package/bin/runners/lib/doctor/modules/runtime.js +0 -224
- package/bin/runners/lib/doctor/modules/security.js +0 -348
- package/bin/runners/lib/doctor/modules/system.js +0 -213
- package/bin/runners/lib/doctor/modules/vibecheck.js +0 -394
- package/bin/runners/lib/doctor/reporter.js +0 -262
- package/bin/runners/lib/doctor/service.js +0 -262
- package/bin/runners/lib/doctor/types.js +0 -113
- package/bin/runners/lib/doctor/ui.js +0 -263
- package/bin/runners/lib/doctor-enhanced.js +0 -233
- package/bin/runners/lib/doctor-v2.js +0 -608
- package/bin/runners/lib/enforcement.js +0 -72
|
@@ -1,1176 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* vibecheck Context Engine - MCP Server
|
|
4
|
-
*
|
|
5
|
-
* A repo-installed Context OS that exposes verified repo facts + patterns + constraints
|
|
6
|
-
* as tool calls, so Cursor/Windsurf/Copilot can't "invent reality".
|
|
7
|
-
*
|
|
8
|
-
* Commands:
|
|
9
|
-
* index - Build truth pack (symbols/graphs/patterns)
|
|
10
|
-
* serve - Start MCP tool server
|
|
11
|
-
* verify - Run verification gates
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const fs = require("fs");
|
|
15
|
-
const path = require("path");
|
|
16
|
-
const http = require("http");
|
|
17
|
-
|
|
18
|
-
// MCP Protocol version
|
|
19
|
-
const MCP_VERSION = "2024-11-05";
|
|
20
|
-
|
|
21
|
-
class ContextEngine {
|
|
22
|
-
constructor(projectPath) {
|
|
23
|
-
this.projectPath = projectPath;
|
|
24
|
-
this.truthPack = null;
|
|
25
|
-
this.indexed = false;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Index the project and build the truth pack
|
|
30
|
-
*/
|
|
31
|
-
async index() {
|
|
32
|
-
console.log("📦 Building truth pack...");
|
|
33
|
-
|
|
34
|
-
const startTime = Date.now();
|
|
35
|
-
|
|
36
|
-
this.truthPack = {
|
|
37
|
-
version: "1.0.0",
|
|
38
|
-
indexedAt: new Date().toISOString(),
|
|
39
|
-
projectPath: this.projectPath,
|
|
40
|
-
|
|
41
|
-
// Symbol index
|
|
42
|
-
symbols: this.buildSymbolIndex(),
|
|
43
|
-
|
|
44
|
-
// Route map
|
|
45
|
-
routes: this.buildRouteMap(),
|
|
46
|
-
|
|
47
|
-
// Component graph
|
|
48
|
-
components: this.buildComponentGraph(),
|
|
49
|
-
|
|
50
|
-
// Dependency versions
|
|
51
|
-
versions: this.buildVersionTruth(),
|
|
52
|
-
|
|
53
|
-
// Security/auth map
|
|
54
|
-
security: this.buildSecurityMap(),
|
|
55
|
-
|
|
56
|
-
// Test obligations
|
|
57
|
-
tests: this.buildTestObligations(),
|
|
58
|
-
|
|
59
|
-
// Golden patterns
|
|
60
|
-
patterns: this.buildGoldenPatterns(),
|
|
61
|
-
|
|
62
|
-
// File importance/risk
|
|
63
|
-
riskMap: this.buildRiskMap(),
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
// Save truth pack
|
|
67
|
-
const vibecheckDir = path.join(this.projectPath, ".vibecheck");
|
|
68
|
-
if (!fs.existsSync(vibecheckDir)) {
|
|
69
|
-
fs.mkdirSync(vibecheckDir, { recursive: true });
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
fs.writeFileSync(
|
|
73
|
-
path.join(vibecheckDir, "truth-pack.json"),
|
|
74
|
-
JSON.stringify(this.truthPack, null, 2)
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
this.indexed = true;
|
|
78
|
-
const duration = Date.now() - startTime;
|
|
79
|
-
|
|
80
|
-
console.log(`✅ Truth pack built in ${duration}ms`);
|
|
81
|
-
console.log(` Symbols: ${Object.keys(this.truthPack.symbols.exports).length}`);
|
|
82
|
-
console.log(` Routes: ${this.truthPack.routes.endpoints.length}`);
|
|
83
|
-
console.log(` Packages: ${Object.keys(this.truthPack.versions.installed).length}`);
|
|
84
|
-
|
|
85
|
-
return this.truthPack;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Build symbol index (what exists in the codebase)
|
|
90
|
-
*/
|
|
91
|
-
buildSymbolIndex() {
|
|
92
|
-
const symbols = {
|
|
93
|
-
exports: {}, // name -> { file, line, type }
|
|
94
|
-
imports: {}, // package -> [files using it]
|
|
95
|
-
functions: [], // all function names
|
|
96
|
-
components: [], // React components
|
|
97
|
-
types: [], // TypeScript types/interfaces
|
|
98
|
-
hooks: [], // Custom hooks
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
const files = this.findSourceFiles([".ts", ".tsx", ".js", ".jsx"]);
|
|
102
|
-
|
|
103
|
-
for (const file of files) {
|
|
104
|
-
try {
|
|
105
|
-
const content = fs.readFileSync(file, "utf-8");
|
|
106
|
-
const lines = content.split("\n");
|
|
107
|
-
const relativePath = path.relative(this.projectPath, file);
|
|
108
|
-
|
|
109
|
-
lines.forEach((line, idx) => {
|
|
110
|
-
// Exported functions/consts
|
|
111
|
-
const exportMatch = line.match(/export\s+(?:const|function|class|type|interface)\s+(\w+)/);
|
|
112
|
-
if (exportMatch) {
|
|
113
|
-
symbols.exports[exportMatch[1]] = {
|
|
114
|
-
file: relativePath,
|
|
115
|
-
line: idx + 1,
|
|
116
|
-
type: line.includes("function") ? "function" :
|
|
117
|
-
line.includes("class") ? "class" :
|
|
118
|
-
line.includes("type") ? "type" :
|
|
119
|
-
line.includes("interface") ? "interface" : "const"
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
if (exportMatch[1].startsWith("use")) {
|
|
123
|
-
symbols.hooks.push(exportMatch[1]);
|
|
124
|
-
}
|
|
125
|
-
if (/^[A-Z]/.test(exportMatch[1]) && file.endsWith(".tsx")) {
|
|
126
|
-
symbols.components.push(exportMatch[1]);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Imports from packages
|
|
131
|
-
const importMatch = line.match(/import\s+.*?\s+from\s+['"]([^./][^'"]+)['"]/);
|
|
132
|
-
if (importMatch) {
|
|
133
|
-
const pkg = importMatch[1].startsWith("@")
|
|
134
|
-
? importMatch[1].split("/").slice(0, 2).join("/")
|
|
135
|
-
: importMatch[1].split("/")[0];
|
|
136
|
-
|
|
137
|
-
if (!symbols.imports[pkg]) {
|
|
138
|
-
symbols.imports[pkg] = [];
|
|
139
|
-
}
|
|
140
|
-
if (!symbols.imports[pkg].includes(relativePath)) {
|
|
141
|
-
symbols.imports[pkg].push(relativePath);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
} catch {}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return symbols;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Build route map (API endpoints)
|
|
153
|
-
*/
|
|
154
|
-
buildRouteMap() {
|
|
155
|
-
const routes = {
|
|
156
|
-
endpoints: [],
|
|
157
|
-
files: [],
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
const routePatterns = [
|
|
161
|
-
/app\.(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
|
162
|
-
/router\.(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
|
163
|
-
];
|
|
164
|
-
|
|
165
|
-
const files = this.findSourceFiles([".ts", ".js"]);
|
|
166
|
-
|
|
167
|
-
for (const file of files) {
|
|
168
|
-
if (!file.includes("route") && !file.includes("api")) continue;
|
|
169
|
-
|
|
170
|
-
try {
|
|
171
|
-
const content = fs.readFileSync(file, "utf-8");
|
|
172
|
-
const lines = content.split("\n");
|
|
173
|
-
const relativePath = path.relative(this.projectPath, file);
|
|
174
|
-
let hasRoutes = false;
|
|
175
|
-
|
|
176
|
-
lines.forEach((line, idx) => {
|
|
177
|
-
for (const pattern of routePatterns) {
|
|
178
|
-
pattern.lastIndex = 0;
|
|
179
|
-
let match;
|
|
180
|
-
while ((match = pattern.exec(line)) !== null) {
|
|
181
|
-
routes.endpoints.push({
|
|
182
|
-
method: match[1].toUpperCase(),
|
|
183
|
-
path: match[2],
|
|
184
|
-
file: relativePath,
|
|
185
|
-
line: idx + 1
|
|
186
|
-
});
|
|
187
|
-
hasRoutes = true;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
if (hasRoutes) {
|
|
193
|
-
routes.files.push(relativePath);
|
|
194
|
-
}
|
|
195
|
-
} catch {}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return routes;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Build component graph
|
|
203
|
-
*/
|
|
204
|
-
buildComponentGraph() {
|
|
205
|
-
const graph = {
|
|
206
|
-
components: {},
|
|
207
|
-
importedBy: {},
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
const files = this.findSourceFiles([".tsx"]);
|
|
211
|
-
|
|
212
|
-
for (const file of files) {
|
|
213
|
-
try {
|
|
214
|
-
const content = fs.readFileSync(file, "utf-8");
|
|
215
|
-
const relativePath = path.relative(this.projectPath, file);
|
|
216
|
-
|
|
217
|
-
// Find component definitions
|
|
218
|
-
const componentMatch = content.match(/(?:export\s+)?(?:default\s+)?function\s+([A-Z]\w+)/);
|
|
219
|
-
if (componentMatch) {
|
|
220
|
-
const name = componentMatch[1];
|
|
221
|
-
graph.components[name] = {
|
|
222
|
-
file: relativePath,
|
|
223
|
-
imports: [],
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
// Find what this component imports
|
|
227
|
-
const imports = content.matchAll(/import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/g);
|
|
228
|
-
for (const imp of imports) {
|
|
229
|
-
const names = imp[1].split(",").map(n => n.trim());
|
|
230
|
-
graph.components[name].imports.push(...names);
|
|
231
|
-
|
|
232
|
-
// Track reverse dependency
|
|
233
|
-
for (const importedName of names) {
|
|
234
|
-
if (!graph.importedBy[importedName]) {
|
|
235
|
-
graph.importedBy[importedName] = [];
|
|
236
|
-
}
|
|
237
|
-
graph.importedBy[importedName].push(name);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
} catch {}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return graph;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Build version truth (installed packages)
|
|
249
|
-
*/
|
|
250
|
-
buildVersionTruth() {
|
|
251
|
-
const versions = {
|
|
252
|
-
installed: {},
|
|
253
|
-
devDependencies: {},
|
|
254
|
-
peerDependencies: {},
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
// Check multiple possible package.json locations
|
|
258
|
-
const pkgPaths = [
|
|
259
|
-
path.join(this.projectPath, "package.json"),
|
|
260
|
-
path.join(this.projectPath, "client", "package.json"),
|
|
261
|
-
path.join(this.projectPath, "server", "package.json"),
|
|
262
|
-
];
|
|
263
|
-
|
|
264
|
-
for (const pkgPath of pkgPaths) {
|
|
265
|
-
if (fs.existsSync(pkgPath)) {
|
|
266
|
-
try {
|
|
267
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
268
|
-
Object.assign(versions.installed, pkg.dependencies || {});
|
|
269
|
-
Object.assign(versions.devDependencies, pkg.devDependencies || {});
|
|
270
|
-
Object.assign(versions.peerDependencies, pkg.peerDependencies || {});
|
|
271
|
-
} catch {}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return versions;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Build security/auth map
|
|
280
|
-
*/
|
|
281
|
-
buildSecurityMap() {
|
|
282
|
-
const security = {
|
|
283
|
-
authFiles: [],
|
|
284
|
-
middlewareFiles: [],
|
|
285
|
-
protectedRoutes: [],
|
|
286
|
-
envSecrets: [],
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
const files = this.findSourceFiles([".ts", ".js"]);
|
|
290
|
-
|
|
291
|
-
for (const file of files) {
|
|
292
|
-
const relativePath = path.relative(this.projectPath, file);
|
|
293
|
-
const lowerPath = relativePath.toLowerCase();
|
|
294
|
-
|
|
295
|
-
if (lowerPath.includes("auth") || lowerPath.includes("login") || lowerPath.includes("session")) {
|
|
296
|
-
security.authFiles.push(relativePath);
|
|
297
|
-
}
|
|
298
|
-
if (lowerPath.includes("middleware")) {
|
|
299
|
-
security.middlewareFiles.push(relativePath);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
try {
|
|
303
|
-
const content = fs.readFileSync(file, "utf-8");
|
|
304
|
-
|
|
305
|
-
// Check for protected route patterns
|
|
306
|
-
if (content.includes("requireAuth") || content.includes("isAuthenticated") || content.includes("protect")) {
|
|
307
|
-
security.protectedRoutes.push(relativePath);
|
|
308
|
-
}
|
|
309
|
-
} catch {}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Check for secret env vars
|
|
313
|
-
const envFiles = [".env", ".env.local", ".env.example"];
|
|
314
|
-
for (const envFile of envFiles) {
|
|
315
|
-
const envPath = path.join(this.projectPath, envFile);
|
|
316
|
-
if (fs.existsSync(envPath)) {
|
|
317
|
-
try {
|
|
318
|
-
const content = fs.readFileSync(envPath, "utf-8");
|
|
319
|
-
const secretPatterns = /^(.*(?:SECRET|KEY|TOKEN|PASSWORD|API_KEY).*)=/gim;
|
|
320
|
-
let match;
|
|
321
|
-
while ((match = secretPatterns.exec(content)) !== null) {
|
|
322
|
-
security.envSecrets.push(match[1].split("=")[0]);
|
|
323
|
-
}
|
|
324
|
-
} catch {}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
return security;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Build test obligations
|
|
333
|
-
*/
|
|
334
|
-
buildTestObligations() {
|
|
335
|
-
const tests = {
|
|
336
|
-
framework: null,
|
|
337
|
-
testFiles: [],
|
|
338
|
-
setupFiles: [],
|
|
339
|
-
coverageRequired: [],
|
|
340
|
-
};
|
|
341
|
-
|
|
342
|
-
// Detect test framework
|
|
343
|
-
const versions = this.truthPack?.versions?.installed || {};
|
|
344
|
-
if (versions["vitest"]) tests.framework = "vitest";
|
|
345
|
-
else if (versions["jest"]) tests.framework = "jest";
|
|
346
|
-
else if (versions["mocha"]) tests.framework = "mocha";
|
|
347
|
-
|
|
348
|
-
// Find test files
|
|
349
|
-
const files = this.findSourceFiles([".test.ts", ".test.tsx", ".spec.ts", ".spec.tsx", ".test.js", ".spec.js"]);
|
|
350
|
-
tests.testFiles = files.map(f => path.relative(this.projectPath, f));
|
|
351
|
-
|
|
352
|
-
// Find setup files
|
|
353
|
-
const setupPatterns = ["setup", "setupTests", "test-setup", "vitest.setup", "jest.setup"];
|
|
354
|
-
const allFiles = this.findSourceFiles([".ts", ".js"]);
|
|
355
|
-
for (const file of allFiles) {
|
|
356
|
-
const basename = path.basename(file, path.extname(file));
|
|
357
|
-
if (setupPatterns.some(p => basename.includes(p))) {
|
|
358
|
-
tests.setupFiles.push(path.relative(this.projectPath, file));
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
return tests;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Build golden patterns
|
|
367
|
-
*/
|
|
368
|
-
buildGoldenPatterns() {
|
|
369
|
-
const patterns = {};
|
|
370
|
-
const files = this.findSourceFiles([".ts", ".tsx", ".js", ".jsx"]);
|
|
371
|
-
|
|
372
|
-
// Find API route pattern
|
|
373
|
-
for (const file of files) {
|
|
374
|
-
if (!file.includes("route")) continue;
|
|
375
|
-
try {
|
|
376
|
-
const content = fs.readFileSync(file, "utf-8");
|
|
377
|
-
const match = content.match(/router\.(get|post|put|delete)\s*\([^)]+\)\s*(?:async\s*)?\([^)]*\)\s*=>\s*\{[\s\S]{50,400}?\}/);
|
|
378
|
-
if (match && !patterns["api-route"]) {
|
|
379
|
-
patterns["api-route"] = {
|
|
380
|
-
file: path.relative(this.projectPath, file),
|
|
381
|
-
code: match[0].substring(0, 300),
|
|
382
|
-
description: "Standard API route handler pattern"
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
} catch {}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// Find component pattern
|
|
389
|
-
for (const file of files) {
|
|
390
|
-
if (!file.endsWith(".tsx")) continue;
|
|
391
|
-
try {
|
|
392
|
-
const content = fs.readFileSync(file, "utf-8");
|
|
393
|
-
const match = content.match(/export\s+(?:default\s+)?function\s+([A-Z]\w+)[^{]*\{[\s\S]{50,300}?return\s*\(/);
|
|
394
|
-
if (match && !patterns["component"]) {
|
|
395
|
-
patterns["component"] = {
|
|
396
|
-
file: path.relative(this.projectPath, file),
|
|
397
|
-
code: match[0].substring(0, 250),
|
|
398
|
-
description: "Standard React component pattern"
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
} catch {}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// Find hook pattern
|
|
405
|
-
for (const file of files) {
|
|
406
|
-
if (!file.includes("hook")) continue;
|
|
407
|
-
try {
|
|
408
|
-
const content = fs.readFileSync(file, "utf-8");
|
|
409
|
-
const match = content.match(/export\s+function\s+(use\w+)[^{]*\{[\s\S]{50,300}?\}/);
|
|
410
|
-
if (match && !patterns["hook"]) {
|
|
411
|
-
patterns["hook"] = {
|
|
412
|
-
file: path.relative(this.projectPath, file),
|
|
413
|
-
code: match[0].substring(0, 250),
|
|
414
|
-
description: "Standard custom hook pattern"
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
} catch {}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
return patterns;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* Build risk map
|
|
425
|
-
*/
|
|
426
|
-
buildRiskMap() {
|
|
427
|
-
const riskMap = {
|
|
428
|
-
critical: [],
|
|
429
|
-
high: [],
|
|
430
|
-
medium: [],
|
|
431
|
-
};
|
|
432
|
-
|
|
433
|
-
const files = this.findSourceFiles([".ts", ".tsx", ".js", ".jsx"]);
|
|
434
|
-
|
|
435
|
-
for (const file of files) {
|
|
436
|
-
const relativePath = path.relative(this.projectPath, file);
|
|
437
|
-
const lowerPath = relativePath.toLowerCase();
|
|
438
|
-
|
|
439
|
-
// Critical: auth, schema, config
|
|
440
|
-
if (lowerPath.includes("schema") || lowerPath.includes("auth") ||
|
|
441
|
-
lowerPath.includes("config") || lowerPath.includes("middleware")) {
|
|
442
|
-
riskMap.critical.push(relativePath);
|
|
443
|
-
}
|
|
444
|
-
// High: routes, api, payments
|
|
445
|
-
else if (lowerPath.includes("route") || lowerPath.includes("api") ||
|
|
446
|
-
lowerPath.includes("payment") || lowerPath.includes("billing")) {
|
|
447
|
-
riskMap.high.push(relativePath);
|
|
448
|
-
}
|
|
449
|
-
// Medium: components, hooks
|
|
450
|
-
else if (lowerPath.includes("component") || lowerPath.includes("hook")) {
|
|
451
|
-
riskMap.medium.push(relativePath);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
return riskMap;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
/**
|
|
459
|
-
* Find source files
|
|
460
|
-
*/
|
|
461
|
-
findSourceFiles(extensions, maxDepth = 5) {
|
|
462
|
-
const results = [];
|
|
463
|
-
|
|
464
|
-
const walk = (dir, depth = 0) => {
|
|
465
|
-
if (depth >= maxDepth) return;
|
|
466
|
-
|
|
467
|
-
try {
|
|
468
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
469
|
-
for (const entry of entries) {
|
|
470
|
-
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist") {
|
|
471
|
-
continue;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
const fullPath = path.join(dir, entry.name);
|
|
475
|
-
if (entry.isDirectory()) {
|
|
476
|
-
walk(fullPath, depth + 1);
|
|
477
|
-
} else if (extensions.some(ext => entry.name.endsWith(ext))) {
|
|
478
|
-
results.push(fullPath);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
} catch {}
|
|
482
|
-
};
|
|
483
|
-
|
|
484
|
-
walk(this.projectPath);
|
|
485
|
-
return results;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
/**
|
|
489
|
-
* Load existing truth pack
|
|
490
|
-
*/
|
|
491
|
-
loadTruthPack() {
|
|
492
|
-
const packPath = path.join(this.projectPath, ".vibecheck", "truth-pack.json");
|
|
493
|
-
if (fs.existsSync(packPath)) {
|
|
494
|
-
try {
|
|
495
|
-
this.truthPack = JSON.parse(fs.readFileSync(packPath, "utf-8"));
|
|
496
|
-
this.indexed = true;
|
|
497
|
-
return true;
|
|
498
|
-
} catch {}
|
|
499
|
-
}
|
|
500
|
-
return false;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// ========== MCP TOOL IMPLEMENTATIONS ==========
|
|
504
|
-
|
|
505
|
-
/**
|
|
506
|
-
* repo.map() - Get project architecture and boundaries
|
|
507
|
-
*/
|
|
508
|
-
repoMap() {
|
|
509
|
-
if (!this.truthPack) this.loadTruthPack();
|
|
510
|
-
if (!this.truthPack) return { error: "Not indexed. Run 'index' first." };
|
|
511
|
-
|
|
512
|
-
return {
|
|
513
|
-
projectPath: this.projectPath,
|
|
514
|
-
indexedAt: this.truthPack.indexedAt,
|
|
515
|
-
stats: {
|
|
516
|
-
symbols: Object.keys(this.truthPack.symbols.exports).length,
|
|
517
|
-
routes: this.truthPack.routes.endpoints.length,
|
|
518
|
-
components: this.truthPack.symbols.components.length,
|
|
519
|
-
packages: Object.keys(this.truthPack.versions.installed).length,
|
|
520
|
-
},
|
|
521
|
-
boundaries: {
|
|
522
|
-
routeFiles: this.truthPack.routes.files,
|
|
523
|
-
authFiles: this.truthPack.security.authFiles,
|
|
524
|
-
middlewareFiles: this.truthPack.security.middlewareFiles,
|
|
525
|
-
},
|
|
526
|
-
riskMap: this.truthPack.riskMap,
|
|
527
|
-
};
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* symbols.exists(name) - Check if a symbol exists
|
|
532
|
-
*/
|
|
533
|
-
symbolsExists(name) {
|
|
534
|
-
if (!this.truthPack) this.loadTruthPack();
|
|
535
|
-
if (!this.truthPack) return { error: "Not indexed" };
|
|
536
|
-
|
|
537
|
-
const symbol = this.truthPack.symbols.exports[name];
|
|
538
|
-
if (symbol) {
|
|
539
|
-
return {
|
|
540
|
-
exists: true,
|
|
541
|
-
name,
|
|
542
|
-
file: symbol.file,
|
|
543
|
-
line: symbol.line,
|
|
544
|
-
type: symbol.type,
|
|
545
|
-
proof: `${symbol.file}:${symbol.line}`
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
return {
|
|
550
|
-
exists: false,
|
|
551
|
-
name,
|
|
552
|
-
suggestion: "Symbol not found. Do not invent it."
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
/**
|
|
557
|
-
* versions.allowed(package) - Check if package is installed
|
|
558
|
-
*/
|
|
559
|
-
versionsAllowed(packageName) {
|
|
560
|
-
if (!this.truthPack) this.loadTruthPack();
|
|
561
|
-
if (!this.truthPack) return { error: "Not indexed" };
|
|
562
|
-
|
|
563
|
-
const version = this.truthPack.versions.installed[packageName] ||
|
|
564
|
-
this.truthPack.versions.devDependencies[packageName];
|
|
565
|
-
|
|
566
|
-
if (version) {
|
|
567
|
-
return {
|
|
568
|
-
allowed: true,
|
|
569
|
-
package: packageName,
|
|
570
|
-
version,
|
|
571
|
-
proof: "package.json"
|
|
572
|
-
};
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
return {
|
|
576
|
-
allowed: false,
|
|
577
|
-
package: packageName,
|
|
578
|
-
suggestion: "Package not installed. Do not suggest using it."
|
|
579
|
-
};
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
/**
|
|
583
|
-
* routes.exists(method, path) - Check if route exists
|
|
584
|
-
*/
|
|
585
|
-
routesExists(method, routePath) {
|
|
586
|
-
if (!this.truthPack) this.loadTruthPack();
|
|
587
|
-
if (!this.truthPack) return { error: "Not indexed" };
|
|
588
|
-
|
|
589
|
-
const route = this.truthPack.routes.endpoints.find(
|
|
590
|
-
r => r.method === method.toUpperCase() && r.path === routePath
|
|
591
|
-
);
|
|
592
|
-
|
|
593
|
-
if (route) {
|
|
594
|
-
return {
|
|
595
|
-
exists: true,
|
|
596
|
-
method: route.method,
|
|
597
|
-
path: route.path,
|
|
598
|
-
file: route.file,
|
|
599
|
-
line: route.line,
|
|
600
|
-
proof: `${route.file}:${route.line}`
|
|
601
|
-
};
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
return {
|
|
605
|
-
exists: false,
|
|
606
|
-
method,
|
|
607
|
-
path: routePath,
|
|
608
|
-
suggestion: "Route not found. Do not invent it."
|
|
609
|
-
};
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
/**
|
|
613
|
-
* patterns.get(type) - Get golden pattern
|
|
614
|
-
*/
|
|
615
|
-
patternsGet(type) {
|
|
616
|
-
if (!this.truthPack) this.loadTruthPack();
|
|
617
|
-
if (!this.truthPack) return { error: "Not indexed" };
|
|
618
|
-
|
|
619
|
-
const pattern = this.truthPack.patterns[type];
|
|
620
|
-
if (pattern) {
|
|
621
|
-
return {
|
|
622
|
-
found: true,
|
|
623
|
-
type,
|
|
624
|
-
file: pattern.file,
|
|
625
|
-
description: pattern.description,
|
|
626
|
-
code: pattern.code
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
return {
|
|
631
|
-
found: false,
|
|
632
|
-
type,
|
|
633
|
-
available: Object.keys(this.truthPack.patterns)
|
|
634
|
-
};
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
/**
|
|
638
|
-
* security.authFlow() - Get auth flow info
|
|
639
|
-
*/
|
|
640
|
-
securityAuthFlow() {
|
|
641
|
-
if (!this.truthPack) this.loadTruthPack();
|
|
642
|
-
if (!this.truthPack) return { error: "Not indexed" };
|
|
643
|
-
|
|
644
|
-
return {
|
|
645
|
-
authFiles: this.truthPack.security.authFiles,
|
|
646
|
-
middlewareFiles: this.truthPack.security.middlewareFiles,
|
|
647
|
-
protectedRoutes: this.truthPack.security.protectedRoutes,
|
|
648
|
-
envSecrets: this.truthPack.security.envSecrets,
|
|
649
|
-
warning: "Do not bypass auth without explicit approval"
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
/**
|
|
654
|
-
* tests.required(files) - Get required tests for changed files
|
|
655
|
-
*/
|
|
656
|
-
testsRequired(changedFiles) {
|
|
657
|
-
if (!this.truthPack) this.loadTruthPack();
|
|
658
|
-
if (!this.truthPack) return { error: "Not indexed" };
|
|
659
|
-
|
|
660
|
-
const required = [];
|
|
661
|
-
|
|
662
|
-
for (const file of changedFiles) {
|
|
663
|
-
// Find corresponding test file
|
|
664
|
-
const baseName = path.basename(file, path.extname(file));
|
|
665
|
-
const testFile = this.truthPack.tests.testFiles.find(
|
|
666
|
-
t => t.includes(baseName) && (t.includes(".test.") || t.includes(".spec."))
|
|
667
|
-
);
|
|
668
|
-
|
|
669
|
-
if (testFile) {
|
|
670
|
-
required.push({
|
|
671
|
-
sourceFile: file,
|
|
672
|
-
testFile,
|
|
673
|
-
mustRun: true
|
|
674
|
-
});
|
|
675
|
-
} else {
|
|
676
|
-
required.push({
|
|
677
|
-
sourceFile: file,
|
|
678
|
-
testFile: null,
|
|
679
|
-
mustRun: false,
|
|
680
|
-
warning: "No test file found - consider adding tests"
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
return {
|
|
686
|
-
framework: this.truthPack.tests.framework,
|
|
687
|
-
required,
|
|
688
|
-
setupFiles: this.truthPack.tests.setupFiles
|
|
689
|
-
};
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
/**
|
|
693
|
-
* risk.blastRadius(files) - Compute blast radius for changes
|
|
694
|
-
*/
|
|
695
|
-
riskBlastRadius(files) {
|
|
696
|
-
if (!this.truthPack) this.loadTruthPack();
|
|
697
|
-
if (!this.truthPack) return { error: "Not indexed" };
|
|
698
|
-
|
|
699
|
-
const analysis = {
|
|
700
|
-
files: [],
|
|
701
|
-
overallRisk: "low",
|
|
702
|
-
warnings: [],
|
|
703
|
-
requiresApproval: false
|
|
704
|
-
};
|
|
705
|
-
|
|
706
|
-
for (const file of files) {
|
|
707
|
-
let risk = "low";
|
|
708
|
-
|
|
709
|
-
if (this.truthPack.riskMap.critical.some(f => file.includes(f) || f.includes(file))) {
|
|
710
|
-
risk = "critical";
|
|
711
|
-
analysis.warnings.push(`CRITICAL: ${file} is a critical file`);
|
|
712
|
-
analysis.requiresApproval = true;
|
|
713
|
-
} else if (this.truthPack.riskMap.high.some(f => file.includes(f) || f.includes(file))) {
|
|
714
|
-
risk = "high";
|
|
715
|
-
analysis.warnings.push(`HIGH: ${file} is a high-risk file`);
|
|
716
|
-
} else if (this.truthPack.riskMap.medium.some(f => file.includes(f) || f.includes(file))) {
|
|
717
|
-
risk = "medium";
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
// Check if file is imported by many others
|
|
721
|
-
const importedBy = this.truthPack.components.importedBy || {};
|
|
722
|
-
const baseName = path.basename(file, path.extname(file));
|
|
723
|
-
if (importedBy[baseName]?.length > 5) {
|
|
724
|
-
analysis.warnings.push(`HIGH IMPACT: ${file} is imported by ${importedBy[baseName].length} files`);
|
|
725
|
-
risk = risk === "low" ? "medium" : risk;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
analysis.files.push({ file, risk });
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
// Overall risk
|
|
732
|
-
if (analysis.files.some(f => f.risk === "critical")) {
|
|
733
|
-
analysis.overallRisk = "critical";
|
|
734
|
-
} else if (analysis.files.some(f => f.risk === "high")) {
|
|
735
|
-
analysis.overallRisk = "high";
|
|
736
|
-
} else if (analysis.files.some(f => f.risk === "medium")) {
|
|
737
|
-
analysis.overallRisk = "medium";
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
return analysis;
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
/**
|
|
744
|
-
* graph.related(file) - Get related files
|
|
745
|
-
*/
|
|
746
|
-
graphRelated(file) {
|
|
747
|
-
if (!this.truthPack) this.loadTruthPack();
|
|
748
|
-
if (!this.truthPack) return { error: "Not indexed" };
|
|
749
|
-
|
|
750
|
-
const related = {
|
|
751
|
-
imports: [],
|
|
752
|
-
importedBy: [],
|
|
753
|
-
sameDirectory: [],
|
|
754
|
-
};
|
|
755
|
-
|
|
756
|
-
const baseName = path.basename(file, path.extname(file));
|
|
757
|
-
const dirName = path.dirname(file);
|
|
758
|
-
|
|
759
|
-
// Find what this file imports (from component graph)
|
|
760
|
-
const component = Object.entries(this.truthPack.components.components || {})
|
|
761
|
-
.find(([name, data]) => data.file.includes(baseName));
|
|
762
|
-
|
|
763
|
-
if (component) {
|
|
764
|
-
related.imports = component[1].imports;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
// Find what imports this file
|
|
768
|
-
related.importedBy = this.truthPack.components.importedBy[baseName] || [];
|
|
769
|
-
|
|
770
|
-
// Find files in same directory
|
|
771
|
-
const allExports = Object.values(this.truthPack.symbols.exports);
|
|
772
|
-
related.sameDirectory = allExports
|
|
773
|
-
.filter(exp => exp.file.includes(dirName) && !exp.file.includes(baseName))
|
|
774
|
-
.map(exp => exp.file)
|
|
775
|
-
.filter((v, i, a) => a.indexOf(v) === i)
|
|
776
|
-
.slice(0, 10);
|
|
777
|
-
|
|
778
|
-
return related;
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
// ========== VERIFICATION GATES ==========
|
|
782
|
-
|
|
783
|
-
/**
|
|
784
|
-
* Verify a proposed change
|
|
785
|
-
*/
|
|
786
|
-
verify(changes) {
|
|
787
|
-
const results = {
|
|
788
|
-
passed: true,
|
|
789
|
-
checks: [],
|
|
790
|
-
};
|
|
791
|
-
|
|
792
|
-
// Symbol reality check
|
|
793
|
-
for (const change of changes.newSymbols || []) {
|
|
794
|
-
const exists = this.symbolsExists(change);
|
|
795
|
-
if (!exists.exists) {
|
|
796
|
-
results.passed = false;
|
|
797
|
-
results.checks.push({
|
|
798
|
-
type: "symbol_reality",
|
|
799
|
-
passed: false,
|
|
800
|
-
message: `Symbol "${change}" does not exist`
|
|
801
|
-
});
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
// Version check
|
|
806
|
-
for (const pkg of changes.newPackages || []) {
|
|
807
|
-
const allowed = this.versionsAllowed(pkg);
|
|
808
|
-
if (!allowed.allowed) {
|
|
809
|
-
results.passed = false;
|
|
810
|
-
results.checks.push({
|
|
811
|
-
type: "version_constraint",
|
|
812
|
-
passed: false,
|
|
813
|
-
message: `Package "${pkg}" is not installed`
|
|
814
|
-
});
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
// Blast radius check
|
|
819
|
-
if (changes.files?.length > 0) {
|
|
820
|
-
const blast = this.riskBlastRadius(changes.files);
|
|
821
|
-
if (blast.requiresApproval) {
|
|
822
|
-
results.checks.push({
|
|
823
|
-
type: "blast_radius",
|
|
824
|
-
passed: false,
|
|
825
|
-
message: "Changes touch critical files - requires approval",
|
|
826
|
-
warnings: blast.warnings
|
|
827
|
-
});
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
return results;
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
// ========== MCP SERVER ==========
|
|
836
|
-
|
|
837
|
-
class MCPServer {
|
|
838
|
-
constructor(engine) {
|
|
839
|
-
this.engine = engine;
|
|
840
|
-
this.tools = this.defineMCPTools();
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
defineMCPTools() {
|
|
844
|
-
return [
|
|
845
|
-
{
|
|
846
|
-
name: "repo_map",
|
|
847
|
-
description: "Get project architecture, boundaries, and risk map. Call this FIRST before planning any changes.",
|
|
848
|
-
inputSchema: {
|
|
849
|
-
type: "object",
|
|
850
|
-
properties: {},
|
|
851
|
-
required: []
|
|
852
|
-
}
|
|
853
|
-
},
|
|
854
|
-
{
|
|
855
|
-
name: "symbols_exists",
|
|
856
|
-
description: "Check if a symbol (function, class, component, hook) exists in the codebase. MUST call before using any symbol.",
|
|
857
|
-
inputSchema: {
|
|
858
|
-
type: "object",
|
|
859
|
-
properties: {
|
|
860
|
-
name: { type: "string", description: "The symbol name to check" }
|
|
861
|
-
},
|
|
862
|
-
required: ["name"]
|
|
863
|
-
}
|
|
864
|
-
},
|
|
865
|
-
{
|
|
866
|
-
name: "versions_allowed",
|
|
867
|
-
description: "Check if a package is installed and get its version. MUST call before suggesting any package usage.",
|
|
868
|
-
inputSchema: {
|
|
869
|
-
type: "object",
|
|
870
|
-
properties: {
|
|
871
|
-
package: { type: "string", description: "The package name to check" }
|
|
872
|
-
},
|
|
873
|
-
required: ["package"]
|
|
874
|
-
}
|
|
875
|
-
},
|
|
876
|
-
{
|
|
877
|
-
name: "routes_exists",
|
|
878
|
-
description: "Check if an API route exists. MUST call before claiming any endpoint exists.",
|
|
879
|
-
inputSchema: {
|
|
880
|
-
type: "object",
|
|
881
|
-
properties: {
|
|
882
|
-
method: { type: "string", description: "HTTP method (GET, POST, etc.)" },
|
|
883
|
-
path: { type: "string", description: "Route path" }
|
|
884
|
-
},
|
|
885
|
-
required: ["method", "path"]
|
|
886
|
-
}
|
|
887
|
-
},
|
|
888
|
-
{
|
|
889
|
-
name: "patterns_get",
|
|
890
|
-
description: "Get a golden pattern (verified code example) for a given type. Call when creating new code.",
|
|
891
|
-
inputSchema: {
|
|
892
|
-
type: "object",
|
|
893
|
-
properties: {
|
|
894
|
-
type: { type: "string", description: "Pattern type: api-route, component, hook" }
|
|
895
|
-
},
|
|
896
|
-
required: ["type"]
|
|
897
|
-
}
|
|
898
|
-
},
|
|
899
|
-
{
|
|
900
|
-
name: "security_auth_flow",
|
|
901
|
-
description: "Get authentication flow info: auth files, middleware, protected routes. Call before touching auth.",
|
|
902
|
-
inputSchema: {
|
|
903
|
-
type: "object",
|
|
904
|
-
properties: {},
|
|
905
|
-
required: []
|
|
906
|
-
}
|
|
907
|
-
},
|
|
908
|
-
{
|
|
909
|
-
name: "tests_required",
|
|
910
|
-
description: "Get required tests for a list of changed files.",
|
|
911
|
-
inputSchema: {
|
|
912
|
-
type: "object",
|
|
913
|
-
properties: {
|
|
914
|
-
files: { type: "array", items: { type: "string" }, description: "Files being changed" }
|
|
915
|
-
},
|
|
916
|
-
required: ["files"]
|
|
917
|
-
}
|
|
918
|
-
},
|
|
919
|
-
{
|
|
920
|
-
name: "risk_blast_radius",
|
|
921
|
-
description: "Compute blast radius and risk level for proposed changes. Call before making changes.",
|
|
922
|
-
inputSchema: {
|
|
923
|
-
type: "object",
|
|
924
|
-
properties: {
|
|
925
|
-
files: { type: "array", items: { type: "string" }, description: "Files to analyze" }
|
|
926
|
-
},
|
|
927
|
-
required: ["files"]
|
|
928
|
-
}
|
|
929
|
-
},
|
|
930
|
-
{
|
|
931
|
-
name: "graph_related",
|
|
932
|
-
description: "Get files related to a given file (imports, importers, same directory).",
|
|
933
|
-
inputSchema: {
|
|
934
|
-
type: "object",
|
|
935
|
-
properties: {
|
|
936
|
-
file: { type: "string", description: "File path to analyze" }
|
|
937
|
-
},
|
|
938
|
-
required: ["file"]
|
|
939
|
-
}
|
|
940
|
-
},
|
|
941
|
-
{
|
|
942
|
-
name: "verify_changes",
|
|
943
|
-
description: "Verify proposed changes against reality checks (symbols, versions, blast radius).",
|
|
944
|
-
inputSchema: {
|
|
945
|
-
type: "object",
|
|
946
|
-
properties: {
|
|
947
|
-
newSymbols: { type: "array", items: { type: "string" }, description: "New symbols being used" },
|
|
948
|
-
newPackages: { type: "array", items: { type: "string" }, description: "New packages being used" },
|
|
949
|
-
files: { type: "array", items: { type: "string" }, description: "Files being changed" }
|
|
950
|
-
},
|
|
951
|
-
required: []
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
];
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
handleToolCall(toolName, args) {
|
|
958
|
-
switch (toolName) {
|
|
959
|
-
case "repo_map":
|
|
960
|
-
return this.engine.repoMap();
|
|
961
|
-
case "symbols_exists":
|
|
962
|
-
return this.engine.symbolsExists(args.name);
|
|
963
|
-
case "versions_allowed":
|
|
964
|
-
return this.engine.versionsAllowed(args.package);
|
|
965
|
-
case "routes_exists":
|
|
966
|
-
return this.engine.routesExists(args.method, args.path);
|
|
967
|
-
case "patterns_get":
|
|
968
|
-
return this.engine.patternsGet(args.type);
|
|
969
|
-
case "security_auth_flow":
|
|
970
|
-
return this.engine.securityAuthFlow();
|
|
971
|
-
case "tests_required":
|
|
972
|
-
return this.engine.testsRequired(args.files || []);
|
|
973
|
-
case "risk_blast_radius":
|
|
974
|
-
return this.engine.riskBlastRadius(args.files || []);
|
|
975
|
-
case "graph_related":
|
|
976
|
-
return this.engine.graphRelated(args.file);
|
|
977
|
-
case "verify_changes":
|
|
978
|
-
return this.engine.verify(args);
|
|
979
|
-
default:
|
|
980
|
-
return { error: `Unknown tool: ${toolName}` };
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
/**
|
|
985
|
-
* Start MCP server (stdio mode for Cursor)
|
|
986
|
-
*/
|
|
987
|
-
startStdio() {
|
|
988
|
-
const readline = require("readline");
|
|
989
|
-
const rl = readline.createInterface({
|
|
990
|
-
input: process.stdin,
|
|
991
|
-
output: process.stdout,
|
|
992
|
-
terminal: false
|
|
993
|
-
});
|
|
994
|
-
|
|
995
|
-
rl.on("line", (line) => {
|
|
996
|
-
try {
|
|
997
|
-
const request = JSON.parse(line);
|
|
998
|
-
const response = this.handleMCPRequest(request);
|
|
999
|
-
console.log(JSON.stringify(response));
|
|
1000
|
-
} catch (err) {
|
|
1001
|
-
console.log(JSON.stringify({ error: err.message }));
|
|
1002
|
-
}
|
|
1003
|
-
});
|
|
1004
|
-
|
|
1005
|
-
// Send server info
|
|
1006
|
-
console.error("vibecheck Context Engine MCP Server started");
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
/**
|
|
1010
|
-
* Start HTTP server (for debugging/testing)
|
|
1011
|
-
*/
|
|
1012
|
-
startHttp(port = 3847) {
|
|
1013
|
-
const server = http.createServer((req, res) => {
|
|
1014
|
-
if (req.method === "POST") {
|
|
1015
|
-
let body = "";
|
|
1016
|
-
req.on("data", chunk => body += chunk);
|
|
1017
|
-
req.on("end", () => {
|
|
1018
|
-
try {
|
|
1019
|
-
const request = JSON.parse(body);
|
|
1020
|
-
const response = this.handleMCPRequest(request);
|
|
1021
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1022
|
-
res.end(JSON.stringify(response, null, 2));
|
|
1023
|
-
} catch (err) {
|
|
1024
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1025
|
-
res.end(JSON.stringify({ error: err.message }));
|
|
1026
|
-
}
|
|
1027
|
-
});
|
|
1028
|
-
} else if (req.method === "GET" && req.url === "/tools") {
|
|
1029
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1030
|
-
res.end(JSON.stringify({ tools: this.tools }, null, 2));
|
|
1031
|
-
} else {
|
|
1032
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1033
|
-
res.end(JSON.stringify({ status: "ok", tools: this.tools.map(t => t.name) }));
|
|
1034
|
-
}
|
|
1035
|
-
});
|
|
1036
|
-
|
|
1037
|
-
server.listen(port, () => {
|
|
1038
|
-
console.log(`🚀 Context Engine HTTP server running on http://localhost:${port}`);
|
|
1039
|
-
console.log(` Tools: ${this.tools.map(t => t.name).join(", ")}`);
|
|
1040
|
-
});
|
|
1041
|
-
|
|
1042
|
-
return server;
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
handleMCPRequest(request) {
|
|
1046
|
-
const { method, params, id } = request;
|
|
1047
|
-
|
|
1048
|
-
switch (method) {
|
|
1049
|
-
case "initialize":
|
|
1050
|
-
return {
|
|
1051
|
-
jsonrpc: "2.0",
|
|
1052
|
-
id,
|
|
1053
|
-
result: {
|
|
1054
|
-
protocolVersion: MCP_VERSION,
|
|
1055
|
-
serverInfo: {
|
|
1056
|
-
name: "vibecheck-context",
|
|
1057
|
-
version: "1.0.0"
|
|
1058
|
-
},
|
|
1059
|
-
capabilities: {
|
|
1060
|
-
tools: {}
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
};
|
|
1064
|
-
|
|
1065
|
-
case "tools/list":
|
|
1066
|
-
return {
|
|
1067
|
-
jsonrpc: "2.0",
|
|
1068
|
-
id,
|
|
1069
|
-
result: { tools: this.tools }
|
|
1070
|
-
};
|
|
1071
|
-
|
|
1072
|
-
case "tools/call":
|
|
1073
|
-
const { name, arguments: args } = params;
|
|
1074
|
-
const result = this.handleToolCall(name, args || {});
|
|
1075
|
-
return {
|
|
1076
|
-
jsonrpc: "2.0",
|
|
1077
|
-
id,
|
|
1078
|
-
result: { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }
|
|
1079
|
-
};
|
|
1080
|
-
|
|
1081
|
-
default:
|
|
1082
|
-
return {
|
|
1083
|
-
jsonrpc: "2.0",
|
|
1084
|
-
id,
|
|
1085
|
-
error: { code: -32601, message: `Method not found: ${method}` }
|
|
1086
|
-
};
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
// ========== CLI ==========
|
|
1092
|
-
|
|
1093
|
-
async function main() {
|
|
1094
|
-
const args = process.argv.slice(2);
|
|
1095
|
-
const command = args[0];
|
|
1096
|
-
const projectPath = args.find(a => a.startsWith("--path="))?.split("=")[1] || process.cwd();
|
|
1097
|
-
|
|
1098
|
-
const engine = new ContextEngine(projectPath);
|
|
1099
|
-
|
|
1100
|
-
switch (command) {
|
|
1101
|
-
case "index":
|
|
1102
|
-
await engine.index();
|
|
1103
|
-
break;
|
|
1104
|
-
|
|
1105
|
-
case "serve":
|
|
1106
|
-
const mode = args.includes("--http") ? "http" : "stdio";
|
|
1107
|
-
const port = parseInt(args.find(a => a.startsWith("--port="))?.split("=")[1] || "3847");
|
|
1108
|
-
|
|
1109
|
-
// Load or build index first
|
|
1110
|
-
if (!engine.loadTruthPack()) {
|
|
1111
|
-
console.log("No truth pack found, indexing first...");
|
|
1112
|
-
await engine.index();
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
const server = new MCPServer(engine);
|
|
1116
|
-
if (mode === "http") {
|
|
1117
|
-
server.startHttp(port);
|
|
1118
|
-
} else {
|
|
1119
|
-
server.startStdio();
|
|
1120
|
-
}
|
|
1121
|
-
break;
|
|
1122
|
-
|
|
1123
|
-
case "verify":
|
|
1124
|
-
if (!engine.loadTruthPack()) {
|
|
1125
|
-
console.error("Not indexed. Run 'index' first.");
|
|
1126
|
-
process.exit(1);
|
|
1127
|
-
}
|
|
1128
|
-
// Read changes from stdin or args
|
|
1129
|
-
const changes = JSON.parse(args[1] || "{}");
|
|
1130
|
-
const result = engine.verify(changes);
|
|
1131
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1132
|
-
break;
|
|
1133
|
-
|
|
1134
|
-
case "query":
|
|
1135
|
-
// Quick query mode for testing
|
|
1136
|
-
if (!engine.loadTruthPack()) {
|
|
1137
|
-
console.error("Not indexed. Run 'index' first.");
|
|
1138
|
-
process.exit(1);
|
|
1139
|
-
}
|
|
1140
|
-
const tool = args[1];
|
|
1141
|
-
const toolArgs = JSON.parse(args[2] || "{}");
|
|
1142
|
-
const mcpServer = new MCPServer(engine);
|
|
1143
|
-
const queryResult = mcpServer.handleToolCall(tool, toolArgs);
|
|
1144
|
-
console.log(JSON.stringify(queryResult, null, 2));
|
|
1145
|
-
break;
|
|
1146
|
-
|
|
1147
|
-
default:
|
|
1148
|
-
console.log(`
|
|
1149
|
-
vibecheck Context Engine - MCP Server
|
|
1150
|
-
|
|
1151
|
-
Usage:
|
|
1152
|
-
node index.js index [--path=<project>] Build truth pack
|
|
1153
|
-
node index.js serve [--http] [--port=3847] Start MCP server
|
|
1154
|
-
node index.js verify <changes-json> Verify proposed changes
|
|
1155
|
-
node index.js query <tool> [args-json] Query a tool directly
|
|
1156
|
-
|
|
1157
|
-
Tools:
|
|
1158
|
-
repo_map - Get project architecture
|
|
1159
|
-
symbols_exists - Check if symbol exists
|
|
1160
|
-
versions_allowed - Check if package is installed
|
|
1161
|
-
routes_exists - Check if route exists
|
|
1162
|
-
patterns_get - Get golden pattern
|
|
1163
|
-
security_auth_flow - Get auth flow info
|
|
1164
|
-
tests_required - Get required tests
|
|
1165
|
-
risk_blast_radius - Compute blast radius
|
|
1166
|
-
graph_related - Get related files
|
|
1167
|
-
verify_changes - Verify proposed changes
|
|
1168
|
-
`);
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
module.exports = { ContextEngine, MCPServer };
|
|
1173
|
-
|
|
1174
|
-
if (require.main === module) {
|
|
1175
|
-
main().catch(console.error);
|
|
1176
|
-
}
|