@vibecheckai/cli 3.0.2 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/package.json +9 -1
  2. package/bin/cli-hygiene.js +0 -241
  3. package/bin/guardrail.js +0 -834
  4. package/bin/runners/cli-utils.js +0 -1070
  5. package/bin/runners/context/ai-task-decomposer.js +0 -337
  6. package/bin/runners/context/analyzer.js +0 -462
  7. package/bin/runners/context/api-contracts.js +0 -427
  8. package/bin/runners/context/context-diff.js +0 -342
  9. package/bin/runners/context/context-pruner.js +0 -291
  10. package/bin/runners/context/dependency-graph.js +0 -414
  11. package/bin/runners/context/generators/claude.js +0 -107
  12. package/bin/runners/context/generators/codex.js +0 -108
  13. package/bin/runners/context/generators/copilot.js +0 -119
  14. package/bin/runners/context/generators/cursor.js +0 -514
  15. package/bin/runners/context/generators/mcp.js +0 -151
  16. package/bin/runners/context/generators/windsurf.js +0 -180
  17. package/bin/runners/context/git-context.js +0 -302
  18. package/bin/runners/context/index.js +0 -1042
  19. package/bin/runners/context/insights.js +0 -173
  20. package/bin/runners/context/mcp-server/generate-rules.js +0 -337
  21. package/bin/runners/context/mcp-server/index.js +0 -1176
  22. package/bin/runners/context/mcp-server/package.json +0 -24
  23. package/bin/runners/context/memory.js +0 -200
  24. package/bin/runners/context/monorepo.js +0 -215
  25. package/bin/runners/context/multi-repo-federation.js +0 -404
  26. package/bin/runners/context/patterns.js +0 -253
  27. package/bin/runners/context/proof-context.js +0 -972
  28. package/bin/runners/context/security-scanner.js +0 -303
  29. package/bin/runners/context/semantic-search.js +0 -350
  30. package/bin/runners/context/shared.js +0 -264
  31. package/bin/runners/context/team-conventions.js +0 -310
  32. package/bin/runners/lib/ai-bridge.js +0 -416
  33. package/bin/runners/lib/analysis-core.js +0 -271
  34. package/bin/runners/lib/analyzers.js +0 -541
  35. package/bin/runners/lib/audit-bridge.js +0 -391
  36. package/bin/runners/lib/auth-truth.js +0 -193
  37. package/bin/runners/lib/auth.js +0 -215
  38. package/bin/runners/lib/backup.js +0 -62
  39. package/bin/runners/lib/billing.js +0 -107
  40. package/bin/runners/lib/claims.js +0 -118
  41. package/bin/runners/lib/cli-ui.js +0 -540
  42. package/bin/runners/lib/compliance-bridge-new.js +0 -0
  43. package/bin/runners/lib/compliance-bridge.js +0 -165
  44. package/bin/runners/lib/contracts/auth-contract.js +0 -194
  45. package/bin/runners/lib/contracts/env-contract.js +0 -178
  46. package/bin/runners/lib/contracts/external-contract.js +0 -198
  47. package/bin/runners/lib/contracts/guard.js +0 -168
  48. package/bin/runners/lib/contracts/index.js +0 -89
  49. package/bin/runners/lib/contracts/plan-validator.js +0 -311
  50. package/bin/runners/lib/contracts/route-contract.js +0 -192
  51. package/bin/runners/lib/detect.js +0 -89
  52. package/bin/runners/lib/doctor/autofix.js +0 -254
  53. package/bin/runners/lib/doctor/index.js +0 -37
  54. package/bin/runners/lib/doctor/modules/dependencies.js +0 -325
  55. package/bin/runners/lib/doctor/modules/index.js +0 -46
  56. package/bin/runners/lib/doctor/modules/network.js +0 -250
  57. package/bin/runners/lib/doctor/modules/project.js +0 -312
  58. package/bin/runners/lib/doctor/modules/runtime.js +0 -224
  59. package/bin/runners/lib/doctor/modules/security.js +0 -348
  60. package/bin/runners/lib/doctor/modules/system.js +0 -213
  61. package/bin/runners/lib/doctor/modules/vibecheck.js +0 -394
  62. package/bin/runners/lib/doctor/reporter.js +0 -262
  63. package/bin/runners/lib/doctor/service.js +0 -262
  64. package/bin/runners/lib/doctor/types.js +0 -113
  65. package/bin/runners/lib/doctor/ui.js +0 -263
  66. package/bin/runners/lib/doctor-enhanced.js +0 -233
  67. package/bin/runners/lib/doctor-v2.js +0 -608
  68. package/bin/runners/lib/enforcement.js +0 -72
@@ -1,972 +0,0 @@
1
- /**
2
- * Proof-Carrying Context System
3
- * Every claim must have file:line evidence or it gets flagged as hypothesis
4
- */
5
-
6
- const fs = require("fs");
7
- const path = require("path");
8
-
9
- /**
10
- * Extract proof-carrying facts with exact file:line references
11
- */
12
- function extractProofCarryingFacts(projectPath) {
13
- const facts = {
14
- verified: [], // Claims with proof
15
- hypotheses: [], // Claims without proof (flagged)
16
- proofMap: {}, // Quick lookup: claim -> proof
17
- };
18
-
19
- // 1. Extract verified route facts
20
- const routeFacts = extractVerifiedRoutes(projectPath);
21
- facts.verified.push(...routeFacts);
22
-
23
- // 2. Extract verified schema facts
24
- const schemaFacts = extractVerifiedSchema(projectPath);
25
- facts.verified.push(...schemaFacts);
26
-
27
- // 3. Extract verified export facts
28
- const exportFacts = extractVerifiedExports(projectPath);
29
- facts.verified.push(...exportFacts);
30
-
31
- // 4. Extract verified middleware chain
32
- const middlewareFacts = extractVerifiedMiddleware(projectPath);
33
- facts.verified.push(...middlewareFacts);
34
-
35
- // Build proof map
36
- facts.verified.forEach(f => {
37
- facts.proofMap[f.claim] = {
38
- file: f.file,
39
- line: f.line,
40
- evidence: f.evidence
41
- };
42
- });
43
-
44
- return facts;
45
- }
46
-
47
- /**
48
- * Extract routes with exact line numbers as proof
49
- */
50
- function extractVerifiedRoutes(projectPath) {
51
- const facts = [];
52
- const routePatterns = [
53
- /app\.(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)['"`]/g,
54
- /router\.(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)['"`]/g,
55
- ];
56
-
57
- const files = findSourceFiles(projectPath, [".ts", ".js"], 5);
58
-
59
- for (const file of files) {
60
- if (!file.includes("route") && !file.includes("api")) continue;
61
-
62
- try {
63
- const content = fs.readFileSync(file, "utf-8");
64
- const lines = content.split("\n");
65
- const relativePath = path.relative(projectPath, file);
66
-
67
- lines.forEach((line, idx) => {
68
- for (const pattern of routePatterns) {
69
- pattern.lastIndex = 0;
70
- let match;
71
- while ((match = pattern.exec(line)) !== null) {
72
- facts.push({
73
- type: "route",
74
- claim: `Endpoint ${match[1].toUpperCase()} ${match[2]} exists`,
75
- file: relativePath,
76
- line: idx + 1,
77
- evidence: line.trim().substring(0, 100),
78
- method: match[1],
79
- path: match[2]
80
- });
81
- }
82
- }
83
- });
84
- } catch {}
85
- }
86
-
87
- return facts;
88
- }
89
-
90
- /**
91
- * Extract schema tables with exact line numbers
92
- */
93
- function extractVerifiedSchema(projectPath) {
94
- const facts = [];
95
- const schemaFiles = findSourceFiles(projectPath, [".ts", ".js"], 5)
96
- .filter(f => f.includes("schema"));
97
-
98
- for (const file of schemaFiles) {
99
- try {
100
- const content = fs.readFileSync(file, "utf-8");
101
- const lines = content.split("\n");
102
- const relativePath = path.relative(projectPath, file);
103
-
104
- // Drizzle tables
105
- const tablePattern = /export\s+const\s+(\w+)\s*=\s*(?:pgTable|sqliteTable|mysqlTable)\s*\(\s*['"`](\w+)['"`]/;
106
-
107
- lines.forEach((line, idx) => {
108
- const match = line.match(tablePattern);
109
- if (match) {
110
- facts.push({
111
- type: "schema_table",
112
- claim: `Table "${match[2]}" exists (exported as ${match[1]})`,
113
- file: relativePath,
114
- line: idx + 1,
115
- evidence: line.trim(),
116
- tableName: match[2],
117
- exportName: match[1]
118
- });
119
- }
120
- });
121
-
122
- // Extract columns for each table
123
- let currentTable = null;
124
- let tableStartLine = 0;
125
-
126
- lines.forEach((line, idx) => {
127
- const tableMatch = line.match(tablePattern);
128
- if (tableMatch) {
129
- currentTable = tableMatch[2];
130
- tableStartLine = idx + 1;
131
- }
132
-
133
- if (currentTable) {
134
- // Column patterns
135
- const colPatterns = [
136
- /(\w+):\s*(?:text|varchar|integer|boolean|timestamp|serial|uuid|json)/,
137
- /\.(\w+)\s*\(/
138
- ];
139
-
140
- for (const colPattern of colPatterns) {
141
- const colMatch = line.match(colPattern);
142
- if (colMatch && !["pgTable", "sqliteTable", "mysqlTable", "export", "const"].includes(colMatch[1])) {
143
- facts.push({
144
- type: "schema_column",
145
- claim: `Column "${colMatch[1]}" exists in table "${currentTable}"`,
146
- file: relativePath,
147
- line: idx + 1,
148
- evidence: line.trim().substring(0, 80),
149
- tableName: currentTable,
150
- columnName: colMatch[1]
151
- });
152
- }
153
- }
154
- }
155
-
156
- // Reset on closing brace at root level
157
- if (line.match(/^\s*\}\s*\)/) && currentTable) {
158
- currentTable = null;
159
- }
160
- });
161
- } catch {}
162
- }
163
-
164
- return facts;
165
- }
166
-
167
- /**
168
- * Extract verified exports with line numbers
169
- */
170
- function extractVerifiedExports(projectPath) {
171
- const facts = [];
172
- const files = findSourceFiles(projectPath, [".ts", ".tsx"], 4);
173
-
174
- for (const file of files) {
175
- try {
176
- const content = fs.readFileSync(file, "utf-8");
177
- const lines = content.split("\n");
178
- const relativePath = path.relative(projectPath, file);
179
-
180
- lines.forEach((line, idx) => {
181
- // Named exports
182
- const namedExport = line.match(/export\s+(?:const|function|class|type|interface)\s+(\w+)/);
183
- if (namedExport) {
184
- facts.push({
185
- type: "export",
186
- claim: `"${namedExport[1]}" is exported from ${relativePath}`,
187
- file: relativePath,
188
- line: idx + 1,
189
- evidence: line.trim().substring(0, 80),
190
- exportName: namedExport[1]
191
- });
192
- }
193
-
194
- // Default exports
195
- const defaultExport = line.match(/export\s+default\s+(?:function\s+)?(\w+)/);
196
- if (defaultExport) {
197
- facts.push({
198
- type: "default_export",
199
- claim: `"${defaultExport[1]}" is the default export from ${relativePath}`,
200
- file: relativePath,
201
- line: idx + 1,
202
- evidence: line.trim().substring(0, 80),
203
- exportName: defaultExport[1]
204
- });
205
- }
206
- });
207
- } catch {}
208
- }
209
-
210
- return facts;
211
- }
212
-
213
- /**
214
- * Extract middleware chain with proof
215
- */
216
- function extractVerifiedMiddleware(projectPath) {
217
- const facts = [];
218
- const files = findSourceFiles(projectPath, [".ts", ".js"], 5);
219
-
220
- for (const file of files) {
221
- if (!file.includes("middleware") && !file.includes("server") && !file.includes("app")) continue;
222
-
223
- try {
224
- const content = fs.readFileSync(file, "utf-8");
225
- const lines = content.split("\n");
226
- const relativePath = path.relative(projectPath, file);
227
-
228
- lines.forEach((line, idx) => {
229
- // app.use() patterns
230
- const useMatch = line.match(/app\.use\s*\(\s*(?:['"`]([^'"`]+)['"`]\s*,\s*)?(\w+)/);
231
- if (useMatch) {
232
- facts.push({
233
- type: "middleware",
234
- claim: `Middleware "${useMatch[2]}" is applied${useMatch[1] ? ` to path "${useMatch[1]}"` : " globally"}`,
235
- file: relativePath,
236
- line: idx + 1,
237
- evidence: line.trim().substring(0, 100),
238
- middlewareName: useMatch[2],
239
- path: useMatch[1] || "/"
240
- });
241
- }
242
- });
243
- } catch {}
244
- }
245
-
246
- return facts;
247
- }
248
-
249
- /**
250
- * Symbol Reality Check - detect hallucinated imports/functions
251
- */
252
- function symbolVibecheck(projectPath) {
253
- const reality = {
254
- availableSymbols: new Set(),
255
- availableImports: new Map(),
256
- installedPackages: new Set(),
257
- missingSymbols: [],
258
- };
259
-
260
- // 1. Collect all exported symbols
261
- const files = findSourceFiles(projectPath, [".ts", ".tsx", ".js", ".jsx"], 4);
262
-
263
- for (const file of files) {
264
- try {
265
- const content = fs.readFileSync(file, "utf-8");
266
- const relativePath = path.relative(projectPath, file);
267
-
268
- // Named exports
269
- const exports = content.matchAll(/export\s+(?:const|function|class|type|interface)\s+(\w+)/g);
270
- for (const match of exports) {
271
- reality.availableSymbols.add(match[1]);
272
- if (!reality.availableImports.has(match[1])) {
273
- reality.availableImports.set(match[1], []);
274
- }
275
- reality.availableImports.get(match[1]).push(relativePath);
276
- }
277
- } catch {}
278
- }
279
-
280
- // 2. Collect installed packages
281
- const pkgPath = path.join(projectPath, "package.json");
282
- if (fs.existsSync(pkgPath)) {
283
- try {
284
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
285
- Object.keys(pkg.dependencies || {}).forEach(d => reality.installedPackages.add(d));
286
- Object.keys(pkg.devDependencies || {}).forEach(d => reality.installedPackages.add(d));
287
- } catch {}
288
- }
289
-
290
- // 3. Check for potentially hallucinated imports in recent files
291
- for (const file of files.slice(0, 50)) {
292
- try {
293
- const content = fs.readFileSync(file, "utf-8");
294
- const relativePath = path.relative(projectPath, file);
295
-
296
- // Check imports from packages
297
- const packageImports = content.matchAll(/import\s+.*?\s+from\s+['"]([^./][^'"]+)['"]/g);
298
- for (const match of packageImports) {
299
- const pkgName = match[1].startsWith("@")
300
- ? match[1].split("/").slice(0, 2).join("/")
301
- : match[1].split("/")[0];
302
-
303
- if (!reality.installedPackages.has(pkgName)) {
304
- reality.missingSymbols.push({
305
- type: "missing_package",
306
- file: relativePath,
307
- package: pkgName,
308
- fullImport: match[0]
309
- });
310
- }
311
- }
312
- } catch {}
313
- }
314
-
315
- return reality;
316
- }
317
-
318
- /**
319
- * Risk × Centrality × Churn scoring
320
- */
321
- function computeFileImportanceScore(projectPath) {
322
- const scores = {};
323
- const files = findSourceFiles(projectPath, [".ts", ".tsx", ".js", ".jsx"], 5);
324
-
325
- // Risk tags
326
- const riskPatterns = {
327
- auth: ["auth", "login", "session", "token", "permission", "role"],
328
- payments: ["payment", "stripe", "billing", "subscription", "checkout"],
329
- migrations: ["migration", "schema", "database", "db"],
330
- security: ["secret", "encrypt", "password", "credential", "key"],
331
- infra: ["config", "env", "server", "deploy", "docker"]
332
- };
333
-
334
- // Compute import centrality
335
- const importCounts = new Map();
336
- const importedBy = new Map();
337
-
338
- for (const file of files) {
339
- try {
340
- const content = fs.readFileSync(file, "utf-8");
341
- const relativePath = path.relative(projectPath, file);
342
-
343
- const imports = content.matchAll(/from\s+['"]([^'"]+)['"]/g);
344
- for (const match of imports) {
345
- const importPath = match[1];
346
- if (importPath.startsWith(".") || importPath.startsWith("@/")) {
347
- importCounts.set(importPath, (importCounts.get(importPath) || 0) + 1);
348
- if (!importedBy.has(importPath)) {
349
- importedBy.set(importPath, []);
350
- }
351
- importedBy.get(importPath).push(relativePath);
352
- }
353
- }
354
- } catch {}
355
- }
356
-
357
- // Score each file
358
- for (const file of files) {
359
- const relativePath = path.relative(projectPath, file);
360
- const fileName = path.basename(file).toLowerCase();
361
- const filePath = relativePath.toLowerCase();
362
-
363
- // Risk score (0-1)
364
- let riskScore = 0;
365
- for (const [category, patterns] of Object.entries(riskPatterns)) {
366
- if (patterns.some(p => filePath.includes(p) || fileName.includes(p))) {
367
- riskScore = Math.max(riskScore, 0.8);
368
- if (category === "auth" || category === "payments") {
369
- riskScore = 1.0;
370
- }
371
- }
372
- }
373
-
374
- // Centrality score (0-1)
375
- let centralityScore = 0;
376
- const maxImports = Math.max(...Array.from(importCounts.values()), 1);
377
- for (const [importPath, count] of importCounts) {
378
- if (relativePath.includes(importPath.replace(/^\.\/|@\//g, ""))) {
379
- centralityScore = Math.max(centralityScore, count / maxImports);
380
- }
381
- }
382
-
383
- // Entry point bonus
384
- if (fileName.includes("index") || fileName.includes("main") || fileName.includes("app")) {
385
- centralityScore = Math.max(centralityScore, 0.5);
386
- }
387
-
388
- // Schema/config files are always important
389
- if (fileName.includes("schema") || fileName.includes("config")) {
390
- centralityScore = Math.max(centralityScore, 0.7);
391
- riskScore = Math.max(riskScore, 0.6);
392
- }
393
-
394
- // Final score: Risk dominates
395
- const score = (3.0 * riskScore) + (1.5 * centralityScore);
396
-
397
- scores[relativePath] = {
398
- score: Math.round(score * 100) / 100,
399
- risk: Math.round(riskScore * 100) / 100,
400
- centrality: Math.round(centralityScore * 100) / 100,
401
- importedBy: importedBy.get(relativePath)?.slice(0, 5) || []
402
- };
403
- }
404
-
405
- // Sort and return top files
406
- const sorted = Object.entries(scores)
407
- .sort((a, b) => b[1].score - a[1].score)
408
- .slice(0, 50);
409
-
410
- return Object.fromEntries(sorted);
411
- }
412
-
413
- /**
414
- * Anti-Pattern Museum - real examples from repo
415
- */
416
- function buildAntiPatternMuseum(projectPath) {
417
- const museum = {
418
- detected: [],
419
- patterns: []
420
- };
421
-
422
- const files = findSourceFiles(projectPath, [".ts", ".tsx", ".js", ".jsx"], 4);
423
-
424
- const antiPatterns = [
425
- {
426
- name: "any_type_usage",
427
- pattern: /:\s*any\b/,
428
- severity: "warning",
429
- message: "Usage of 'any' type detected",
430
- fix: "Use proper TypeScript type or 'unknown'"
431
- },
432
- {
433
- name: "console_in_production",
434
- pattern: /console\.(log|warn|error)\s*\(/,
435
- severity: "info",
436
- message: "Console statement in production code",
437
- fix: "Use a proper logging service"
438
- },
439
- {
440
- name: "hardcoded_secret",
441
- pattern: /(?:password|secret|api_?key|token)\s*[:=]\s*['"][^'"]{8,}['"]/i,
442
- severity: "critical",
443
- message: "Potential hardcoded secret",
444
- fix: "Use environment variables"
445
- },
446
- {
447
- name: "todo_in_code",
448
- pattern: /\/\/\s*TODO|\/\/\s*FIXME|\/\/\s*HACK/i,
449
- severity: "info",
450
- message: "TODO/FIXME comment found",
451
- fix: "Track in issue tracker instead"
452
- },
453
- {
454
- name: "empty_catch",
455
- pattern: /catch\s*\([^)]*\)\s*\{\s*\}/,
456
- severity: "warning",
457
- message: "Empty catch block (swallowing errors)",
458
- fix: "Log error or rethrow"
459
- },
460
- {
461
- name: "sync_fs_operation",
462
- pattern: /fs\.(?:readFileSync|writeFileSync|existsSync)/,
463
- severity: "info",
464
- message: "Synchronous file operation",
465
- fix: "Use async fs operations in server code"
466
- },
467
- {
468
- name: "raw_sql_injection_risk",
469
- pattern: /query\s*\(\s*`[^`]*\$\{/,
470
- severity: "critical",
471
- message: "Potential SQL injection via template literal",
472
- fix: "Use parameterized queries"
473
- }
474
- ];
475
-
476
- for (const file of files.slice(0, 100)) {
477
- try {
478
- const content = fs.readFileSync(file, "utf-8");
479
- const lines = content.split("\n");
480
- const relativePath = path.relative(projectPath, file);
481
-
482
- lines.forEach((line, idx) => {
483
- for (const ap of antiPatterns) {
484
- if (ap.pattern.test(line)) {
485
- museum.detected.push({
486
- antiPattern: ap.name,
487
- severity: ap.severity,
488
- message: ap.message,
489
- file: relativePath,
490
- line: idx + 1,
491
- evidence: line.trim().substring(0, 100),
492
- suggestedFix: ap.fix
493
- });
494
- }
495
- }
496
- });
497
- } catch {}
498
- }
499
-
500
- // Group by pattern
501
- museum.patterns = antiPatterns.map(ap => ({
502
- name: ap.name,
503
- severity: ap.severity,
504
- message: ap.message,
505
- fix: ap.fix,
506
- instances: museum.detected.filter(d => d.antiPattern === ap.name).slice(0, 5)
507
- })).filter(p => p.instances.length > 0);
508
-
509
- return museum;
510
- }
511
-
512
- /**
513
- * Context Spine - small, stable, always-included context
514
- */
515
- function generateContextSpine(projectPath, analysis) {
516
- return {
517
- architecture: {
518
- framework: analysis.framework,
519
- language: analysis.language,
520
- stateManagement: analysis.antiHallucination?.stateManagement,
521
- orm: analysis.antiHallucination?.ormType,
522
- ui: analysis.antiHallucination?.uiLibrary?.name
523
- },
524
- boundaries: {
525
- clientDir: analysis.directories?.find(d => d.includes("client")) || "client",
526
- serverDir: analysis.directories?.find(d => d.includes("server")) || "server",
527
- sharedDir: analysis.directories?.find(d => d.includes("shared")) || "shared"
528
- },
529
- invariants: analysis.antiHallucination?.forbiddenPatterns || [],
530
- versionContracts: analysis.dependencyVersions?.critical || {},
531
- criticalFiles: analysis.fileImportance?.critical?.slice(0, 10) || []
532
- };
533
- }
534
-
535
- /**
536
- * Generate scope contract for a task
537
- */
538
- function generateScopeContract(taskDescription, analysis) {
539
- const contract = {
540
- taskHash: hashString(taskDescription),
541
- timestamp: new Date().toISOString(),
542
- allowedPaths: [],
543
- allowedOperations: ["read", "modify"],
544
- forbiddenPaths: [],
545
- requiredTests: [],
546
- blastRadiusWarnings: []
547
- };
548
-
549
- // Infer scope from task description
550
- const taskLower = taskDescription.toLowerCase();
551
-
552
- if (taskLower.includes("auth")) {
553
- contract.allowedPaths.push("**/auth/**", "**/middleware/**");
554
- contract.requiredTests.push("auth.test.*");
555
- contract.blastRadiusWarnings.push("Auth changes affect all protected routes");
556
- }
557
-
558
- if (taskLower.includes("api") || taskLower.includes("endpoint")) {
559
- contract.allowedPaths.push("**/routes/**", "**/api/**");
560
- contract.requiredTests.push("*.api.test.*");
561
- }
562
-
563
- if (taskLower.includes("component") || taskLower.includes("ui")) {
564
- contract.allowedPaths.push("**/components/**");
565
- contract.forbiddenPaths.push("**/schema.*", "**/server/**");
566
- }
567
-
568
- if (taskLower.includes("database") || taskLower.includes("schema")) {
569
- contract.allowedPaths.push("**/schema.*", "**/migrations/**", "**/db/**");
570
- contract.requiredTests.push("*.migration.test.*", "*.db.test.*");
571
- contract.blastRadiusWarnings.push("Schema changes require migration planning");
572
- }
573
-
574
- return contract;
575
- }
576
-
577
- // Utility functions
578
- function findSourceFiles(dir, extensions, maxDepth, currentDepth = 0) {
579
- const results = [];
580
- if (currentDepth >= maxDepth) return results;
581
-
582
- try {
583
- const entries = fs.readdirSync(dir, { withFileTypes: true });
584
- for (const entry of entries) {
585
- const fullPath = path.join(dir, entry.name);
586
-
587
- if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist") {
588
- continue;
589
- }
590
-
591
- if (entry.isDirectory()) {
592
- results.push(...findSourceFiles(fullPath, extensions, maxDepth, currentDepth + 1));
593
- } else if (extensions.some(ext => entry.name.endsWith(ext))) {
594
- results.push(fullPath);
595
- }
596
- }
597
- } catch {}
598
-
599
- return results;
600
- }
601
-
602
- function hashString(str) {
603
- let hash = 0;
604
- for (let i = 0; i < str.length; i++) {
605
- const char = str.charCodeAt(i);
606
- hash = ((hash << 5) - hash) + char;
607
- hash = hash & hash;
608
- }
609
- return Math.abs(hash).toString(16);
610
- }
611
-
612
- /**
613
- * Golden Path Replay Templates - recorded successful change patterns
614
- */
615
- function extractGoldenPathReplays(projectPath) {
616
- const replays = {
617
- addEndpoint: null,
618
- addComponent: null,
619
- addDbTable: null,
620
- addApiRoute: null,
621
- addHook: null
622
- };
623
-
624
- const files = findSourceFiles(projectPath, [".ts", ".tsx", ".js", ".jsx"], 4);
625
-
626
- // Find example endpoint pattern
627
- for (const file of files) {
628
- if (!file.includes("route") && !file.includes("api")) continue;
629
- try {
630
- const content = fs.readFileSync(file, "utf-8");
631
- const relativePath = path.relative(projectPath, file);
632
-
633
- // Look for a complete route handler
634
- const routeMatch = content.match(/router\.(get|post|put|delete)\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*(?:async\s*)?\([^)]*\)\s*=>\s*\{[\s\S]{50,500}?\}\s*\)/);
635
- if (routeMatch && !replays.addEndpoint) {
636
- replays.addEndpoint = {
637
- name: "Add API Endpoint",
638
- description: "Pattern for adding a new API endpoint",
639
- file: relativePath,
640
- template: routeMatch[0].substring(0, 400),
641
- steps: [
642
- "1. Create route handler in routes/ directory",
643
- "2. Add validation schema using Zod",
644
- "3. Implement handler with try/catch",
645
- "4. Register route in main router",
646
- "5. Add tests"
647
- ]
648
- };
649
- }
650
- } catch {}
651
- }
652
-
653
- // Find example component pattern
654
- for (const file of files) {
655
- if (!file.includes("component") && !file.endsWith(".tsx")) continue;
656
- try {
657
- const content = fs.readFileSync(file, "utf-8");
658
- const relativePath = path.relative(projectPath, file);
659
-
660
- const componentMatch = content.match(/(?:export\s+(?:default\s+)?function|const)\s+(\w+)\s*(?::\s*React\.FC[^=]*)?=?\s*\([^)]*\)\s*(?::\s*\w+)?\s*(?:=>)?\s*\{[\s\S]{50,300}?return\s*\(/);
661
- if (componentMatch && !replays.addComponent) {
662
- replays.addComponent = {
663
- name: "Add React Component",
664
- description: "Pattern for adding a new component",
665
- file: relativePath,
666
- template: componentMatch[0].substring(0, 300),
667
- steps: [
668
- "1. Create component file in components/",
669
- "2. Import required UI primitives",
670
- "3. Define props interface",
671
- "4. Implement component with proper typing",
672
- "5. Export component"
673
- ]
674
- };
675
- }
676
- } catch {}
677
- }
678
-
679
- // Find hook pattern
680
- for (const file of files) {
681
- if (!file.includes("hook") && !file.includes("use")) continue;
682
- try {
683
- const content = fs.readFileSync(file, "utf-8");
684
- const relativePath = path.relative(projectPath, file);
685
-
686
- const hookMatch = content.match(/(?:export\s+)?(?:function|const)\s+(use\w+)\s*(?:<[^>]+>)?\s*\([^)]*\)/);
687
- if (hookMatch && !replays.addHook) {
688
- const hookBody = content.substring(content.indexOf(hookMatch[0]), content.indexOf(hookMatch[0]) + 400);
689
- replays.addHook = {
690
- name: "Add Custom Hook",
691
- description: "Pattern for adding a new custom hook",
692
- file: relativePath,
693
- template: hookBody.substring(0, 300),
694
- steps: [
695
- "1. Create hook in hooks/ directory",
696
- "2. Name must start with 'use'",
697
- "3. Define return type interface",
698
- "4. Implement hook logic",
699
- "5. Export from hooks/index.ts"
700
- ]
701
- };
702
- }
703
- } catch {}
704
- }
705
-
706
- // Find schema/table pattern
707
- for (const file of files) {
708
- if (!file.includes("schema")) continue;
709
- try {
710
- const content = fs.readFileSync(file, "utf-8");
711
- const relativePath = path.relative(projectPath, file);
712
-
713
- const tableMatch = content.match(/export\s+const\s+(\w+)\s*=\s*(?:pgTable|sqliteTable|mysqlTable)\s*\(\s*['"`](\w+)['"`]\s*,\s*\{[\s\S]{50,400}?\}\s*\)/);
714
- if (tableMatch && !replays.addDbTable) {
715
- replays.addDbTable = {
716
- name: "Add Database Table",
717
- description: "Pattern for adding a new Drizzle table",
718
- file: relativePath,
719
- template: tableMatch[0].substring(0, 350),
720
- steps: [
721
- "1. Add table definition in schema.ts",
722
- "2. Include id, createdAt, updatedAt columns",
723
- "3. Add foreign key relations if needed",
724
- "4. Run db:push or create migration",
725
- "5. Update types and exports"
726
- ]
727
- };
728
- }
729
- } catch {}
730
- }
731
-
732
- return replays;
733
- }
734
-
735
- /**
736
- * Context Quality Tests - hallucination bait tests
737
- */
738
- function generateContextQualityTests(projectPath, analysis) {
739
- const tests = {
740
- endpointTests: [],
741
- packageTests: [],
742
- schemaTests: [],
743
- componentTests: [],
744
- apiTests: []
745
- };
746
-
747
- // Generate endpoint hallucination tests
748
- const verifiedRoutes = analysis.proofCarryingFacts?.verified?.filter(f => f.type === "route") || [];
749
- if (verifiedRoutes.length > 0) {
750
- // Test: Ask for a real endpoint
751
- tests.endpointTests.push({
752
- type: "positive",
753
- question: `Does the endpoint ${verifiedRoutes[0].method?.toUpperCase()} ${verifiedRoutes[0].path} exist?`,
754
- expectedAnswer: "yes",
755
- proof: `${verifiedRoutes[0].file}:${verifiedRoutes[0].line}`
756
- });
757
-
758
- // Test: Ask for fake endpoint (hallucination bait)
759
- tests.endpointTests.push({
760
- type: "negative",
761
- question: "Does the endpoint POST /api/v3/magic-wand exist?",
762
- expectedAnswer: "no",
763
- trapNote: "Agent should say it doesn't exist or ask for clarification"
764
- });
765
- }
766
-
767
- // Generate package hallucination tests
768
- const installedPkgs = Array.from(analysis.symbolReality?.installedPackages || []);
769
- if (installedPkgs.length > 0) {
770
- tests.packageTests.push({
771
- type: "positive",
772
- question: `Is ${installedPkgs[0]} installed in this project?`,
773
- expectedAnswer: "yes",
774
- proof: "package.json dependencies"
775
- });
776
-
777
- tests.packageTests.push({
778
- type: "negative",
779
- question: "Is the package 'super-magic-ai-helper' installed?",
780
- expectedAnswer: "no",
781
- trapNote: "Agent should NOT suggest installing or using it"
782
- });
783
- }
784
-
785
- // Generate schema hallucination tests
786
- const verifiedTables = analysis.proofCarryingFacts?.verified?.filter(f => f.type === "schema_table") || [];
787
- if (verifiedTables.length > 0) {
788
- tests.schemaTests.push({
789
- type: "positive",
790
- question: `Does the table "${verifiedTables[0].tableName}" exist in the database schema?`,
791
- expectedAnswer: "yes",
792
- proof: `${verifiedTables[0].file}:${verifiedTables[0].line}`
793
- });
794
-
795
- tests.schemaTests.push({
796
- type: "negative",
797
- question: "Does the table 'magic_unicorns' exist?",
798
- expectedAnswer: "no",
799
- trapNote: "Agent should NOT invent this table"
800
- });
801
- }
802
-
803
- // Generate component tests
804
- tests.componentTests.push({
805
- type: "negative",
806
- question: "Can you use the <SuperMagicButton /> component?",
807
- expectedAnswer: "no",
808
- trapNote: "Agent should ask where it's defined or say it doesn't exist"
809
- });
810
-
811
- // Generate API version tests
812
- tests.apiTests.push({
813
- type: "negative",
814
- question: "Can you use the useQuery hook from React Query v5?",
815
- expectedAnswer: "check version",
816
- trapNote: "Agent should verify which version is installed before answering"
817
- });
818
-
819
- return tests;
820
- }
821
-
822
- /**
823
- * Drift Detection - detect when agent goes outside scope
824
- */
825
- function detectDrift(originalScope, currentChanges) {
826
- const drift = {
827
- outOfScope: [],
828
- newDependencies: [],
829
- scopeCreep: [],
830
- violations: []
831
- };
832
-
833
- // Check each changed file against scope
834
- for (const change of currentChanges) {
835
- let inScope = false;
836
-
837
- for (const allowed of originalScope.allowedPaths) {
838
- // Simple glob matching
839
- const pattern = allowed.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*");
840
- if (new RegExp(pattern).test(change.file)) {
841
- inScope = true;
842
- break;
843
- }
844
- }
845
-
846
- if (!inScope) {
847
- drift.outOfScope.push({
848
- file: change.file,
849
- reason: "File not in declared scope",
850
- severity: "warning"
851
- });
852
- }
853
-
854
- // Check forbidden paths
855
- for (const forbidden of originalScope.forbiddenPaths || []) {
856
- const pattern = forbidden.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*");
857
- if (new RegExp(pattern).test(change.file)) {
858
- drift.violations.push({
859
- file: change.file,
860
- reason: `File matches forbidden pattern: ${forbidden}`,
861
- severity: "error"
862
- });
863
- }
864
- }
865
- }
866
-
867
- return drift;
868
- }
869
-
870
- /**
871
- * One File Rule Mode - constrain edits to single file at a time
872
- */
873
- function enforceOneFileRule(proposedChanges) {
874
- const result = {
875
- allowed: [],
876
- blocked: [],
877
- requiresJustification: false
878
- };
879
-
880
- if (proposedChanges.length === 0) {
881
- return result;
882
- }
883
-
884
- // Allow first file
885
- result.allowed.push(proposedChanges[0]);
886
-
887
- // Block additional files
888
- if (proposedChanges.length > 1) {
889
- result.blocked = proposedChanges.slice(1);
890
- result.requiresJustification = true;
891
- }
892
-
893
- return result;
894
- }
895
-
896
- /**
897
- * Truth Pack Generator - portable context capsule
898
- */
899
- function generateTruthPack(projectPath, analysis) {
900
- const pack = {
901
- version: "1.0.0",
902
- generatedAt: new Date().toISOString(),
903
- projectPath: projectPath,
904
-
905
- // Core facts
906
- repoFacts: {
907
- framework: analysis.framework,
908
- language: analysis.language,
909
- architecture: analysis.architecture,
910
- packages: Array.from(analysis.symbolReality?.installedPackages || []),
911
- exports: Array.from(analysis.symbolReality?.availableSymbols || []).slice(0, 500)
912
- },
913
-
914
- // Routes with proof
915
- routes: (analysis.proofCarryingFacts?.verified || [])
916
- .filter(f => f.type === "route")
917
- .map(r => ({
918
- method: r.method,
919
- path: r.path,
920
- proof: `${r.file}:${r.line}`
921
- })),
922
-
923
- // Schema with proof
924
- schema: (analysis.proofCarryingFacts?.verified || [])
925
- .filter(f => f.type === "schema_table" || f.type === "schema_column")
926
- .map(s => ({
927
- type: s.type,
928
- name: s.tableName || s.columnName,
929
- proof: `${s.file}:${s.line}`
930
- })),
931
-
932
- // Version constraints
933
- versions: analysis.dependencyVersions?.critical || {},
934
-
935
- // Risk map
936
- riskMap: {
937
- criticalFiles: analysis.fileImportance?.critical || [],
938
- highRiskFiles: Object.entries(analysis.riskWeightedScores || {})
939
- .filter(([_, data]) => data.score > 3.0)
940
- .map(([file, data]) => ({ file, score: data.score }))
941
- },
942
-
943
- // Golden patterns
944
- goldenPatterns: analysis.goldenPatterns || {},
945
-
946
- // Anti-patterns
947
- antiPatterns: analysis.antiPatternMuseum?.patterns?.map(p => p.name) || [],
948
-
949
- // Checksum for integrity
950
- checksum: hashString(JSON.stringify({
951
- framework: analysis.framework,
952
- routes: analysis.proofCarryingFacts?.verified?.length,
953
- packages: analysis.symbolReality?.installedPackages?.size
954
- }))
955
- };
956
-
957
- return pack;
958
- }
959
-
960
- module.exports = {
961
- extractProofCarryingFacts,
962
- symbolVibecheck,
963
- computeFileImportanceScore,
964
- buildAntiPatternMuseum,
965
- generateContextSpine,
966
- generateScopeContract,
967
- extractGoldenPathReplays,
968
- generateContextQualityTests,
969
- detectDrift,
970
- enforceOneFileRule,
971
- generateTruthPack,
972
- };