@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.
Files changed (108) hide show
  1. package/bin/dev/run-v2-torture.js +30 -0
  2. package/bin/runners/context/index.js +1 -1
  3. package/bin/runners/lib/analyzers.js +38 -0
  4. package/bin/runners/lib/assets/vibecheck-logo.png +0 -0
  5. package/bin/runners/lib/contracts/auth-contract.js +8 -0
  6. package/bin/runners/lib/contracts/env-contract.js +3 -0
  7. package/bin/runners/lib/contracts/external-contract.js +10 -2
  8. package/bin/runners/lib/contracts/route-contract.js +7 -0
  9. package/bin/runners/lib/contracts.js +804 -0
  10. package/bin/runners/lib/detectors-v2.js +703 -0
  11. package/bin/runners/lib/drift.js +425 -0
  12. package/bin/runners/lib/entitlements-v2.js +3 -1
  13. package/bin/runners/lib/entitlements.js +11 -3
  14. package/bin/runners/lib/env-resolver.js +417 -0
  15. package/bin/runners/lib/extractors/client-calls.js +990 -0
  16. package/bin/runners/lib/extractors/fastify-route-dump.js +573 -0
  17. package/bin/runners/lib/extractors/fastify-routes.js +426 -0
  18. package/bin/runners/lib/extractors/index.js +363 -0
  19. package/bin/runners/lib/extractors/next-routes.js +524 -0
  20. package/bin/runners/lib/extractors/proof-graph.js +431 -0
  21. package/bin/runners/lib/extractors/route-matcher.js +451 -0
  22. package/bin/runners/lib/extractors/truthpack-v2.js +377 -0
  23. package/bin/runners/lib/extractors/ui-bindings.js +547 -0
  24. package/bin/runners/lib/findings-schema.js +281 -0
  25. package/bin/runners/lib/html-report.js +650 -0
  26. package/bin/runners/lib/missions/templates.js +45 -0
  27. package/bin/runners/lib/policy.js +295 -0
  28. package/bin/runners/lib/reality/correlation-detectors.js +359 -0
  29. package/bin/runners/lib/reality/index.js +318 -0
  30. package/bin/runners/lib/reality/request-hashing.js +416 -0
  31. package/bin/runners/lib/reality/request-mapper.js +453 -0
  32. package/bin/runners/lib/reality/safety-rails.js +463 -0
  33. package/bin/runners/lib/reality/semantic-snapshot.js +408 -0
  34. package/bin/runners/lib/reality/toast-detector.js +393 -0
  35. package/bin/runners/lib/report-html.js +5 -0
  36. package/bin/runners/lib/report-templates.js +5 -0
  37. package/bin/runners/lib/report.js +135 -0
  38. package/bin/runners/lib/route-truth.js +10 -10
  39. package/bin/runners/lib/schema-validator.js +350 -0
  40. package/bin/runners/lib/schemas/contracts.schema.json +160 -0
  41. package/bin/runners/lib/schemas/finding.schema.json +100 -0
  42. package/bin/runners/lib/schemas/mission-pack.schema.json +206 -0
  43. package/bin/runners/lib/schemas/proof-graph.schema.json +176 -0
  44. package/bin/runners/lib/schemas/reality-report.schema.json +162 -0
  45. package/bin/runners/lib/schemas/share-pack.schema.json +180 -0
  46. package/bin/runners/lib/schemas/ship-report.schema.json +117 -0
  47. package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -0
  48. package/bin/runners/lib/schemas/validator.js +438 -0
  49. package/bin/runners/lib/ui.js +562 -0
  50. package/bin/runners/lib/verdict-engine.js +628 -0
  51. package/bin/runners/runAIAgent.js +228 -1
  52. package/bin/runners/runBadge.js +181 -1
  53. package/bin/runners/runCtx.js +7 -2
  54. package/bin/runners/runCtxDiff.js +301 -0
  55. package/bin/runners/runGuard.js +168 -0
  56. package/bin/runners/runInitGha.js +78 -15
  57. package/bin/runners/runLabs.js +341 -0
  58. package/bin/runners/runLaunch.js +180 -1
  59. package/bin/runners/runMdc.js +203 -1
  60. package/bin/runners/runProof.zip +0 -0
  61. package/bin/runners/runProve.js +23 -0
  62. package/bin/runners/runReplay.js +114 -84
  63. package/bin/runners/runScan.js +111 -32
  64. package/bin/runners/runShip.js +23 -2
  65. package/bin/runners/runTruthpack.js +9 -7
  66. package/bin/runners/runValidate.js +161 -1
  67. package/bin/vibecheck.js +416 -770
  68. package/mcp-server/.guardrail/audit/audit.log.jsonl +2 -0
  69. package/mcp-server/.specs/architecture.mdc +90 -0
  70. package/mcp-server/.specs/security.mdc +30 -0
  71. package/mcp-server/README.md +252 -0
  72. package/mcp-server/agent-checkpoint.js +364 -0
  73. package/mcp-server/architect-tools.js +707 -0
  74. package/mcp-server/audit-mcp.js +206 -0
  75. package/mcp-server/codebase-architect-tools.js +838 -0
  76. package/mcp-server/consolidated-tools.js +804 -0
  77. package/mcp-server/hygiene-tools.js +428 -0
  78. package/mcp-server/index-v1.js +698 -0
  79. package/mcp-server/index.js +2092 -0
  80. package/mcp-server/index.old.js +4137 -0
  81. package/mcp-server/intelligence-tools.js +664 -0
  82. package/mcp-server/intent-drift-tools.js +873 -0
  83. package/mcp-server/mdc-generator.js +298 -0
  84. package/mcp-server/package-lock.json +165 -0
  85. package/mcp-server/package.json +47 -0
  86. package/mcp-server/premium-tools.js +1275 -0
  87. package/mcp-server/test-mcp.js +108 -0
  88. package/mcp-server/test-tools.js +36 -0
  89. package/mcp-server/tier-auth.js +147 -0
  90. package/mcp-server/tools/index.js +72 -0
  91. package/mcp-server/tools-reorganized.ts +244 -0
  92. package/mcp-server/truth-context.js +581 -0
  93. package/mcp-server/truth-firewall-tools.js +1500 -0
  94. package/mcp-server/vibecheck-2.0-tools.js +748 -0
  95. package/mcp-server/vibecheck-tools.js +1075 -0
  96. package/package.json +10 -8
  97. package/bin/guardrail.js +0 -834
  98. package/bin/runners/runAudit.js +0 -2
  99. package/bin/runners/runAutopilot.js +0 -2
  100. package/bin/runners/runCertify.js +0 -2
  101. package/bin/runners/runDashboard.js +0 -10
  102. package/bin/runners/runEnhancedShip.js +0 -2
  103. package/bin/runners/runFixPacks.js +0 -2
  104. package/bin/runners/runNaturalLanguage.js +0 -3
  105. package/bin/runners/runProof.js +0 -2
  106. package/bin/runners/runRealitySniff.js +0 -2
  107. package/bin/runners/runUpgrade.js +0 -2
  108. 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
+ };