@vibecheckai/cli 3.1.8 → 3.2.0

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 (36) hide show
  1. package/bin/registry.js +106 -116
  2. package/bin/runners/context/generators/mcp.js +18 -0
  3. package/bin/runners/context/index.js +72 -4
  4. package/bin/runners/context/proof-context.js +293 -1
  5. package/bin/runners/context/security-scanner.js +311 -73
  6. package/bin/runners/lib/analyzers.js +607 -20
  7. package/bin/runners/lib/detectors-v2.js +172 -15
  8. package/bin/runners/lib/entitlements-v2.js +48 -1
  9. package/bin/runners/lib/evidence-pack.js +678 -0
  10. package/bin/runners/lib/html-proof-report.js +913 -0
  11. package/bin/runners/lib/missions/plan.js +231 -41
  12. package/bin/runners/lib/missions/templates.js +125 -0
  13. package/bin/runners/lib/scan-output.js +492 -253
  14. package/bin/runners/lib/ship-output.js +901 -641
  15. package/bin/runners/runCheckpoint.js +44 -3
  16. package/bin/runners/runContext.d.ts +4 -0
  17. package/bin/runners/runDoctor.js +10 -2
  18. package/bin/runners/runFix.js +51 -341
  19. package/bin/runners/runInit.js +11 -0
  20. package/bin/runners/runPolish.d.ts +4 -0
  21. package/bin/runners/runPolish.js +608 -29
  22. package/bin/runners/runProve.js +210 -25
  23. package/bin/runners/runReality.js +846 -101
  24. package/bin/runners/runScan.js +238 -4
  25. package/bin/runners/runShip.js +19 -3
  26. package/bin/runners/runWatch.js +14 -1
  27. package/bin/vibecheck.js +32 -2
  28. package/mcp-server/consolidated-tools.js +408 -42
  29. package/mcp-server/index.js +152 -15
  30. package/mcp-server/proof-tools.js +571 -0
  31. package/mcp-server/tier-auth.js +22 -19
  32. package/mcp-server/tools-v3.js +744 -0
  33. package/mcp-server/truth-firewall-tools.js +190 -4
  34. package/package.json +3 -1
  35. package/bin/runners/runInstall.js +0 -281
  36. package/bin/runners/runLabs.js +0 -341
@@ -355,6 +355,7 @@ function detectAuthContractDrift(truthpack, authContract) {
355
355
  /**
356
356
  * D_ENV_USED_BUT_UNDECLARED (WARN→BLOCK if required)
357
357
  * Trigger: truthpack envUsage includes FOO but contracts/env.json does not
358
+ * Enhanced with better required/optional detection
358
359
  */
359
360
  function detectEnvUsedButUndeclared(truthpack, envContract) {
360
361
  const findings = [];
@@ -363,7 +364,28 @@ function detectEnvUsedButUndeclared(truthpack, envContract) {
363
364
 
364
365
  for (const usage of envUsage) {
365
366
  if (!declaredVars.has(usage.name)) {
366
- const isRequired = usage.inferredRequiredness === "required" || isLikelyRequired(usage.name);
367
+ // Check if usage pattern suggests optional
368
+ const usageCode = usage.locations?.[0]?.snippet || "";
369
+ const hasOptionalPattern = hasOptionalUsagePattern(usageCode);
370
+
371
+ // Determine if required based on name patterns and usage
372
+ const isRequired = !hasOptionalPattern &&
373
+ (usage.inferredRequiredness === "required" || isLikelyRequired(usage.name));
374
+
375
+ // Skip reporting for common optional env vars that are typically not documented
376
+ const commonOptionalVars = [
377
+ /^DEBUG$/i,
378
+ /^LOG_LEVEL$/i,
379
+ /^NODE_ENV$/i,
380
+ /^CI$/i,
381
+ /^VERCEL/i,
382
+ /^GITHUB_/i,
383
+ /^NEXT_PUBLIC_VERCEL/i,
384
+ ];
385
+
386
+ if (commonOptionalVars.some(p => p.test(usage.name))) {
387
+ continue;
388
+ }
367
389
 
368
390
  findings.push(createFindingV2({
369
391
  detectorId: "ENV_USED_BUT_UNDECLARED",
@@ -620,44 +642,177 @@ function detectContractsOutOfDate(truthpack, contracts) {
620
642
  // Helpers
621
643
  // =============================================================================
622
644
 
645
+ /**
646
+ * Enhanced route matching with support for:
647
+ * - Dynamic segments (:id, [id], [slug], [...slug])
648
+ * - Optional catch-all routes [[...slug]]
649
+ * - Query string stripping
650
+ * - Trailing slash normalization
651
+ */
623
652
  function routeMatches(pattern, actual) {
624
- const patternParts = pattern.split("/").filter(Boolean);
625
- const actualParts = actual.split("/").filter(Boolean);
653
+ // Normalize: strip query strings and trailing slashes
654
+ const normalizedPattern = pattern.split("?")[0].replace(/\/+$/, "") || "/";
655
+ const normalizedActual = actual.split("?")[0].replace(/\/+$/, "") || "/";
656
+
657
+ const patternParts = normalizedPattern.split("/").filter(Boolean);
658
+ const actualParts = normalizedActual.split("/").filter(Boolean);
659
+
660
+ // Handle catch-all routes: [...slug] or [[...slug]]
661
+ const hasCatchAll = patternParts.some(p =>
662
+ p.startsWith("[...") || p.startsWith("[[...")
663
+ );
664
+
665
+ if (hasCatchAll) {
666
+ const catchAllIndex = patternParts.findIndex(p =>
667
+ p.startsWith("[...") || p.startsWith("[[...")
668
+ );
669
+
670
+ // For catch-all, pattern up to catch-all must match
671
+ for (let i = 0; i < catchAllIndex; i++) {
672
+ const p = patternParts[i];
673
+ if (isDynamicSegment(p)) continue;
674
+ if (p !== actualParts[i]) return false;
675
+ }
676
+
677
+ // Catch-all matches any remaining segments (including none for [[...]])
678
+ const isOptional = patternParts[catchAllIndex].startsWith("[[...");
679
+ if (!isOptional && actualParts.length <= catchAllIndex) {
680
+ return false;
681
+ }
682
+
683
+ return true;
684
+ }
626
685
 
686
+ // Standard matching: lengths must match
627
687
  if (patternParts.length !== actualParts.length) return false;
628
688
 
629
689
  for (let i = 0; i < patternParts.length; i++) {
630
690
  const p = patternParts[i];
631
- if (p.startsWith(":") || p.startsWith("[") || p === "*") continue;
691
+ if (isDynamicSegment(p)) continue;
632
692
  if (p !== actualParts[i]) return false;
633
693
  }
634
694
  return true;
635
695
  }
636
696
 
697
+ /**
698
+ * Check if a route segment is dynamic
699
+ */
700
+ function isDynamicSegment(segment) {
701
+ return segment.startsWith(":") || // Express-style :id
702
+ segment.startsWith("[") || // Next.js-style [id]
703
+ segment === "*" || // Wildcard
704
+ segment.startsWith("$"); // Remix-style $id
705
+ }
706
+
707
+ /**
708
+ * Enhanced glob pattern matching with proper escaping
709
+ */
637
710
  function matchPattern(pattern, url) {
638
- // Convert glob-like pattern to regex
639
- const regex = new RegExp(
640
- "^" + pattern
641
- .replace(/\*/g, ".*")
642
- .replace(/\//g, "\\/")
643
- + "$"
644
- );
645
- return regex.test(url) || regex.test(new URL(url, "http://localhost").pathname);
711
+ try {
712
+ // Normalize URL: extract pathname
713
+ let pathname;
714
+ try {
715
+ pathname = new URL(url, "http://localhost").pathname;
716
+ } catch {
717
+ pathname = url.split("?")[0];
718
+ }
719
+
720
+ // Escape special regex chars except * and ?
721
+ const escaped = pattern
722
+ .replace(/[.+^${}()|[\]\\]/g, "\\$&")
723
+ .replace(/\*\*/g, "{{GLOBSTAR}}")
724
+ .replace(/\*/g, "[^/]*")
725
+ .replace(/\?/g, ".")
726
+ .replace(/{{GLOBSTAR}}/g, ".*");
727
+
728
+ const regex = new RegExp("^" + escaped + "$");
729
+ return regex.test(url) || regex.test(pathname);
730
+ } catch {
731
+ // Fallback to simple comparison
732
+ return pattern === url;
733
+ }
646
734
  }
647
735
 
736
+ /**
737
+ * Enhanced env var requirement detection
738
+ * Returns { required: boolean, confidence: 'high' | 'medium' | 'low', reason: string }
739
+ */
648
740
  function isLikelyRequired(name) {
649
- const requiredPatterns = [
741
+ // High-confidence required patterns
742
+ const highConfidenceRequired = [
650
743
  /^DATABASE_URL$/i,
651
744
  /^NEXTAUTH_SECRET$/i,
652
745
  /^NEXTAUTH_URL$/i,
653
746
  /^JWT_SECRET$/i,
747
+ /^AUTH_SECRET$/i,
748
+ /^SESSION_SECRET$/i,
749
+ /^ENCRYPTION_KEY$/i,
654
750
  /^STRIPE_SECRET_KEY$/i,
655
751
  /^STRIPE_WEBHOOK_SECRET$/i,
656
- /SECRET/i,
752
+ /^OPENAI_API_KEY$/i,
753
+ /^ANTHROPIC_API_KEY$/i,
754
+ ];
755
+
756
+ // Medium-confidence required patterns
757
+ const mediumConfidenceRequired = [
758
+ /SECRET$/i,
657
759
  /TOKEN$/i,
658
760
  /API_KEY$/i,
761
+ /PRIVATE_KEY$/i,
762
+ /PASSWORD$/i,
763
+ /CREDENTIALS$/i,
764
+ ];
765
+
766
+ // Patterns that indicate optional env vars
767
+ const optionalPatterns = [
768
+ /^DEBUG/i,
769
+ /^LOG_/i,
770
+ /^ENABLE_/i,
771
+ /^DISABLE_/i,
772
+ /^FEATURE_/i,
773
+ /^FLAG_/i,
774
+ /^ANALYTICS/i,
775
+ /^TELEMETRY/i,
776
+ /^SENTRY/i,
777
+ /^PORT$/i,
778
+ /^HOST$/i,
779
+ /^NODE_ENV$/i,
659
780
  ];
660
- return requiredPatterns.some(p => p.test(name));
781
+
782
+ // Check optional first (overrides required)
783
+ if (optionalPatterns.some(p => p.test(name))) {
784
+ return false;
785
+ }
786
+
787
+ // Check high-confidence required
788
+ if (highConfidenceRequired.some(p => p.test(name))) {
789
+ return true;
790
+ }
791
+
792
+ // Check medium-confidence required
793
+ if (mediumConfidenceRequired.some(p => p.test(name))) {
794
+ return true;
795
+ }
796
+
797
+ return false;
798
+ }
799
+
800
+ /**
801
+ * Check if an env var usage suggests it's optional
802
+ */
803
+ function hasOptionalUsagePattern(code) {
804
+ const optionalPatterns = [
805
+ /\|\|\s*['"]?undefined['"]?/, // || undefined
806
+ /\?\?\s*['"]?undefined['"]?/, // ?? undefined
807
+ /\|\|\s*null/, // || null
808
+ /\?\?\s*null/, // ?? null
809
+ /\|\|\s*false/, // || false
810
+ /\?\?\s*false/, // ?? false
811
+ /if\s*\(\s*process\.env\./, // Conditional usage
812
+ /process\.env\.\w+\s*\?\s*\./, // Optional chaining
813
+ ];
814
+
815
+ return optionalPatterns.some(p => p.test(code));
661
816
  }
662
817
 
663
818
  // =============================================================================
@@ -700,4 +855,6 @@ module.exports = {
700
855
  routeMatches,
701
856
  matchPattern,
702
857
  isLikelyRequired,
858
+ isDynamicSegment,
859
+ hasOptionalUsagePattern,
703
860
  };
@@ -141,6 +141,50 @@ const ENTITLEMENTS = {
141
141
  // ─────────────────────────────────────────────────────────────────────────────
142
142
  "security": { minTier: "pro" },
143
143
 
144
+ // ─────────────────────────────────────────────────────────────────────────────
145
+ // AI FEATURES - Token/Cost Controls
146
+ // ─────────────────────────────────────────────────────────────────────────────
147
+ "ai": { minTier: "starter", downgrade: "ai.none" },
148
+ "ai.none": { minTier: "free", caps: { free: "No AI features - upgrade for AI" } },
149
+ "ai.starter": {
150
+ minTier: "starter",
151
+ caps: {
152
+ starter: {
153
+ model: "gpt-3.5-turbo", // Cheaper model for Starter
154
+ maxCallsPerMonth: 10, // 10 AI calls/month
155
+ maxInputTokensPerRequest: 2000, // ~$0.002 max input cost
156
+ maxOutputTokensPerRequest: 1000, // ~$0.002 max output cost
157
+ maxCostPerMonth: 0.50, // $0.50 max AI spend/month
158
+ }
159
+ }
160
+ },
161
+ "ai.pro": {
162
+ minTier: "pro",
163
+ caps: {
164
+ pro: {
165
+ model: "gpt-4-turbo-preview", // Premium model for Pro
166
+ alternateModel: "claude-3-5-sonnet-20241022", // Fallback
167
+ maxCallsPerMonth: 50, // 50 AI calls/month
168
+ maxInputTokensPerRequest: 4000, // ~$0.04 max input cost
169
+ maxOutputTokensPerRequest: 2000, // ~$0.06 max output cost
170
+ maxCostPerMonth: 5.00, // $5.00 max AI spend/month
171
+ }
172
+ }
173
+ },
174
+ "ai.compliance": {
175
+ minTier: "compliance",
176
+ caps: {
177
+ compliance: {
178
+ model: "gpt-4-turbo-preview",
179
+ alternateModel: "claude-3-5-sonnet-20241022",
180
+ maxCallsPerMonth: 500, // 500 AI calls/month
181
+ maxInputTokensPerRequest: 8000,
182
+ maxOutputTokensPerRequest: 4000,
183
+ maxCostPerMonth: 50.00, // $50 max AI spend/month
184
+ }
185
+ }
186
+ },
187
+
144
188
  // ─────────────────────────────────────────────────────────────────────────────
145
189
  // PRO ONLY
146
190
  // ─────────────────────────────────────────────────────────────────────────────
@@ -155,7 +199,10 @@ const ENTITLEMENTS = {
155
199
  // ─────────────────────────────────────────────────────────────────────────────
156
200
  "gate": { minTier: "starter" },
157
201
  "pr": { minTier: "starter" },
158
- "badge": { minTier: "starter" },
202
+ "badge": { minTier: "starter", downgrade: "badge.basic" },
203
+ "badge.basic": { minTier: "starter" }, // Basic badge (STARTER)
204
+ "badge.verified": { minTier: "pro" }, // Verified badge with seal (PRO)
205
+ "fix_hints": { minTier: "starter" }, // Fix hints/missions in output
159
206
  "launch": { minTier: "starter" },
160
207
  "dashboard_sync": { minTier: "starter" },
161
208