@vibecheckai/cli 3.0.4 → 3.0.7
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/bin/dev/run-v2-torture.js +30 -0
- package/bin/runners/context/index.js +1 -1
- package/bin/runners/lib/analyzers.js +38 -0
- package/bin/runners/lib/assets/vibecheck-logo.png +0 -0
- package/bin/runners/lib/contracts/auth-contract.js +8 -0
- package/bin/runners/lib/contracts/env-contract.js +3 -0
- package/bin/runners/lib/contracts/external-contract.js +10 -2
- package/bin/runners/lib/contracts/route-contract.js +7 -0
- package/bin/runners/lib/contracts.js +804 -0
- package/bin/runners/lib/detectors-v2.js +703 -0
- package/bin/runners/lib/drift.js +425 -0
- package/bin/runners/lib/entitlements-v2.js +3 -1
- package/bin/runners/lib/entitlements.js +11 -3
- package/bin/runners/lib/env-resolver.js +417 -0
- package/bin/runners/lib/extractors/client-calls.js +990 -0
- package/bin/runners/lib/extractors/fastify-route-dump.js +573 -0
- package/bin/runners/lib/extractors/fastify-routes.js +426 -0
- package/bin/runners/lib/extractors/index.js +363 -0
- package/bin/runners/lib/extractors/next-routes.js +524 -0
- package/bin/runners/lib/extractors/proof-graph.js +431 -0
- package/bin/runners/lib/extractors/route-matcher.js +451 -0
- package/bin/runners/lib/extractors/truthpack-v2.js +377 -0
- package/bin/runners/lib/extractors/ui-bindings.js +547 -0
- package/bin/runners/lib/findings-schema.js +281 -0
- package/bin/runners/lib/html-report.js +650 -0
- package/bin/runners/lib/missions/templates.js +45 -0
- package/bin/runners/lib/policy.js +295 -0
- package/bin/runners/lib/reality/correlation-detectors.js +359 -0
- package/bin/runners/lib/reality/index.js +318 -0
- package/bin/runners/lib/reality/request-hashing.js +416 -0
- package/bin/runners/lib/reality/request-mapper.js +453 -0
- package/bin/runners/lib/reality/safety-rails.js +463 -0
- package/bin/runners/lib/reality/semantic-snapshot.js +408 -0
- package/bin/runners/lib/reality/toast-detector.js +393 -0
- package/bin/runners/lib/report-html.js +5 -0
- package/bin/runners/lib/report-templates.js +5 -0
- package/bin/runners/lib/report.js +135 -0
- package/bin/runners/lib/route-truth.js +10 -10
- package/bin/runners/lib/schema-validator.js +350 -0
- package/bin/runners/lib/schemas/contracts.schema.json +160 -0
- package/bin/runners/lib/schemas/finding.schema.json +100 -0
- package/bin/runners/lib/schemas/mission-pack.schema.json +206 -0
- package/bin/runners/lib/schemas/proof-graph.schema.json +176 -0
- package/bin/runners/lib/schemas/reality-report.schema.json +162 -0
- package/bin/runners/lib/schemas/share-pack.schema.json +180 -0
- package/bin/runners/lib/schemas/ship-report.schema.json +117 -0
- package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -0
- package/bin/runners/lib/schemas/validator.js +438 -0
- package/bin/runners/lib/ui.js +562 -0
- package/bin/runners/lib/verdict-engine.js +628 -0
- package/bin/runners/runAIAgent.js +228 -1
- package/bin/runners/runBadge.js +181 -1
- package/bin/runners/runCtx.js +7 -2
- package/bin/runners/runCtxDiff.js +301 -0
- package/bin/runners/runGuard.js +168 -0
- package/bin/runners/runInitGha.js +78 -15
- package/bin/runners/runLabs.js +341 -0
- package/bin/runners/runLaunch.js +180 -1
- package/bin/runners/runMdc.js +203 -1
- package/bin/runners/runProof.zip +0 -0
- package/bin/runners/runProve.js +23 -0
- package/bin/runners/runReplay.js +114 -84
- package/bin/runners/runScan.js +111 -32
- package/bin/runners/runShip.js +23 -2
- package/bin/runners/runTruthpack.js +9 -7
- package/bin/runners/runValidate.js +161 -1
- package/bin/vibecheck.js +416 -770
- package/mcp-server/.guardrail/audit/audit.log.jsonl +2 -0
- package/mcp-server/.specs/architecture.mdc +90 -0
- package/mcp-server/.specs/security.mdc +30 -0
- package/mcp-server/README.md +252 -0
- package/mcp-server/agent-checkpoint.js +364 -0
- package/mcp-server/architect-tools.js +707 -0
- package/mcp-server/audit-mcp.js +206 -0
- package/mcp-server/codebase-architect-tools.js +838 -0
- package/mcp-server/consolidated-tools.js +804 -0
- package/mcp-server/hygiene-tools.js +428 -0
- package/mcp-server/index-v1.js +698 -0
- package/mcp-server/index.js +2092 -0
- package/mcp-server/index.old.js +4137 -0
- package/mcp-server/intelligence-tools.js +664 -0
- package/mcp-server/intent-drift-tools.js +873 -0
- package/mcp-server/mdc-generator.js +298 -0
- package/mcp-server/package-lock.json +165 -0
- package/mcp-server/package.json +47 -0
- package/mcp-server/premium-tools.js +1275 -0
- package/mcp-server/test-mcp.js +108 -0
- package/mcp-server/test-tools.js +36 -0
- package/mcp-server/tier-auth.js +147 -0
- package/mcp-server/tools/index.js +72 -0
- package/mcp-server/tools-reorganized.ts +244 -0
- package/mcp-server/truth-context.js +581 -0
- package/mcp-server/truth-firewall-tools.js +1500 -0
- package/mcp-server/vibecheck-2.0-tools.js +748 -0
- package/mcp-server/vibecheck-tools.js +1075 -0
- package/package.json +10 -8
- package/bin/guardrail.js +0 -834
- package/bin/runners/runAudit.js +0 -2
- package/bin/runners/runAutopilot.js +0 -2
- package/bin/runners/runCertify.js +0 -2
- package/bin/runners/runDashboard.js +0 -10
- package/bin/runners/runEnhancedShip.js +0 -2
- package/bin/runners/runFixPacks.js +0 -2
- package/bin/runners/runNaturalLanguage.js +0 -3
- package/bin/runners/runProof.js +0 -2
- package/bin/runners/runRealitySniff.js +0 -2
- package/bin/runners/runUpgrade.js +0 -2
- package/bin/runners/runVerifyAgentOutput.js +0 -2
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Truth Context - MCP Tools for Evidence-Backed AI
|
|
3
|
+
*
|
|
4
|
+
* Core Context Engine tools that provide truth-backed context for AI agents.
|
|
5
|
+
* All responses include citations (file/line) and confidence levels.
|
|
6
|
+
*
|
|
7
|
+
* This is the "Truth Firewall" made visible as "Evidence Pack" / "Truth Pack".
|
|
8
|
+
*
|
|
9
|
+
* Tools:
|
|
10
|
+
* vibecheck.ctx - Get repo truth bundle (routes, auth, billing, env, schema)
|
|
11
|
+
* vibecheck.verify_claim - Verify a claim has evidence
|
|
12
|
+
* vibecheck.evidence - Get evidence for a specific file/function
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import fs from "fs/promises";
|
|
16
|
+
import path from "path";
|
|
17
|
+
import { execSync } from "child_process";
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// TRUTH CONTEXT TOOLS
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
export const TRUTH_CONTEXT_TOOLS = [
|
|
24
|
+
{
|
|
25
|
+
name: "vibecheck.ctx",
|
|
26
|
+
description: `📋 Get repo Truth Pack — routes, auth, billing, env vars, schema.
|
|
27
|
+
|
|
28
|
+
Returns evidence-backed context with file/line citations.
|
|
29
|
+
Use this before making any claims about the codebase.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
- routes: All defined routes with handlers and middleware
|
|
33
|
+
- auth: Auth guards, protected routes, auth flow
|
|
34
|
+
- billing: Payment gates, subscription checks, paid features
|
|
35
|
+
- env: Environment variables (declared vs used)
|
|
36
|
+
- schema: Database schema, API contracts
|
|
37
|
+
- confidence: Overall confidence score (0-1)`,
|
|
38
|
+
inputSchema: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
scope: {
|
|
42
|
+
type: "string",
|
|
43
|
+
enum: ["all", "routes", "auth", "billing", "env", "schema"],
|
|
44
|
+
description: "What context to extract (default: all)",
|
|
45
|
+
default: "all",
|
|
46
|
+
},
|
|
47
|
+
path: {
|
|
48
|
+
type: "string",
|
|
49
|
+
description: "Project path (default: current directory)",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "vibecheck.verify_claim",
|
|
56
|
+
description: `🔍 Verify a claim has evidence — Truth Firewall check.
|
|
57
|
+
|
|
58
|
+
Before claiming something exists or works, verify it.
|
|
59
|
+
Returns evidence (file/line) or rejection with reason.
|
|
60
|
+
|
|
61
|
+
Examples:
|
|
62
|
+
- "Route /api/users exists" → Verified with handler at src/routes/users.ts:45
|
|
63
|
+
- "Auth is required for /admin" → Verified with middleware at src/middleware/auth.ts:12
|
|
64
|
+
- "Stripe is configured" → REJECTED: No evidence of Stripe integration found`,
|
|
65
|
+
inputSchema: {
|
|
66
|
+
type: "object",
|
|
67
|
+
properties: {
|
|
68
|
+
claim_type: {
|
|
69
|
+
type: "string",
|
|
70
|
+
enum: ["route", "endpoint", "env_var", "middleware", "auth_guard", "billing_gate", "file", "function"],
|
|
71
|
+
description: "Type of claim to verify",
|
|
72
|
+
},
|
|
73
|
+
claim: {
|
|
74
|
+
type: "string",
|
|
75
|
+
description: "The claim to verify (e.g., '/api/users', 'AUTH_SECRET', 'authMiddleware')",
|
|
76
|
+
},
|
|
77
|
+
path: {
|
|
78
|
+
type: "string",
|
|
79
|
+
description: "Project path (default: current directory)",
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
required: ["claim_type", "claim"],
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "vibecheck.evidence",
|
|
87
|
+
description: `📎 Get evidence for a file/function — citations with context.
|
|
88
|
+
|
|
89
|
+
Returns the actual code with line numbers for citation.
|
|
90
|
+
Use this when you need to reference specific code in your response.`,
|
|
91
|
+
inputSchema: {
|
|
92
|
+
type: "object",
|
|
93
|
+
properties: {
|
|
94
|
+
file: {
|
|
95
|
+
type: "string",
|
|
96
|
+
description: "File path relative to project root",
|
|
97
|
+
},
|
|
98
|
+
function_name: {
|
|
99
|
+
type: "string",
|
|
100
|
+
description: "Optional function/class name to find",
|
|
101
|
+
},
|
|
102
|
+
line: {
|
|
103
|
+
type: "number",
|
|
104
|
+
description: "Optional specific line number",
|
|
105
|
+
},
|
|
106
|
+
context_lines: {
|
|
107
|
+
type: "number",
|
|
108
|
+
description: "Lines of context around target (default: 10)",
|
|
109
|
+
default: 10,
|
|
110
|
+
},
|
|
111
|
+
path: {
|
|
112
|
+
type: "string",
|
|
113
|
+
description: "Project path (default: current directory)",
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
required: ["file"],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
// ============================================================================
|
|
122
|
+
// TOOL HANDLERS
|
|
123
|
+
// ============================================================================
|
|
124
|
+
|
|
125
|
+
export async function handleTruthContextTool(toolName, args) {
|
|
126
|
+
const projectPath = args.path || process.cwd();
|
|
127
|
+
|
|
128
|
+
switch (toolName) {
|
|
129
|
+
case "vibecheck.ctx":
|
|
130
|
+
return await getTruthPack(projectPath, args.scope || "all");
|
|
131
|
+
case "vibecheck.verify_claim":
|
|
132
|
+
return await verifyClaim(projectPath, args.claim_type, args.claim);
|
|
133
|
+
case "vibecheck.evidence":
|
|
134
|
+
return await getEvidence(projectPath, args.file, args);
|
|
135
|
+
default:
|
|
136
|
+
return { error: `Unknown tool: ${toolName}` };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ============================================================================
|
|
141
|
+
// CONTEXT EXTRACTION
|
|
142
|
+
// ============================================================================
|
|
143
|
+
|
|
144
|
+
async function getTruthPack(projectPath, scope) {
|
|
145
|
+
const truthPack = {
|
|
146
|
+
version: "1.0.0",
|
|
147
|
+
generatedAt: new Date().toISOString(),
|
|
148
|
+
projectPath,
|
|
149
|
+
scope,
|
|
150
|
+
confidence: 0,
|
|
151
|
+
sections: {},
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
if (scope === "all" || scope === "routes") {
|
|
156
|
+
truthPack.sections.routes = await extractRoutes(projectPath);
|
|
157
|
+
}
|
|
158
|
+
if (scope === "all" || scope === "auth") {
|
|
159
|
+
truthPack.sections.auth = await extractAuth(projectPath);
|
|
160
|
+
}
|
|
161
|
+
if (scope === "all" || scope === "billing") {
|
|
162
|
+
truthPack.sections.billing = await extractBilling(projectPath);
|
|
163
|
+
}
|
|
164
|
+
if (scope === "all" || scope === "env") {
|
|
165
|
+
truthPack.sections.env = await extractEnvVars(projectPath);
|
|
166
|
+
}
|
|
167
|
+
if (scope === "all" || scope === "schema") {
|
|
168
|
+
truthPack.sections.schema = await extractSchema(projectPath);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Calculate overall confidence
|
|
172
|
+
const sections = Object.values(truthPack.sections);
|
|
173
|
+
if (sections.length > 0) {
|
|
174
|
+
truthPack.confidence = sections.reduce((sum, s) => sum + (s.confidence || 0), 0) / sections.length;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return truthPack;
|
|
178
|
+
} catch (error) {
|
|
179
|
+
return {
|
|
180
|
+
error: error.message,
|
|
181
|
+
projectPath,
|
|
182
|
+
suggestion: "Run 'vibecheck init' to set up the project",
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function extractRoutes(projectPath) {
|
|
188
|
+
const routes = [];
|
|
189
|
+
const routePatterns = [
|
|
190
|
+
/app\.(get|post|put|patch|delete|use)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
|
|
191
|
+
/router\.(get|post|put|patch|delete|use)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
|
|
192
|
+
/@(Get|Post|Put|Patch|Delete)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
const files = await findSourceFiles(projectPath, [".ts", ".js", ".tsx", ".jsx"]);
|
|
196
|
+
|
|
197
|
+
for (const file of files.slice(0, 50)) { // Limit for performance
|
|
198
|
+
try {
|
|
199
|
+
const content = await fs.readFile(file, "utf8");
|
|
200
|
+
const relPath = path.relative(projectPath, file);
|
|
201
|
+
|
|
202
|
+
for (const pattern of routePatterns) {
|
|
203
|
+
let match;
|
|
204
|
+
pattern.lastIndex = 0;
|
|
205
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
206
|
+
const line = content.substring(0, match.index).split("\n").length;
|
|
207
|
+
routes.push({
|
|
208
|
+
method: match[1].toUpperCase(),
|
|
209
|
+
path: match[2],
|
|
210
|
+
file: relPath,
|
|
211
|
+
line,
|
|
212
|
+
evidence: {
|
|
213
|
+
snippet: content.split("\n")[line - 1]?.trim(),
|
|
214
|
+
verifiedAt: new Date().toISOString(),
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} catch {
|
|
220
|
+
// Skip unreadable files
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
count: routes.length,
|
|
226
|
+
routes: routes.slice(0, 100), // Limit output
|
|
227
|
+
confidence: routes.length > 0 ? 0.8 : 0.2,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async function extractAuth(projectPath) {
|
|
232
|
+
const authIndicators = [];
|
|
233
|
+
const authPatterns = [
|
|
234
|
+
/auth(enticate|orize|Middleware|Guard|Check)/gi,
|
|
235
|
+
/isAuthenticated|requireAuth|verifyToken|jwt\.verify/gi,
|
|
236
|
+
/passport\.(authenticate|use)/gi,
|
|
237
|
+
/session\.|cookie\./gi,
|
|
238
|
+
];
|
|
239
|
+
|
|
240
|
+
const files = await findSourceFiles(projectPath, [".ts", ".js"]);
|
|
241
|
+
|
|
242
|
+
for (const file of files.slice(0, 50)) {
|
|
243
|
+
try {
|
|
244
|
+
const content = await fs.readFile(file, "utf8");
|
|
245
|
+
const relPath = path.relative(projectPath, file);
|
|
246
|
+
|
|
247
|
+
for (const pattern of authPatterns) {
|
|
248
|
+
let match;
|
|
249
|
+
pattern.lastIndex = 0;
|
|
250
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
251
|
+
const line = content.substring(0, match.index).split("\n").length;
|
|
252
|
+
authIndicators.push({
|
|
253
|
+
type: "auth_indicator",
|
|
254
|
+
match: match[0],
|
|
255
|
+
file: relPath,
|
|
256
|
+
line,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
} catch {
|
|
261
|
+
// Skip
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
count: authIndicators.length,
|
|
267
|
+
indicators: authIndicators.slice(0, 50),
|
|
268
|
+
confidence: authIndicators.length > 5 ? 0.8 : authIndicators.length > 0 ? 0.5 : 0.1,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function extractBilling(projectPath) {
|
|
273
|
+
const billingIndicators = [];
|
|
274
|
+
const billingPatterns = [
|
|
275
|
+
/stripe|paddle|lemonsqueezy|gumroad/gi,
|
|
276
|
+
/subscription|payment|checkout|invoice/gi,
|
|
277
|
+
/isPro|isPremium|isEnterprise|hasPaid/gi,
|
|
278
|
+
/price|tier|plan/gi,
|
|
279
|
+
];
|
|
280
|
+
|
|
281
|
+
const files = await findSourceFiles(projectPath, [".ts", ".js"]);
|
|
282
|
+
|
|
283
|
+
for (const file of files.slice(0, 30)) {
|
|
284
|
+
try {
|
|
285
|
+
const content = await fs.readFile(file, "utf8");
|
|
286
|
+
const relPath = path.relative(projectPath, file);
|
|
287
|
+
|
|
288
|
+
for (const pattern of billingPatterns) {
|
|
289
|
+
let match;
|
|
290
|
+
pattern.lastIndex = 0;
|
|
291
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
292
|
+
const line = content.substring(0, match.index).split("\n").length;
|
|
293
|
+
billingIndicators.push({
|
|
294
|
+
type: "billing_indicator",
|
|
295
|
+
match: match[0],
|
|
296
|
+
file: relPath,
|
|
297
|
+
line,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
} catch {
|
|
302
|
+
// Skip
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
count: billingIndicators.length,
|
|
308
|
+
indicators: billingIndicators.slice(0, 30),
|
|
309
|
+
confidence: billingIndicators.length > 3 ? 0.7 : billingIndicators.length > 0 ? 0.4 : 0.1,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async function extractEnvVars(projectPath) {
|
|
314
|
+
const declared = [];
|
|
315
|
+
const used = [];
|
|
316
|
+
|
|
317
|
+
// Check .env.example, .env.local.example, etc.
|
|
318
|
+
const envFiles = [".env.example", ".env.local.example", ".env.sample"];
|
|
319
|
+
for (const envFile of envFiles) {
|
|
320
|
+
try {
|
|
321
|
+
const content = await fs.readFile(path.join(projectPath, envFile), "utf8");
|
|
322
|
+
const lines = content.split("\n");
|
|
323
|
+
for (let i = 0; i < lines.length; i++) {
|
|
324
|
+
const match = lines[i].match(/^([A-Z][A-Z0-9_]*)=/);
|
|
325
|
+
if (match) {
|
|
326
|
+
declared.push({
|
|
327
|
+
name: match[1],
|
|
328
|
+
file: envFile,
|
|
329
|
+
line: i + 1,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
} catch {
|
|
334
|
+
// File doesn't exist
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Find process.env usage in code
|
|
339
|
+
const files = await findSourceFiles(projectPath, [".ts", ".js"]);
|
|
340
|
+
for (const file of files.slice(0, 30)) {
|
|
341
|
+
try {
|
|
342
|
+
const content = await fs.readFile(file, "utf8");
|
|
343
|
+
const relPath = path.relative(projectPath, file);
|
|
344
|
+
const pattern = /process\.env\.([A-Z][A-Z0-9_]*)/g;
|
|
345
|
+
let match;
|
|
346
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
347
|
+
const line = content.substring(0, match.index).split("\n").length;
|
|
348
|
+
used.push({
|
|
349
|
+
name: match[1],
|
|
350
|
+
file: relPath,
|
|
351
|
+
line,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
} catch {
|
|
355
|
+
// Skip
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Find mismatches
|
|
360
|
+
const declaredNames = new Set(declared.map(d => d.name));
|
|
361
|
+
const usedNames = new Set(used.map(u => u.name));
|
|
362
|
+
const undeclared = [...usedNames].filter(n => !declaredNames.has(n));
|
|
363
|
+
const unused = [...declaredNames].filter(n => !usedNames.has(n));
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
declared: declared.slice(0, 50),
|
|
367
|
+
used: used.slice(0, 50),
|
|
368
|
+
mismatches: {
|
|
369
|
+
undeclared,
|
|
370
|
+
unused,
|
|
371
|
+
},
|
|
372
|
+
confidence: undeclared.length === 0 ? 0.9 : 0.5,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
async function extractSchema(projectPath) {
|
|
377
|
+
const schemas = [];
|
|
378
|
+
|
|
379
|
+
// Check for Prisma schema
|
|
380
|
+
try {
|
|
381
|
+
const prismaPath = path.join(projectPath, "prisma", "schema.prisma");
|
|
382
|
+
const content = await fs.readFile(prismaPath, "utf8");
|
|
383
|
+
const modelMatches = content.matchAll(/model\s+(\w+)\s*\{/g);
|
|
384
|
+
for (const match of modelMatches) {
|
|
385
|
+
schemas.push({
|
|
386
|
+
type: "prisma_model",
|
|
387
|
+
name: match[1],
|
|
388
|
+
file: "prisma/schema.prisma",
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
} catch {
|
|
392
|
+
// No Prisma
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Check for TypeScript types/interfaces
|
|
396
|
+
const files = await findSourceFiles(projectPath, [".ts", ".tsx"]);
|
|
397
|
+
for (const file of files.slice(0, 20)) {
|
|
398
|
+
try {
|
|
399
|
+
const content = await fs.readFile(file, "utf8");
|
|
400
|
+
const relPath = path.relative(projectPath, file);
|
|
401
|
+
|
|
402
|
+
const typeMatches = content.matchAll(/(?:interface|type)\s+(\w+)/g);
|
|
403
|
+
for (const match of typeMatches) {
|
|
404
|
+
const line = content.substring(0, match.index).split("\n").length;
|
|
405
|
+
schemas.push({
|
|
406
|
+
type: "typescript_type",
|
|
407
|
+
name: match[1],
|
|
408
|
+
file: relPath,
|
|
409
|
+
line,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
} catch {
|
|
413
|
+
// Skip
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return {
|
|
418
|
+
count: schemas.length,
|
|
419
|
+
schemas: schemas.slice(0, 50),
|
|
420
|
+
confidence: schemas.length > 5 ? 0.7 : schemas.length > 0 ? 0.4 : 0.2,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ============================================================================
|
|
425
|
+
// CLAIM VERIFICATION
|
|
426
|
+
// ============================================================================
|
|
427
|
+
|
|
428
|
+
async function verifyClaim(projectPath, claimType, claim) {
|
|
429
|
+
const result = {
|
|
430
|
+
claim: { type: claimType, value: claim },
|
|
431
|
+
verified: false,
|
|
432
|
+
evidence: null,
|
|
433
|
+
confidence: 0,
|
|
434
|
+
rejection: null,
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
try {
|
|
438
|
+
switch (claimType) {
|
|
439
|
+
case "file":
|
|
440
|
+
const filePath = path.join(projectPath, claim);
|
|
441
|
+
try {
|
|
442
|
+
await fs.access(filePath);
|
|
443
|
+
const stats = await fs.stat(filePath);
|
|
444
|
+
result.verified = true;
|
|
445
|
+
result.confidence = 1.0;
|
|
446
|
+
result.evidence = {
|
|
447
|
+
file: claim,
|
|
448
|
+
exists: true,
|
|
449
|
+
size: stats.size,
|
|
450
|
+
verifiedAt: new Date().toISOString(),
|
|
451
|
+
};
|
|
452
|
+
} catch {
|
|
453
|
+
result.rejection = `File does not exist: ${claim}`;
|
|
454
|
+
}
|
|
455
|
+
break;
|
|
456
|
+
|
|
457
|
+
case "route":
|
|
458
|
+
case "endpoint":
|
|
459
|
+
const routes = await extractRoutes(projectPath);
|
|
460
|
+
const matchingRoute = routes.routes.find(r => r.path === claim || r.path.includes(claim));
|
|
461
|
+
if (matchingRoute) {
|
|
462
|
+
result.verified = true;
|
|
463
|
+
result.confidence = 0.9;
|
|
464
|
+
result.evidence = matchingRoute;
|
|
465
|
+
} else {
|
|
466
|
+
result.rejection = `No route matching "${claim}" found in codebase`;
|
|
467
|
+
}
|
|
468
|
+
break;
|
|
469
|
+
|
|
470
|
+
case "env_var":
|
|
471
|
+
const envData = await extractEnvVars(projectPath);
|
|
472
|
+
const isDeclared = envData.declared.some(d => d.name === claim);
|
|
473
|
+
const isUsed = envData.used.some(u => u.name === claim);
|
|
474
|
+
if (isDeclared || isUsed) {
|
|
475
|
+
result.verified = true;
|
|
476
|
+
result.confidence = isDeclared && isUsed ? 1.0 : 0.7;
|
|
477
|
+
result.evidence = {
|
|
478
|
+
declared: isDeclared,
|
|
479
|
+
used: isUsed,
|
|
480
|
+
locations: [...envData.declared.filter(d => d.name === claim), ...envData.used.filter(u => u.name === claim)],
|
|
481
|
+
};
|
|
482
|
+
} else {
|
|
483
|
+
result.rejection = `Environment variable "${claim}" not found`;
|
|
484
|
+
}
|
|
485
|
+
break;
|
|
486
|
+
|
|
487
|
+
default:
|
|
488
|
+
result.rejection = `Claim type "${claimType}" verification not yet implemented`;
|
|
489
|
+
}
|
|
490
|
+
} catch (error) {
|
|
491
|
+
result.rejection = `Verification error: ${error.message}`;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return result;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// ============================================================================
|
|
498
|
+
// EVIDENCE EXTRACTION
|
|
499
|
+
// ============================================================================
|
|
500
|
+
|
|
501
|
+
async function getEvidence(projectPath, file, options) {
|
|
502
|
+
const filePath = path.join(projectPath, file);
|
|
503
|
+
|
|
504
|
+
try {
|
|
505
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
506
|
+
const lines = content.split("\n");
|
|
507
|
+
|
|
508
|
+
let targetLine = options.line || 1;
|
|
509
|
+
const contextLines = options.context_lines || 10;
|
|
510
|
+
|
|
511
|
+
// If function_name provided, find it
|
|
512
|
+
if (options.function_name) {
|
|
513
|
+
const pattern = new RegExp(`(function|const|let|var|class)\\s+${options.function_name}`, "i");
|
|
514
|
+
for (let i = 0; i < lines.length; i++) {
|
|
515
|
+
if (pattern.test(lines[i])) {
|
|
516
|
+
targetLine = i + 1;
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const startLine = Math.max(1, targetLine - contextLines);
|
|
523
|
+
const endLine = Math.min(lines.length, targetLine + contextLines);
|
|
524
|
+
|
|
525
|
+
const snippet = lines.slice(startLine - 1, endLine)
|
|
526
|
+
.map((line, i) => `${String(startLine + i).padStart(4, " ")} | ${line}`)
|
|
527
|
+
.join("\n");
|
|
528
|
+
|
|
529
|
+
return {
|
|
530
|
+
file,
|
|
531
|
+
targetLine,
|
|
532
|
+
startLine,
|
|
533
|
+
endLine,
|
|
534
|
+
totalLines: lines.length,
|
|
535
|
+
snippet,
|
|
536
|
+
verifiedAt: new Date().toISOString(),
|
|
537
|
+
};
|
|
538
|
+
} catch (error) {
|
|
539
|
+
return {
|
|
540
|
+
error: `Cannot read file: ${error.message}`,
|
|
541
|
+
file,
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// ============================================================================
|
|
547
|
+
// UTILITIES
|
|
548
|
+
// ============================================================================
|
|
549
|
+
|
|
550
|
+
async function findSourceFiles(projectPath, extensions) {
|
|
551
|
+
const files = [];
|
|
552
|
+
|
|
553
|
+
async function walk(dir) {
|
|
554
|
+
try {
|
|
555
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
556
|
+
for (const entry of entries) {
|
|
557
|
+
const fullPath = path.join(dir, entry.name);
|
|
558
|
+
if (entry.isDirectory()) {
|
|
559
|
+
if (!entry.name.startsWith(".") && entry.name !== "node_modules" && entry.name !== "dist" && entry.name !== "build") {
|
|
560
|
+
await walk(fullPath);
|
|
561
|
+
}
|
|
562
|
+
} else if (entry.isFile()) {
|
|
563
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
564
|
+
if (extensions.includes(ext)) {
|
|
565
|
+
files.push(fullPath);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
} catch {
|
|
570
|
+
// Skip inaccessible directories
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
await walk(projectPath);
|
|
575
|
+
return files;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
export default {
|
|
579
|
+
TRUTH_CONTEXT_TOOLS,
|
|
580
|
+
handleTruthContextTool,
|
|
581
|
+
};
|