@vibecheck-ai/mcp 24.6.9 → 24.6.12
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 +1 -1
- package/dist/APITruthEngine-IZRR3NT5-LPFUOMLD.js +9 -0
- package/dist/CredentialsEngine-B66ANCBB-HY5ZQTSX.js +9 -0
- package/dist/EnvVarEngine-ZFNW2XKP-6HRTZULP.js +9 -0
- package/dist/ErrorHandlingEngine-FG65SFRB-4NEANCMF.js +11 -0
- package/dist/FrameworkPackEngine-RRBJW4MC-KH7WRXXS.js +12 -0
- package/dist/GhostRouteEngine-UMYBCOCL-MSZOPVZY.js +9 -0
- package/dist/LogicGapEngine-OK5UKZQ5-YGXZDERB.js +11 -0
- package/dist/PhantomDepEngine-5O7Z7MDE-4A37GGL4.js +10 -0
- package/dist/SecurityEngine-MVMRPKLH-BNP7IC46.js +9 -0
- package/dist/VersionHallucinationEngine-673DJ26J-BD4SK6JX.js +9 -0
- package/dist/chokidar-CI5VJY5M.js +2414 -0
- package/dist/chunk-43XAAYST.js +863 -0
- package/dist/chunk-5DADZJ3D.js +650 -0
- package/dist/chunk-DDTUTWRY.js +605 -0
- package/dist/chunk-DGNNNAVK.js +304 -0
- package/dist/chunk-F34MHA6A.js +772 -0
- package/dist/chunk-FGMVY5QW.js +42 -0
- package/dist/chunk-FMRX5OVJ.js +1968 -0
- package/dist/chunk-FRK2XZX5.js +213309 -0
- package/dist/chunk-J52EUKKW.js +196 -0
- package/dist/chunk-JZSHXEYP.js +915 -0
- package/dist/chunk-LQSBUKYZ.js +551 -0
- package/dist/chunk-MUP4JXOF.js +219 -0
- package/dist/chunk-NR36RTVO.js +152 -0
- package/dist/chunk-QGPX6H6L.js +3044 -0
- package/dist/chunk-QYXENOVK.js +499 -0
- package/dist/chunk-RR5ETBSV.js +66 -0
- package/dist/chunk-WUHPSW7M.js +11130 -0
- package/dist/chunk-YWUMPN4Z.js +53 -0
- package/dist/dist-HFMJ3GIR.js +1091 -0
- package/dist/dist-JUOVMQEA.js +9 -0
- package/dist/dist-NXITTS32-O3XLWR6T.js +386 -0
- package/dist/dist-Y2Z46SBD.js +22 -0
- package/dist/fingerprint-NOJ7TDB6-K6SB7LCZ.js +9 -0
- package/dist/index.js +5462 -4676
- package/dist/semantic-WW6XVII4.js +8544 -0
- package/dist/transformers.node-K4WKH4PR.js +45809 -0
- package/dist/tree-sitter-AGICL65I.js +1412 -0
- package/dist/tree-sitter-H5E7LKR4-MKO3NNLJ.js +9 -0
- package/package.json +7 -6
|
@@ -0,0 +1,915 @@
|
|
|
1
|
+
import { createRequire } from 'module';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { dirname } from 'path';
|
|
5
|
+
import * as fs from 'fs/promises';
|
|
6
|
+
import { existsSync, readFileSync } from 'fs';
|
|
7
|
+
|
|
8
|
+
createRequire(import.meta.url);
|
|
9
|
+
const __filename$1 = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname$1 = dirname(__filename$1);
|
|
11
|
+
var HallucinationTrie = class {
|
|
12
|
+
_root = { children: /* @__PURE__ */ new Map() };
|
|
13
|
+
_size = 0;
|
|
14
|
+
get size() {
|
|
15
|
+
return this._size;
|
|
16
|
+
}
|
|
17
|
+
insert(hallucinatedPath, suggestion) {
|
|
18
|
+
let node = this._root;
|
|
19
|
+
for (const char of hallucinatedPath) {
|
|
20
|
+
let child = node.children.get(char);
|
|
21
|
+
if (!child) {
|
|
22
|
+
child = { children: /* @__PURE__ */ new Map() };
|
|
23
|
+
node.children.set(char, child);
|
|
24
|
+
}
|
|
25
|
+
node = child;
|
|
26
|
+
}
|
|
27
|
+
node.value = suggestion;
|
|
28
|
+
this._size++;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Look up an exact call path. Returns the suggestion if found, undefined otherwise.
|
|
32
|
+
* O(k) where k = length of the call path string.
|
|
33
|
+
*/
|
|
34
|
+
lookup(callPath) {
|
|
35
|
+
let node = this._root;
|
|
36
|
+
for (const char of callPath) {
|
|
37
|
+
const child = node.children.get(char);
|
|
38
|
+
if (!child) return void 0;
|
|
39
|
+
node = child;
|
|
40
|
+
}
|
|
41
|
+
return node.value;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Check if any hallucination starts with the given prefix.
|
|
45
|
+
* Useful for early-exit: if prefix doesn't match, skip deeper checks.
|
|
46
|
+
*/
|
|
47
|
+
hasPrefix(prefix) {
|
|
48
|
+
let node = this._root;
|
|
49
|
+
for (const char of prefix) {
|
|
50
|
+
const child = node.children.get(char);
|
|
51
|
+
if (!child) return false;
|
|
52
|
+
node = child;
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var SDK_CALL_RE = /(\w+(?:\.\w+)*)\.(\w+)\s*\(/g;
|
|
58
|
+
var IMPORT_PATTERNS = [
|
|
59
|
+
// ESM: import X from 'pkg' / import { X } from 'pkg'
|
|
60
|
+
{
|
|
61
|
+
regex: /import\s+(?:\{?\s*(\w+)\s*\}?)\s+from\s+['"]([^'"]+)['"]/g,
|
|
62
|
+
nameIndex: 1,
|
|
63
|
+
packageIndex: 2
|
|
64
|
+
},
|
|
65
|
+
// CJS: const x = require('pkg') / const { X } = require('pkg')
|
|
66
|
+
{
|
|
67
|
+
regex: /(?:const|let|var)\s+(?:\{?\s*(\w+)\s*\}?)\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
68
|
+
nameIndex: 1,
|
|
69
|
+
packageIndex: 2
|
|
70
|
+
},
|
|
71
|
+
// Dynamic import: const x = await import('pkg')
|
|
72
|
+
{
|
|
73
|
+
regex: /(?:const|let|var)\s+(?:\{?\s*(\w+)\s*\}?)\s*=\s*await\s+import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
74
|
+
nameIndex: 1,
|
|
75
|
+
packageIndex: 2
|
|
76
|
+
}
|
|
77
|
+
];
|
|
78
|
+
var PACKAGE_TO_SDK = {
|
|
79
|
+
"stripe": "stripe",
|
|
80
|
+
"openai": "openai",
|
|
81
|
+
"@anthropic-ai/sdk": "anthropic",
|
|
82
|
+
"anthropic": "anthropic",
|
|
83
|
+
"@prisma/client": "prisma",
|
|
84
|
+
"@supabase/supabase-js": "supabase",
|
|
85
|
+
"firebase": "firebase",
|
|
86
|
+
"firebase-admin": "firebase",
|
|
87
|
+
"firebase/app": "firebase",
|
|
88
|
+
"resend": "resend",
|
|
89
|
+
"@clerk/nextjs": "clerk",
|
|
90
|
+
"@clerk/express": "clerk",
|
|
91
|
+
"@clerk/backend": "clerk",
|
|
92
|
+
"ai": "ai",
|
|
93
|
+
// Vercel AI SDK
|
|
94
|
+
"@ai-sdk/openai": "ai",
|
|
95
|
+
"@ai-sdk/anthropic": "ai",
|
|
96
|
+
"drizzle-orm": "drizzle",
|
|
97
|
+
"@trpc/server": "trpc",
|
|
98
|
+
"@trpc/client": "trpc",
|
|
99
|
+
"hono": "hono",
|
|
100
|
+
"zod": "zod"
|
|
101
|
+
};
|
|
102
|
+
var NAME_TO_SDK = {
|
|
103
|
+
"stripe": "stripe",
|
|
104
|
+
"openai": "openai",
|
|
105
|
+
"anthropic": "anthropic",
|
|
106
|
+
"prisma": "prisma",
|
|
107
|
+
"supabase": "supabase",
|
|
108
|
+
"firebase": "firebase",
|
|
109
|
+
"resend": "resend",
|
|
110
|
+
"clerk": "clerk",
|
|
111
|
+
"ai": "ai",
|
|
112
|
+
"drizzle": "drizzle",
|
|
113
|
+
"trpc": "trpc",
|
|
114
|
+
"hono": "hono",
|
|
115
|
+
"zod": "zod"
|
|
116
|
+
};
|
|
117
|
+
function isRelativeOrLocalImport(pkg) {
|
|
118
|
+
return pkg.startsWith(".") || pkg.startsWith("@/") || pkg.includes("/db/") || pkg.includes("db/client");
|
|
119
|
+
}
|
|
120
|
+
function detectImports(text) {
|
|
121
|
+
const results = [];
|
|
122
|
+
const seen = /* @__PURE__ */ new Set();
|
|
123
|
+
const lines = text.split("\n");
|
|
124
|
+
const importRegion = lines.slice(0, 80).join("\n");
|
|
125
|
+
const rawImports = [];
|
|
126
|
+
const packagesInFile = /* @__PURE__ */ new Set();
|
|
127
|
+
for (const pattern of IMPORT_PATTERNS) {
|
|
128
|
+
pattern.regex.lastIndex = 0;
|
|
129
|
+
let match;
|
|
130
|
+
while ((match = pattern.regex.exec(importRegion)) !== null) {
|
|
131
|
+
const localName = match[pattern.nameIndex];
|
|
132
|
+
const packageName = match[pattern.packageIndex];
|
|
133
|
+
rawImports.push({ localName, packageName, index: match.index });
|
|
134
|
+
if (PACKAGE_TO_SDK[packageName]) {
|
|
135
|
+
packagesInFile.add(packageName);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const hasDrizzle = packagesInFile.has("drizzle-orm");
|
|
140
|
+
const hasPrisma = packagesInFile.has("@prisma/client");
|
|
141
|
+
for (const { localName, packageName, index } of rawImports) {
|
|
142
|
+
if (seen.has(localName)) continue;
|
|
143
|
+
let sdk = PACKAGE_TO_SDK[packageName] ?? NAME_TO_SDK[localName.toLowerCase()] ?? null;
|
|
144
|
+
if (localName.toLowerCase() === "db" && isRelativeOrLocalImport(packageName)) {
|
|
145
|
+
if (hasDrizzle && !hasPrisma) {
|
|
146
|
+
sdk = "drizzle";
|
|
147
|
+
} else if (hasPrisma && !hasDrizzle) {
|
|
148
|
+
sdk = "prisma";
|
|
149
|
+
} else if (!hasDrizzle && !hasPrisma) {
|
|
150
|
+
sdk = null;
|
|
151
|
+
} else {
|
|
152
|
+
sdk = "drizzle";
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (sdk) {
|
|
156
|
+
seen.add(localName);
|
|
157
|
+
let lineNum = 0;
|
|
158
|
+
for (let ci = 0; ci < index && ci < importRegion.length; ci++) {
|
|
159
|
+
if (importRegion.charCodeAt(ci) === 10) lineNum++;
|
|
160
|
+
}
|
|
161
|
+
results.push({ sdk, localName, line: lineNum });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return results;
|
|
165
|
+
}
|
|
166
|
+
function buildCodeRegions(lines) {
|
|
167
|
+
const regions = [];
|
|
168
|
+
let inBlockComment = false;
|
|
169
|
+
for (const line of lines) {
|
|
170
|
+
const stringRanges = [];
|
|
171
|
+
let isFullComment = inBlockComment;
|
|
172
|
+
let i = 0;
|
|
173
|
+
if (inBlockComment) {
|
|
174
|
+
const closeIdx = line.indexOf("*/");
|
|
175
|
+
if (closeIdx === -1) {
|
|
176
|
+
regions.push({ inComment: true, stringRanges: [] });
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
i = closeIdx + 2;
|
|
180
|
+
inBlockComment = false;
|
|
181
|
+
isFullComment = false;
|
|
182
|
+
}
|
|
183
|
+
let inString = null;
|
|
184
|
+
let stringStart = 0;
|
|
185
|
+
while (i < line.length) {
|
|
186
|
+
const ch = line[i];
|
|
187
|
+
const next = line[i + 1];
|
|
188
|
+
if (inString) {
|
|
189
|
+
if (ch === "\\") {
|
|
190
|
+
i += 2;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (ch === inString) {
|
|
194
|
+
stringRanges.push({ start: stringStart, end: i });
|
|
195
|
+
inString = null;
|
|
196
|
+
}
|
|
197
|
+
i++;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (ch === "/" && next === "/") {
|
|
201
|
+
if (i === 0 || line.slice(0, i).trim() === "") isFullComment = true;
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
if (ch === "/" && next === "*") {
|
|
205
|
+
inBlockComment = true;
|
|
206
|
+
const closeIdx = line.indexOf("*/", i + 2);
|
|
207
|
+
if (closeIdx !== -1) {
|
|
208
|
+
inBlockComment = false;
|
|
209
|
+
i = closeIdx + 2;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (i === 0 || line.slice(0, i).trim() === "") isFullComment = true;
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
if (ch === "'" || ch === '"' || ch === "`") {
|
|
216
|
+
inString = ch;
|
|
217
|
+
stringStart = i;
|
|
218
|
+
i++;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
i++;
|
|
222
|
+
}
|
|
223
|
+
regions.push({ inComment: isFullComment, stringRanges });
|
|
224
|
+
}
|
|
225
|
+
return regions;
|
|
226
|
+
}
|
|
227
|
+
function isInString(region, col) {
|
|
228
|
+
for (const r of region.stringRanges) {
|
|
229
|
+
if (col >= r.start && col <= r.end) return true;
|
|
230
|
+
}
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
function levenshtein(a, b) {
|
|
234
|
+
if (a === b) return 0;
|
|
235
|
+
if (a.length === 0) return b.length;
|
|
236
|
+
if (b.length === 0) return a.length;
|
|
237
|
+
if (a.length > b.length) [a, b] = [b, a];
|
|
238
|
+
const m = a.length;
|
|
239
|
+
const n = b.length;
|
|
240
|
+
const row = new Uint16Array(m + 1);
|
|
241
|
+
for (let i = 0; i <= m; i++) row[i] = i;
|
|
242
|
+
for (let j = 1; j <= n; j++) {
|
|
243
|
+
let prev = row[0];
|
|
244
|
+
row[0] = j;
|
|
245
|
+
for (let i = 1; i <= m; i++) {
|
|
246
|
+
const temp = row[i];
|
|
247
|
+
row[i] = a[i - 1] === b[j - 1] ? prev : 1 + Math.min(prev, row[i], row[i - 1]);
|
|
248
|
+
prev = temp;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return row[m];
|
|
252
|
+
}
|
|
253
|
+
function findClosestMatch(map, callPath, maxDist = 3) {
|
|
254
|
+
let best;
|
|
255
|
+
let bestDist = maxDist + 1;
|
|
256
|
+
for (const key of Object.keys(map)) {
|
|
257
|
+
const entry = map[key];
|
|
258
|
+
if (!entry?.exists) continue;
|
|
259
|
+
if (Math.abs(key.length - callPath.length) >= bestDist) continue;
|
|
260
|
+
const d = levenshtein(callPath, key);
|
|
261
|
+
if (d < bestDist) {
|
|
262
|
+
bestDist = d;
|
|
263
|
+
best = key;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return best;
|
|
267
|
+
}
|
|
268
|
+
function deterministicId(uri, line, col, ruleId, evidence) {
|
|
269
|
+
const input = `${uri}::${line}::${col}::${ruleId}::${evidence}`;
|
|
270
|
+
let hash = 2166136261;
|
|
271
|
+
for (let i = 0; i < input.length; i++) {
|
|
272
|
+
hash ^= input.charCodeAt(i);
|
|
273
|
+
hash = hash * 16777619 >>> 0;
|
|
274
|
+
}
|
|
275
|
+
return `api-truth-${hash.toString(16).padStart(8, "0")}`;
|
|
276
|
+
}
|
|
277
|
+
var KNOWN_HALLUCINATIONS = [
|
|
278
|
+
// ── Stripe ─────────────────────────────────────────────────────────────
|
|
279
|
+
["stripe.confirmPayment", "stripe.paymentIntents.confirm"],
|
|
280
|
+
["stripe.createPayment", "stripe.paymentIntents.create"],
|
|
281
|
+
["stripe.createPaymentIntent", "stripe.paymentIntents.create"],
|
|
282
|
+
["stripe.confirmPaymentIntent", "stripe.paymentIntents.confirm"],
|
|
283
|
+
["stripe.cancelPaymentIntent", "stripe.paymentIntents.cancel"],
|
|
284
|
+
["stripe.capturePaymentIntent", "stripe.paymentIntents.capture"],
|
|
285
|
+
["stripe.getPaymentIntent", "stripe.paymentIntents.retrieve"],
|
|
286
|
+
["stripe.listPaymentIntents", "stripe.paymentIntents.list"],
|
|
287
|
+
["stripe.getSubscription", "stripe.subscriptions.retrieve"],
|
|
288
|
+
["stripe.createSubscription", "stripe.subscriptions.create"],
|
|
289
|
+
["stripe.cancelSubscription", "stripe.subscriptions.cancel"],
|
|
290
|
+
["stripe.updateSubscription", "stripe.subscriptions.update"],
|
|
291
|
+
["stripe.listSubscriptions", "stripe.subscriptions.list"],
|
|
292
|
+
["stripe.createCustomer", "stripe.customers.create"],
|
|
293
|
+
["stripe.getCustomer", "stripe.customers.retrieve"],
|
|
294
|
+
["stripe.updateCustomer", "stripe.customers.update"],
|
|
295
|
+
["stripe.deleteCustomer", "stripe.customers.del"],
|
|
296
|
+
["stripe.listCustomers", "stripe.customers.list"],
|
|
297
|
+
["stripe.createCheckoutSession", "stripe.checkout.sessions.create"],
|
|
298
|
+
["stripe.getCheckoutSession", "stripe.checkout.sessions.retrieve"],
|
|
299
|
+
["stripe.createRefund", "stripe.refunds.create"],
|
|
300
|
+
["stripe.createInvoice", "stripe.invoices.create"],
|
|
301
|
+
["stripe.payInvoice", "stripe.invoices.pay"],
|
|
302
|
+
["stripe.finalizeInvoice", "stripe.invoices.finalizeInvoice"],
|
|
303
|
+
["stripe.verify", "stripe.webhooks.constructEvent"],
|
|
304
|
+
["stripe.constructWebhookEvent", "stripe.webhooks.constructEvent"],
|
|
305
|
+
["stripe.verifyWebhook", "stripe.webhooks.constructEvent"],
|
|
306
|
+
["stripe.createPrice", "stripe.prices.create"],
|
|
307
|
+
["stripe.getPrice", "stripe.prices.retrieve"],
|
|
308
|
+
["stripe.createProduct", "stripe.products.create"],
|
|
309
|
+
["stripe.getProduct", "stripe.products.retrieve"],
|
|
310
|
+
["stripe.createSetupIntent", "stripe.setupIntents.create"],
|
|
311
|
+
["stripe.getSetupIntent", "stripe.setupIntents.retrieve"],
|
|
312
|
+
["stripe.attachPaymentMethod", "stripe.paymentMethods.attach"],
|
|
313
|
+
["stripe.detachPaymentMethod", "stripe.paymentMethods.detach"],
|
|
314
|
+
// ── OpenAI ─────────────────────────────────────────────────────────────
|
|
315
|
+
["openai.createCompletion", "openai.completions.create"],
|
|
316
|
+
["openai.createChatCompletion", "openai.chat.completions.create"],
|
|
317
|
+
["openai.createEmbedding", "openai.embeddings.create"],
|
|
318
|
+
["openai.createEmbeddings", "openai.embeddings.create"],
|
|
319
|
+
["openai.createImage", "openai.images.generate"],
|
|
320
|
+
["openai.generateImage", "openai.images.generate"],
|
|
321
|
+
["openai.editImage", "openai.images.edit"],
|
|
322
|
+
["openai.getModels", "openai.models.list"],
|
|
323
|
+
["openai.listModels", "openai.models.list"],
|
|
324
|
+
["openai.complete", "openai.chat.completions.create"],
|
|
325
|
+
["openai.generate", "openai.chat.completions.create"],
|
|
326
|
+
["openai.chat", "openai.chat.completions.create"],
|
|
327
|
+
["openai.createTranscription", "openai.audio.transcriptions.create"],
|
|
328
|
+
["openai.transcribe", "openai.audio.transcriptions.create"],
|
|
329
|
+
["openai.createModeration", "openai.moderations.create"],
|
|
330
|
+
["openai.moderate", "openai.moderations.create"],
|
|
331
|
+
["openai.createFile", "openai.files.create"],
|
|
332
|
+
["openai.uploadFile", "openai.files.create"],
|
|
333
|
+
["openai.createFineTune", "openai.fineTuning.jobs.create"],
|
|
334
|
+
["openai.createThread", "openai.beta.threads.create"],
|
|
335
|
+
["openai.createAssistant", "openai.beta.assistants.create"],
|
|
336
|
+
["openai.createRun", "openai.beta.threads.runs.create"],
|
|
337
|
+
["openai.streamChat", "openai.chat.completions.create({ stream: true })"],
|
|
338
|
+
// ── Anthropic ──────────────────────────────────────────────────────────
|
|
339
|
+
["anthropic.complete", "anthropic.messages.create"],
|
|
340
|
+
["anthropic.createCompletion", "anthropic.messages.create"],
|
|
341
|
+
["anthropic.chat", "anthropic.messages.create"],
|
|
342
|
+
["anthropic.sendMessage", "anthropic.messages.create"],
|
|
343
|
+
["anthropic.createMessage", "anthropic.messages.create"],
|
|
344
|
+
["anthropic.generate", "anthropic.messages.create"],
|
|
345
|
+
["anthropic.stream", "anthropic.messages.stream"],
|
|
346
|
+
["anthropic.streamMessage", "anthropic.messages.stream"],
|
|
347
|
+
["anthropic.countTokens", "anthropic.messages.count_tokens"],
|
|
348
|
+
["anthropic.createBatch", "anthropic.messages.batches.create"],
|
|
349
|
+
// ── Prisma ─────────────────────────────────────────────────────────────
|
|
350
|
+
["prisma.findAll", "prisma.<model>.findMany"],
|
|
351
|
+
["prisma.getAll", "prisma.<model>.findMany"],
|
|
352
|
+
["prisma.fetchAll", "prisma.<model>.findMany"],
|
|
353
|
+
["prisma.getOne", "prisma.<model>.findUnique"],
|
|
354
|
+
["prisma.fetchOne", "prisma.<model>.findUnique"],
|
|
355
|
+
["prisma.getById", "prisma.<model>.findUnique"],
|
|
356
|
+
["prisma.removeAll", "prisma.<model>.deleteMany"],
|
|
357
|
+
["prisma.updateAll", "prisma.<model>.updateMany"],
|
|
358
|
+
["prisma.createOrUpdate", "prisma.<model>.upsert"],
|
|
359
|
+
["prisma.search", "prisma.<model>.findMany({ where: ... })"],
|
|
360
|
+
["prisma.query", "prisma.$queryRaw"],
|
|
361
|
+
["prisma.raw", "prisma.$queryRaw"],
|
|
362
|
+
["prisma.execute", "prisma.$executeRaw"],
|
|
363
|
+
["prisma.transaction", "prisma.$transaction"],
|
|
364
|
+
["prisma.disconnect", "prisma.$disconnect"],
|
|
365
|
+
["prisma.connect", "prisma.$connect"],
|
|
366
|
+
// ── Supabase ───────────────────────────────────────────────────────────
|
|
367
|
+
["supabase.query", "supabase.from(table).select()"],
|
|
368
|
+
["supabase.fetch", "supabase.from(table).select()"],
|
|
369
|
+
["supabase.getUser", "supabase.auth.getUser()"],
|
|
370
|
+
["supabase.login", "supabase.auth.signInWithPassword()"],
|
|
371
|
+
["supabase.signup", "supabase.auth.signUp()"],
|
|
372
|
+
["supabase.signIn", "supabase.auth.signInWithPassword()"],
|
|
373
|
+
["supabase.signOut", "supabase.auth.signOut()"],
|
|
374
|
+
["supabase.logout", "supabase.auth.signOut()"],
|
|
375
|
+
["supabase.upload", "supabase.storage.from(bucket).upload()"],
|
|
376
|
+
["supabase.subscribe", "supabase.channel(name).subscribe()"],
|
|
377
|
+
// ── Firebase ───────────────────────────────────────────────────────────
|
|
378
|
+
["firebase.getDocument", "getDoc(docRef)"],
|
|
379
|
+
["firebase.setDocument", "setDoc(docRef, data)"],
|
|
380
|
+
["firebase.queryCollection", "getDocs(query(collectionRef, ...))"],
|
|
381
|
+
["firebase.onAuth", "onAuthStateChanged(auth, callback)"],
|
|
382
|
+
["firebase.login", "signInWithEmailAndPassword(auth, ...)"],
|
|
383
|
+
["firebase.signup", "createUserWithEmailAndPassword(auth, ...)"],
|
|
384
|
+
["firebase.signOut", "signOut(auth)"],
|
|
385
|
+
["firebase.getCurrentUser", "auth.currentUser or onAuthStateChanged()"],
|
|
386
|
+
["firebase.addDocument", "addDoc(collectionRef, data)"],
|
|
387
|
+
["firebase.updateDocument", "updateDoc(docRef, data)"],
|
|
388
|
+
["firebase.deleteDocument", "deleteDoc(docRef)"],
|
|
389
|
+
["firebase.onSnapshot", "onSnapshot(docRef, callback) \u2014 imported from firebase/firestore"],
|
|
390
|
+
["firebase.collection", "collection(db, collectionName) \u2014 v9+ uses modular API"],
|
|
391
|
+
["firebase.doc", "doc(db, collection, id) \u2014 v9+ uses modular API"],
|
|
392
|
+
// ── Resend ──────────────────────────────────────────────────────────────
|
|
393
|
+
["resend.send", "resend.emails.send"],
|
|
394
|
+
["resend.sendEmail", "resend.emails.send"],
|
|
395
|
+
["resend.createEmail", "resend.emails.send"],
|
|
396
|
+
["resend.sendBatch", "resend.batch.send"],
|
|
397
|
+
["resend.getEmail", "resend.emails.get"],
|
|
398
|
+
["resend.listEmails", "resend.emails.list"],
|
|
399
|
+
// ── Clerk ───────────────────────────────────────────────────────────────
|
|
400
|
+
["clerk.getUser", "clerkClient.users.getUser(userId)"],
|
|
401
|
+
["clerk.createUser", "clerkClient.users.createUser(params)"],
|
|
402
|
+
["clerk.updateUser", "clerkClient.users.updateUser(userId, params)"],
|
|
403
|
+
["clerk.deleteUser", "clerkClient.users.deleteUser(userId)"],
|
|
404
|
+
["clerk.listUsers", "clerkClient.users.getUserList()"],
|
|
405
|
+
["clerk.verifyToken", "clerkClient.verifyToken(token) or authenticateRequest()"],
|
|
406
|
+
// ── Vercel AI SDK ───────────────────────────────────────────────────────
|
|
407
|
+
["ai.chat", "generateText({ model, prompt }) or streamText({ model, prompt })"],
|
|
408
|
+
["ai.complete", "generateText({ model, prompt })"],
|
|
409
|
+
["ai.generate", "generateText({ model, prompt })"],
|
|
410
|
+
["ai.stream", "streamText({ model, prompt })"],
|
|
411
|
+
["ai.createCompletion", "generateText({ model, prompt })"],
|
|
412
|
+
["ai.embed", "embed({ model, value }) or embedMany({ model, values })"],
|
|
413
|
+
// ── Drizzle ORM ─────────────────────────────────────────────────────────
|
|
414
|
+
["db.findMany", "db.select().from(table)"],
|
|
415
|
+
["db.findOne", "db.select().from(table).where(...).limit(1)"],
|
|
416
|
+
["db.findUnique", "db.select().from(table).where(eq(table.id, id))"],
|
|
417
|
+
["db.findFirst", "db.select().from(table).where(...).limit(1)"],
|
|
418
|
+
["db.create", "db.insert(table).values(data)"],
|
|
419
|
+
["db.remove", "db.delete(table).where(...)"],
|
|
420
|
+
["db.destroy", "db.delete(table).where(...)"],
|
|
421
|
+
// ── tRPC ────────────────────────────────────────────────────────────────
|
|
422
|
+
["trpc.query", "publicProcedure.query(({ ctx, input }) => ...)"],
|
|
423
|
+
["trpc.mutate", "publicProcedure.mutation(({ ctx, input }) => ...)"],
|
|
424
|
+
["trpc.procedure", "publicProcedure or protectedProcedure (from your trpc setup)"],
|
|
425
|
+
["trpc.createClient", "createTRPCClient<AppRouter>({ links: [...] })"],
|
|
426
|
+
["trpc.createRouter", "router({ ... }) from initTRPC.create()"],
|
|
427
|
+
// ── Hono ────────────────────────────────────────────────────────────────
|
|
428
|
+
["hono.listen", "serve(app) from @hono/node-server"],
|
|
429
|
+
["hono.start", "serve({ fetch: app.fetch, port })"],
|
|
430
|
+
["hono.register", "app.route(path, subApp) or app.use(middleware)"],
|
|
431
|
+
["hono.sendJSON", "c.json(data)"],
|
|
432
|
+
// ── Zod ─────────────────────────────────────────────────────────────────
|
|
433
|
+
["zod.validate", "schema.parse(data) or schema.safeParse(data)"],
|
|
434
|
+
["zod.check", "schema.refine(fn) or schema.superRefine(fn)"],
|
|
435
|
+
["zod.assert", "schema.parse(data) \u2014 throws on failure"],
|
|
436
|
+
["zod.isValid", "schema.safeParse(data).success"],
|
|
437
|
+
["zod.create", "z.object({...}), z.string(), z.number(), etc."],
|
|
438
|
+
["zod.schema", "z.object({...})"],
|
|
439
|
+
// ── Stripe (additional) ─────────────────────────────────────────────────
|
|
440
|
+
["stripe.createPaymentLink", "stripe.paymentLinks.create"],
|
|
441
|
+
["stripe.getPaymentLink", "stripe.paymentLinks.retrieve"],
|
|
442
|
+
["stripe.listPaymentLinks", "stripe.paymentLinks.list"],
|
|
443
|
+
["stripe.createCoupon", "stripe.coupons.create"],
|
|
444
|
+
["stripe.createTaxRate", "stripe.taxRates.create"],
|
|
445
|
+
["stripe.createQuote", "stripe.quotes.create"],
|
|
446
|
+
["stripe.createBillingSession", "stripe.billingPortal.sessions.create"],
|
|
447
|
+
// ── OpenAI (additional) ─────────────────────────────────────────────────
|
|
448
|
+
["openai.createSpeech", "openai.audio.speech.create"],
|
|
449
|
+
["openai.listFiles", "openai.files.list"],
|
|
450
|
+
["openai.deleteFile", "openai.files.del"],
|
|
451
|
+
["openai.createVectorStore", "openai.beta.vectorStores.create"],
|
|
452
|
+
["openai.createBatch", "openai.batches.create"],
|
|
453
|
+
// ── Anthropic (additional) ──────────────────────────────────────────────
|
|
454
|
+
["anthropic.completion", "anthropic.messages.create"],
|
|
455
|
+
["anthropic.chat", "anthropic.messages.create"],
|
|
456
|
+
["anthropic.listModels", "anthropic.models.list"],
|
|
457
|
+
["anthropic.getModel", "anthropic.models.retrieve"],
|
|
458
|
+
["anthropic.createBatch", "anthropic.messages.batches.create"],
|
|
459
|
+
["anthropic.getBatch", "anthropic.messages.batches.retrieve"]
|
|
460
|
+
];
|
|
461
|
+
var DEFAULT_PATTERN_RULES = [
|
|
462
|
+
// Prisma: hallucinated methods on any model
|
|
463
|
+
{
|
|
464
|
+
ruleId: "HAL002",
|
|
465
|
+
regex: /\bprisma\.\w+\.(?:getAll|fetchAll|search|getOne|fetchOne|removeAll|updateAll|createOrUpdate|getById|fetch|get|remove|save|insert)\s*\(/,
|
|
466
|
+
message: "Hallucinated Prisma method \u2014 this does not exist in any Prisma version",
|
|
467
|
+
suggestion: "Prisma model methods: findMany, findUnique, findFirst, create, update, delete, upsert, deleteMany, updateMany, count, aggregate, groupBy",
|
|
468
|
+
confidence: 0.88
|
|
469
|
+
},
|
|
470
|
+
// Express: hallucinated convenience methods
|
|
471
|
+
{
|
|
472
|
+
ruleId: "HAL002",
|
|
473
|
+
regex: /(?:app|router)\.(?:handle|serve|mount|register|bind|attach)\s*\(\s*['"`]\/\w/,
|
|
474
|
+
message: "Hallucinated Express/router method \u2014 this does not exist",
|
|
475
|
+
suggestion: "Express uses: .get(), .post(), .put(), .delete(), .patch(), .use(), .all(), .route()",
|
|
476
|
+
confidence: 0.8
|
|
477
|
+
},
|
|
478
|
+
// Next.js: hallucinated config options
|
|
479
|
+
{
|
|
480
|
+
ruleId: "HAL004",
|
|
481
|
+
regex: /(?:enableSSR|enableCSR|autoOptimize|smartBundling|lazyHydration|autoCache|enableSWC|enableTurbo|autoRouting|staticPaths|hybridMode)\s*:/,
|
|
482
|
+
message: "Hallucinated Next.js config option \u2014 this property does not exist",
|
|
483
|
+
suggestion: "Check the Next.js docs for valid next.config.js options: reactStrictMode, images, experimental, env, redirects, rewrites, headers, etc.",
|
|
484
|
+
confidence: 0.82
|
|
485
|
+
},
|
|
486
|
+
// React: hallucinated hooks
|
|
487
|
+
{
|
|
488
|
+
ruleId: "HAL003",
|
|
489
|
+
regex: /\b(?:useFormState|useAsyncEffect|usePromise|useRequest|useFetchData|useLocalStorage|useApi|useQuery)\s*\(/,
|
|
490
|
+
message: "This is not a built-in React hook \u2014 it may be hallucinated or require a third-party library",
|
|
491
|
+
suggestion: "Built-in React hooks: useState, useEffect, useContext, useReducer, useCallback, useMemo, useRef, useId, useDeferredValue, useTransition, useOptimistic, useActionState, use",
|
|
492
|
+
confidence: 0.72,
|
|
493
|
+
severity: "medium",
|
|
494
|
+
extensions: /* @__PURE__ */ new Set([".tsx", ".jsx"])
|
|
495
|
+
},
|
|
496
|
+
// Node.js: hallucinated fs methods
|
|
497
|
+
{
|
|
498
|
+
ruleId: "HAL002",
|
|
499
|
+
regex: /\bfs\.(?:readJSON|writeJSON|readDir|isFile|isDirectory|getSize|move|copy|create)\s*\(/,
|
|
500
|
+
message: "Hallucinated Node.js fs method \u2014 this does not exist",
|
|
501
|
+
suggestion: "Use: readFile/writeFile (with JSON.parse/stringify), readdir, stat, rename, copyFile, open",
|
|
502
|
+
confidence: 0.85
|
|
503
|
+
},
|
|
504
|
+
// Deprecated API detection (layer 3.5)
|
|
505
|
+
{
|
|
506
|
+
ruleId: "HAL005",
|
|
507
|
+
regex: /\bnew Buffer\s*\(/,
|
|
508
|
+
message: "Buffer() constructor is deprecated and unsafe",
|
|
509
|
+
suggestion: "Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe()",
|
|
510
|
+
confidence: 0.95,
|
|
511
|
+
severity: "high"
|
|
512
|
+
},
|
|
513
|
+
// Placeholder URLs — AI often uses these instead of real config
|
|
514
|
+
{
|
|
515
|
+
ruleId: "HAL007",
|
|
516
|
+
regex: /(?:https?:\/\/)?(?:www\.)?(?:example\.com|your-site\.com|yourdomain\.com|placeholder\.com|example\.org)\b/i,
|
|
517
|
+
message: "Placeholder URL \u2014 replace with your actual domain or config",
|
|
518
|
+
suggestion: "Use environment variables for URLs: process.env.API_URL or NEXT_PUBLIC_APP_URL",
|
|
519
|
+
confidence: 0.85,
|
|
520
|
+
severity: "medium"
|
|
521
|
+
},
|
|
522
|
+
// Fake test emails — common AI placeholder
|
|
523
|
+
{
|
|
524
|
+
ruleId: "HAL008",
|
|
525
|
+
regex: /['"`]?(?:test@example\.com|user@example\.com|admin@example\.com|email@example\.com|user@test\.com)['"`]?/,
|
|
526
|
+
message: "Placeholder test email \u2014 use a real test fixture or env var",
|
|
527
|
+
suggestion: "Use process.env.TEST_EMAIL or a dedicated test fixture (e.g. fixtures/users.ts)",
|
|
528
|
+
confidence: 0.8,
|
|
529
|
+
severity: "low"
|
|
530
|
+
},
|
|
531
|
+
// localhost in production config — AI often leaves dev URLs in prod
|
|
532
|
+
{
|
|
533
|
+
ruleId: "HAL009",
|
|
534
|
+
regex: /(?:NEXT_PUBLIC_|VITE_|API_|BASE_)?(?:URL|HOST|ORIGIN)\s*[:=]\s*['"`]https?:\/\/localhost(?::\d+)?\/?['"`]/,
|
|
535
|
+
message: "localhost URL in config \u2014 will fail in production",
|
|
536
|
+
suggestion: "Use environment-specific URLs: process.env.API_URL or conditional based on NODE_ENV",
|
|
537
|
+
confidence: 0.9,
|
|
538
|
+
severity: "high"
|
|
539
|
+
},
|
|
540
|
+
// Drizzle: hallucinated Prisma-style methods
|
|
541
|
+
{
|
|
542
|
+
ruleId: "HAL002",
|
|
543
|
+
regex: /\bdb\.(?:findMany|findUnique|findFirst|create|update|delete|upsert|createMany|deleteMany|updateMany)\s*\(/,
|
|
544
|
+
message: "Prisma method used with Drizzle ORM \u2014 Drizzle uses a different API",
|
|
545
|
+
suggestion: "Drizzle uses: db.select().from(table), db.insert(table).values(), db.update(table).set(), db.delete(table).where()",
|
|
546
|
+
confidence: 0.82
|
|
547
|
+
},
|
|
548
|
+
// tRPC: hallucinated Express-style route handlers
|
|
549
|
+
{
|
|
550
|
+
ruleId: "HAL002",
|
|
551
|
+
regex: /\btrpc\.(?:get|post|put|delete|patch|handler|endpoint)\s*\(/,
|
|
552
|
+
message: "Express-style route handler used with tRPC \u2014 tRPC uses procedures, not routes",
|
|
553
|
+
suggestion: "tRPC uses: publicProcedure.input(schema).query/mutation(({ input, ctx }) => ...)",
|
|
554
|
+
confidence: 0.8
|
|
555
|
+
},
|
|
556
|
+
// Hono: Express-style middleware patterns that don't exist
|
|
557
|
+
{
|
|
558
|
+
ruleId: "HAL002",
|
|
559
|
+
regex: /\bapp\.(?:use\(\s*express\.|bodyParser|cookieParser|cors\(\))\b/,
|
|
560
|
+
message: "Express middleware used with Hono \u2014 Hono has its own middleware",
|
|
561
|
+
suggestion: 'Hono middleware: import { cors } from "hono/cors", import { logger } from "hono/logger"',
|
|
562
|
+
confidence: 0.78,
|
|
563
|
+
severity: "medium"
|
|
564
|
+
},
|
|
565
|
+
// Vercel AI SDK: OpenAI-style API used instead of Vercel AI SDK patterns
|
|
566
|
+
{
|
|
567
|
+
ruleId: "HAL002",
|
|
568
|
+
regex: /\bai\.(?:chat|completions|embeddings|images)\./,
|
|
569
|
+
message: "OpenAI-style method chain used with Vercel AI SDK \u2014 different API",
|
|
570
|
+
suggestion: "Vercel AI SDK uses: generateText({ model, prompt }), streamText({ model, prompt }), embed({ model, value })",
|
|
571
|
+
confidence: 0.8,
|
|
572
|
+
severity: "high"
|
|
573
|
+
},
|
|
574
|
+
// React Server Components: hallucinated hooks in server components
|
|
575
|
+
{
|
|
576
|
+
ruleId: "HAL003",
|
|
577
|
+
regex: /['"`]use server['"`]\s*;[\s\S]*\b(?:useState|useEffect|useCallback|useMemo|useRef|useReducer|useContext)\s*\(/,
|
|
578
|
+
message: "React hook used in a Server Component \u2014 hooks only work in Client Components",
|
|
579
|
+
suggestion: 'Add "use client" directive at the top of the file, or move the hook to a Client Component.',
|
|
580
|
+
confidence: 0.92,
|
|
581
|
+
severity: "critical",
|
|
582
|
+
extensions: /* @__PURE__ */ new Set([".tsx", ".jsx"])
|
|
583
|
+
}
|
|
584
|
+
];
|
|
585
|
+
function detectSdkVersions(workspaceRoot) {
|
|
586
|
+
const versions = {};
|
|
587
|
+
const pkgPath = path.join(workspaceRoot, "package.json");
|
|
588
|
+
try {
|
|
589
|
+
if (!existsSync(pkgPath)) return versions;
|
|
590
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
591
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
592
|
+
for (const [pkgName, sdkKey] of Object.entries(PACKAGE_TO_SDK)) {
|
|
593
|
+
const ver = deps[pkgName];
|
|
594
|
+
if (typeof ver === "string") {
|
|
595
|
+
const major = ver.replace(/^[\^~>=<]*/, "").split(".")[0];
|
|
596
|
+
if (major && /^\d+$/.test(major)) {
|
|
597
|
+
versions[sdkKey] = major;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
} catch {
|
|
602
|
+
}
|
|
603
|
+
return versions;
|
|
604
|
+
}
|
|
605
|
+
var SdkMapCache = class {
|
|
606
|
+
constructor(_sdkMapsDir) {
|
|
607
|
+
this._sdkMapsDir = _sdkMapsDir;
|
|
608
|
+
}
|
|
609
|
+
_cache = /* @__PURE__ */ new Map();
|
|
610
|
+
_loading = /* @__PURE__ */ new Map();
|
|
611
|
+
/** Synchronous cache hit. Returns undefined on miss. */
|
|
612
|
+
get(sdk, version) {
|
|
613
|
+
return this._cache.get(`${sdk}@${version}`);
|
|
614
|
+
}
|
|
615
|
+
/** Load a map from disk. Returns cached if already loaded. */
|
|
616
|
+
async load(sdk, version) {
|
|
617
|
+
const key = `${sdk}@${version}`;
|
|
618
|
+
const cached = this._cache.get(key);
|
|
619
|
+
if (cached) return cached;
|
|
620
|
+
const inflight = this._loading.get(key);
|
|
621
|
+
if (inflight) return inflight;
|
|
622
|
+
const promise = this._loadFromDisk(sdk, version, key);
|
|
623
|
+
this._loading.set(key, promise);
|
|
624
|
+
try {
|
|
625
|
+
return await promise;
|
|
626
|
+
} finally {
|
|
627
|
+
this._loading.delete(key);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
_warnedSdks = /* @__PURE__ */ new Set();
|
|
631
|
+
async _loadFromDisk(sdk, version, key) {
|
|
632
|
+
try {
|
|
633
|
+
const filePath = path.join(this._sdkMapsDir, sdk, `${version}.json`);
|
|
634
|
+
const raw = await fs.readFile(filePath, "utf-8");
|
|
635
|
+
const map = JSON.parse(raw);
|
|
636
|
+
this._cache.set(key, map);
|
|
637
|
+
return map;
|
|
638
|
+
} catch {
|
|
639
|
+
if (!this._warnedSdks.has(sdk)) {
|
|
640
|
+
this._warnedSdks.add(sdk);
|
|
641
|
+
console.warn(`[VibeCheck] SDK map not found for ${sdk}@${version} \u2014 API validation skipped for this SDK`);
|
|
642
|
+
}
|
|
643
|
+
const empty = {};
|
|
644
|
+
this._cache.set(key, empty);
|
|
645
|
+
return empty;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
/** Pre-warm cache for known SDKs. Non-blocking. */
|
|
649
|
+
async warmup(versions, defaults) {
|
|
650
|
+
const tasks = [];
|
|
651
|
+
const allSdks = /* @__PURE__ */ new Set([...Object.keys(versions), ...Object.keys(defaults)]);
|
|
652
|
+
for (const sdk of allSdks) {
|
|
653
|
+
const version = versions[sdk] ?? defaults[sdk];
|
|
654
|
+
if (version) tasks.push(this.load(sdk, version));
|
|
655
|
+
}
|
|
656
|
+
await Promise.allSettled(tasks);
|
|
657
|
+
}
|
|
658
|
+
clear() {
|
|
659
|
+
this._cache.clear();
|
|
660
|
+
this._loading.clear();
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
var DEFAULT_VERSIONS = {
|
|
664
|
+
stripe: "14",
|
|
665
|
+
openai: "4",
|
|
666
|
+
anthropic: "0",
|
|
667
|
+
prisma: "5",
|
|
668
|
+
supabase: "2",
|
|
669
|
+
firebase: "10",
|
|
670
|
+
resend: "3",
|
|
671
|
+
clerk: "5",
|
|
672
|
+
ai: "3",
|
|
673
|
+
drizzle: "0",
|
|
674
|
+
trpc: "11",
|
|
675
|
+
hono: "4",
|
|
676
|
+
zod: "3"
|
|
677
|
+
};
|
|
678
|
+
var APITruthEngine = class {
|
|
679
|
+
constructor(confidenceThreshold = 0.75, sdkMapsDir, _workspaceRoot) {
|
|
680
|
+
this._workspaceRoot = _workspaceRoot;
|
|
681
|
+
this._confidenceThreshold = confidenceThreshold;
|
|
682
|
+
this._sdkMapCache = new SdkMapCache(
|
|
683
|
+
sdkMapsDir ?? path.join(__dirname$1, "sdk-maps")
|
|
684
|
+
);
|
|
685
|
+
this._patternRules = [...DEFAULT_PATTERN_RULES];
|
|
686
|
+
for (const [hallucinated, correct] of KNOWN_HALLUCINATIONS) {
|
|
687
|
+
this._trie.insert(hallucinated, correct);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
id = "api_truth";
|
|
691
|
+
_trie = new HallucinationTrie();
|
|
692
|
+
_patternRules;
|
|
693
|
+
_sdkMapCache;
|
|
694
|
+
_confidenceThreshold;
|
|
695
|
+
_sdkVersions = {};
|
|
696
|
+
_activated = false;
|
|
697
|
+
// Stats
|
|
698
|
+
_stats = {
|
|
699
|
+
filesScanned: 0,
|
|
700
|
+
filesSkipped: 0,
|
|
701
|
+
findingsEmitted: 0,
|
|
702
|
+
hallucationHits: 0,
|
|
703
|
+
sdkMapHits: 0,
|
|
704
|
+
patternRuleHits: 0,
|
|
705
|
+
avgScanMs: 0
|
|
706
|
+
};
|
|
707
|
+
_totalScanMs = 0;
|
|
708
|
+
/**
|
|
709
|
+
* Async activation: detect versions from package.json, warm SDK map cache.
|
|
710
|
+
* Call once after construction. Scans will still work without activation
|
|
711
|
+
* (using defaults), but version-specific maps may not be loaded.
|
|
712
|
+
*/
|
|
713
|
+
async activate() {
|
|
714
|
+
if (this._workspaceRoot) {
|
|
715
|
+
this._sdkVersions = detectSdkVersions(this._workspaceRoot);
|
|
716
|
+
}
|
|
717
|
+
await this._sdkMapCache.warmup(this._sdkVersions, DEFAULT_VERSIONS);
|
|
718
|
+
this._activated = true;
|
|
719
|
+
}
|
|
720
|
+
/** Add a custom pattern rule at runtime. */
|
|
721
|
+
addPatternRule(rule) {
|
|
722
|
+
this._patternRules.push(rule);
|
|
723
|
+
}
|
|
724
|
+
/** Remove a pattern rule by ruleId. */
|
|
725
|
+
removePatternRule(ruleId) {
|
|
726
|
+
const idx = this._patternRules.findIndex((r) => r.ruleId === ruleId);
|
|
727
|
+
if (idx !== -1) this._patternRules.splice(idx, 1);
|
|
728
|
+
}
|
|
729
|
+
/** Current engine stats. */
|
|
730
|
+
get stats() {
|
|
731
|
+
return { ...this._stats };
|
|
732
|
+
}
|
|
733
|
+
// ── Main Scan ────────────────────────────────────────────────────────────
|
|
734
|
+
async scan(delta, signal) {
|
|
735
|
+
if (signal?.aborted) return [];
|
|
736
|
+
const t0 = performance.now();
|
|
737
|
+
try {
|
|
738
|
+
const imports = detectImports(delta.fullText);
|
|
739
|
+
if (imports.length === 0) {
|
|
740
|
+
this._stats.filesSkipped++;
|
|
741
|
+
return [];
|
|
742
|
+
}
|
|
743
|
+
this._stats.filesScanned++;
|
|
744
|
+
const localToSdk = /* @__PURE__ */ new Map();
|
|
745
|
+
for (const imp of imports) {
|
|
746
|
+
localToSdk.set(imp.localName, imp.sdk);
|
|
747
|
+
localToSdk.set(imp.localName.toLowerCase(), imp.sdk);
|
|
748
|
+
}
|
|
749
|
+
const lines = delta.lines ?? delta.fullText.split("\n");
|
|
750
|
+
const regions = buildCodeRegions(lines);
|
|
751
|
+
const findings = [];
|
|
752
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
753
|
+
if (signal?.aborted) break;
|
|
754
|
+
const region = regions[lineNum];
|
|
755
|
+
if (region.inComment) continue;
|
|
756
|
+
const line = lines[lineNum];
|
|
757
|
+
SDK_CALL_RE.lastIndex = 0;
|
|
758
|
+
let m;
|
|
759
|
+
while ((m = SDK_CALL_RE.exec(line)) !== null) {
|
|
760
|
+
if (isInString(region, m.index)) continue;
|
|
761
|
+
const objPath = m[1];
|
|
762
|
+
const method = m[2];
|
|
763
|
+
;
|
|
764
|
+
const callPath = `${objPath}.${method}`;
|
|
765
|
+
const rootObj = objPath.split(".")[0];
|
|
766
|
+
const sdk = localToSdk.get(rootObj) ?? localToSdk.get(rootObj.toLowerCase());
|
|
767
|
+
if (!sdk) continue;
|
|
768
|
+
const normalizedPath = callPath.replace(
|
|
769
|
+
new RegExp(`^${rootObj}`, "i"),
|
|
770
|
+
sdk
|
|
771
|
+
);
|
|
772
|
+
const knownSuggestion = this._trie.lookup(normalizedPath);
|
|
773
|
+
if (knownSuggestion !== void 0) {
|
|
774
|
+
const confidence2 = 0.95;
|
|
775
|
+
if (confidence2 >= this._confidenceThreshold) {
|
|
776
|
+
this._stats.hallucationHits++;
|
|
777
|
+
findings.push({
|
|
778
|
+
id: deterministicId(delta.documentUri, lineNum + 1, m.index, "HAL002", callPath),
|
|
779
|
+
engine: this.id,
|
|
780
|
+
severity: "critical",
|
|
781
|
+
category: "api_truth",
|
|
782
|
+
file: delta.documentUri,
|
|
783
|
+
line: lineNum + 1,
|
|
784
|
+
column: m.index,
|
|
785
|
+
endLine: lineNum + 1,
|
|
786
|
+
endColumn: m.index + callPath.length + 1,
|
|
787
|
+
message: `\`${callPath}()\` does not exist \u2014 AI hallucinated this ${sdk} API call`,
|
|
788
|
+
evidence: `${callPath}()`,
|
|
789
|
+
suggestion: `Use: \`${knownSuggestion.replace(sdk, rootObj)}()\``,
|
|
790
|
+
confidence: confidence2,
|
|
791
|
+
autoFixable: !knownSuggestion.includes("<model>"),
|
|
792
|
+
ruleId: "HAL002"
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
continue;
|
|
796
|
+
}
|
|
797
|
+
const version = this._sdkVersions[sdk] ?? DEFAULT_VERSIONS[sdk];
|
|
798
|
+
if (!version) continue;
|
|
799
|
+
const map = this._sdkMapCache.get(sdk, version);
|
|
800
|
+
if (!map || Object.keys(map).length === 0) continue;
|
|
801
|
+
if (map[normalizedPath]?.exists) {
|
|
802
|
+
if (map[normalizedPath].deprecated) {
|
|
803
|
+
const replacement = map[normalizedPath].replacement;
|
|
804
|
+
findings.push({
|
|
805
|
+
id: deterministicId(delta.documentUri, lineNum + 1, m.index, "HAL006", callPath),
|
|
806
|
+
engine: this.id,
|
|
807
|
+
severity: "low",
|
|
808
|
+
category: "api_truth",
|
|
809
|
+
file: delta.documentUri,
|
|
810
|
+
line: lineNum + 1,
|
|
811
|
+
column: m.index,
|
|
812
|
+
endLine: lineNum + 1,
|
|
813
|
+
endColumn: m.index + callPath.length + 1,
|
|
814
|
+
message: `\`${callPath}()\` is deprecated in ${sdk}@${version}.x`,
|
|
815
|
+
evidence: `${callPath}()`,
|
|
816
|
+
suggestion: replacement ? `Use: \`${replacement.replace(sdk, rootObj)}()\`` : `Check the ${sdk} migration guide for the replacement.`,
|
|
817
|
+
confidence: 0.9,
|
|
818
|
+
autoFixable: !!replacement,
|
|
819
|
+
ruleId: "HAL006"
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
const closest = findClosestMatch(map, normalizedPath);
|
|
825
|
+
const confidence = closest ? 0.84 : 0.78;
|
|
826
|
+
if (confidence < this._confidenceThreshold) continue;
|
|
827
|
+
this._stats.sdkMapHits++;
|
|
828
|
+
findings.push({
|
|
829
|
+
id: deterministicId(delta.documentUri, lineNum + 1, m.index, "HAL002", callPath),
|
|
830
|
+
engine: this.id,
|
|
831
|
+
severity: "critical",
|
|
832
|
+
category: "api_truth",
|
|
833
|
+
file: delta.documentUri,
|
|
834
|
+
line: lineNum + 1,
|
|
835
|
+
column: m.index,
|
|
836
|
+
endLine: lineNum + 1,
|
|
837
|
+
endColumn: m.index + callPath.length + 1,
|
|
838
|
+
message: `\`${callPath}()\` not found in ${sdk}@${version}.x API surface`,
|
|
839
|
+
evidence: `${callPath}()`,
|
|
840
|
+
suggestion: closest ? `Did you mean: \`${closest.replace(sdk, rootObj)}()\`?` : `Check the ${sdk}@${version}.x docs for the correct method.`,
|
|
841
|
+
confidence,
|
|
842
|
+
autoFixable: !!closest,
|
|
843
|
+
ruleId: "HAL002"
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
const fileExt = path.extname(delta.documentUri).toLowerCase();
|
|
847
|
+
for (const rule of this._patternRules) {
|
|
848
|
+
if (rule.confidence < this._confidenceThreshold) continue;
|
|
849
|
+
if (rule.extensions && !rule.extensions.has(fileExt)) continue;
|
|
850
|
+
rule.regex.lastIndex = 0;
|
|
851
|
+
const pm = rule.regex.exec(line);
|
|
852
|
+
if (!pm) continue;
|
|
853
|
+
if (isInString(region, pm.index)) continue;
|
|
854
|
+
this._stats.patternRuleHits++;
|
|
855
|
+
findings.push({
|
|
856
|
+
id: deterministicId(delta.documentUri, lineNum + 1, pm.index, rule.ruleId, pm[0]),
|
|
857
|
+
engine: this.id,
|
|
858
|
+
severity: rule.severity ?? "high",
|
|
859
|
+
category: "api_truth",
|
|
860
|
+
file: delta.documentUri,
|
|
861
|
+
line: lineNum + 1,
|
|
862
|
+
column: pm.index,
|
|
863
|
+
endLine: lineNum + 1,
|
|
864
|
+
endColumn: pm.index + pm[0].length,
|
|
865
|
+
message: rule.message,
|
|
866
|
+
evidence: pm[0],
|
|
867
|
+
suggestion: rule.suggestion,
|
|
868
|
+
confidence: rule.confidence,
|
|
869
|
+
autoFixable: false,
|
|
870
|
+
ruleId: rule.ruleId
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
const elapsed = performance.now() - t0;
|
|
875
|
+
this._totalScanMs += elapsed;
|
|
876
|
+
this._stats.findingsEmitted += findings.length;
|
|
877
|
+
this._stats.avgScanMs = Math.round(this._totalScanMs / this._stats.filesScanned);
|
|
878
|
+
return findings;
|
|
879
|
+
} catch (error) {
|
|
880
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
881
|
+
console.error(`[APITruthEngine] Scan failed for ${delta.documentUri}:`, errorMsg);
|
|
882
|
+
return [{
|
|
883
|
+
id: deterministicId(delta.documentUri, 1, 0, "ENGINE_ERROR", "scan_failure"),
|
|
884
|
+
engine: this.id,
|
|
885
|
+
severity: "medium",
|
|
886
|
+
category: "engine_error",
|
|
887
|
+
file: delta.documentUri,
|
|
888
|
+
line: 1,
|
|
889
|
+
column: 0,
|
|
890
|
+
endLine: 1,
|
|
891
|
+
endColumn: 0,
|
|
892
|
+
message: `API Truth Engine scan failed: ${errorMsg}`,
|
|
893
|
+
evidence: "Engine internal error",
|
|
894
|
+
suggestion: "Check file syntax and report this issue if it persists",
|
|
895
|
+
confidence: 1,
|
|
896
|
+
autoFixable: false,
|
|
897
|
+
ruleId: "ENGINE_ERROR"
|
|
898
|
+
}];
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
// ── Lifecycle ────────────────────────────────────────────────────────────
|
|
902
|
+
/** Refresh SDK versions (e.g. after npm install or branch switch). */
|
|
903
|
+
async refreshVersions() {
|
|
904
|
+
if (this._workspaceRoot) {
|
|
905
|
+
this._sdkVersions = detectSdkVersions(this._workspaceRoot);
|
|
906
|
+
}
|
|
907
|
+
this._sdkMapCache.clear();
|
|
908
|
+
await this._sdkMapCache.warmup(this._sdkVersions, DEFAULT_VERSIONS);
|
|
909
|
+
}
|
|
910
|
+
dispose() {
|
|
911
|
+
this._sdkMapCache.clear();
|
|
912
|
+
}
|
|
913
|
+
};
|
|
914
|
+
|
|
915
|
+
export { APITruthEngine };
|