@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
@@ -1,11 +1,211 @@
1
1
  /**
2
2
  * Proof-Carrying Context System
3
3
  * Every claim must have file:line evidence or it gets flagged as hypothesis
4
+ *
5
+ * This module exports the SINGLE TRUTH CONTRACT used across CLI, MCP, and extension.
4
6
  */
5
7
 
6
8
  const fs = require("fs");
7
9
  const path = require("path");
8
10
 
11
+ const DEFAULT_EVIDENCE_CONFIDENCE = 0.9;
12
+ const MAX_EVIDENCE_SNIPPET = 200;
13
+
14
+ // Context attribution message shown when AI uses vibecheck data
15
+ const CONTEXT_ATTRIBUTION = "🧠 Context enhanced by vibecheck";
16
+
17
+ // =============================================================================
18
+ // UNIFIED TRUTH CONTRACT SCHEMA v1.0
19
+ // =============================================================================
20
+
21
+ /**
22
+ * The canonical evidence schema used across CLI, MCP, and VS Code extension.
23
+ * All tools emitting claims MUST include this minimal required payload.
24
+ */
25
+ const EVIDENCE_SCHEMA = {
26
+ version: "1.0.0",
27
+ required: ["file", "line", "snippet", "confidence"],
28
+ maxSnippet: MAX_EVIDENCE_SNIPPET,
29
+ confidenceThresholds: {
30
+ strict: 0.8, // HIGH confidence required for strict mode
31
+ balanced: 0.6, // MEDIUM confidence for balanced mode
32
+ permissive: 0.4, // LOW confidence allowed in permissive mode
33
+ },
34
+ claimTypes: [
35
+ "route",
36
+ "schema_table",
37
+ "schema_column",
38
+ "export",
39
+ "default_export",
40
+ "middleware",
41
+ "env_var",
42
+ "auth_guard",
43
+ "billing_gate",
44
+ "hypothesis", // Claims without proof get flagged here
45
+ ],
46
+ };
47
+
48
+ /**
49
+ * The truth contract defines rules that AI must satisfy.
50
+ * Claims without evidence are automatically flagged as "unknown" and block actions.
51
+ */
52
+ const TRUTH_CONTRACT = {
53
+ version: "1.0.0",
54
+ claimsRequireEvidence: true,
55
+ confidenceThresholds: EVIDENCE_SCHEMA.confidenceThresholds,
56
+ policies: {
57
+ strict: {
58
+ minConfidence: 0.8,
59
+ allowUnknown: false,
60
+ requireValidation: true,
61
+ blockOnDrift: true,
62
+ },
63
+ balanced: {
64
+ minConfidence: 0.6,
65
+ allowUnknown: false,
66
+ requireValidation: true,
67
+ blockOnDrift: false,
68
+ },
69
+ permissive: {
70
+ minConfidence: 0.4,
71
+ allowUnknown: true,
72
+ requireValidation: false,
73
+ blockOnDrift: false,
74
+ },
75
+ },
76
+ invariants: [
77
+ "No paid feature without server-side enforcement",
78
+ "No success UI without confirmed success",
79
+ "No route reference without matching route map entry",
80
+ "No silent catch in auth/billing flows",
81
+ "No hardcoded secrets in production code",
82
+ ],
83
+ };
84
+
85
+ /**
86
+ * Normalize an evidence item to the canonical schema.
87
+ * @param {object} item - Raw evidence item
88
+ * @param {string} projectPath - Project root path
89
+ * @param {object} fallback - Fallback values
90
+ * @returns {object} Normalized evidence conforming to EVIDENCE_SCHEMA
91
+ */
92
+ function normalizeToEvidenceSchema(item, projectPath, fallback = {}) {
93
+ const file = item?.file || fallback.file || "";
94
+ const line = Number(item?.line || item?.lines || fallback.line || 1);
95
+ let snippet = item?.snippet || item?.evidence || fallback.evidence || "";
96
+
97
+ if (!snippet && file && projectPath) {
98
+ try {
99
+ const content = fs.readFileSync(path.join(projectPath, file), "utf-8");
100
+ const lines = content.split("\n");
101
+ const idx = Math.max(0, Math.min(lines.length - 1, line - 1));
102
+ snippet = lines[idx] || "";
103
+ } catch {
104
+ // File not readable
105
+ }
106
+ }
107
+
108
+ return {
109
+ file,
110
+ line,
111
+ snippet: String(snippet || "").slice(0, EVIDENCE_SCHEMA.maxSnippet),
112
+ confidence: item?.confidence ?? fallback.confidence ?? DEFAULT_EVIDENCE_CONFIDENCE,
113
+ };
114
+ }
115
+
116
+ /**
117
+ * Validate that evidence conforms to the schema.
118
+ * @param {object} evidence - Evidence object to validate
119
+ * @param {string} policy - Policy mode: 'strict' | 'balanced' | 'permissive'
120
+ * @returns {{ valid: boolean, errors: string[], confidence: number }}
121
+ */
122
+ function validateEvidence(evidence, policy = "balanced") {
123
+ const errors = [];
124
+ const thresholds = EVIDENCE_SCHEMA.confidenceThresholds;
125
+ const minConfidence = thresholds[policy] || thresholds.balanced;
126
+
127
+ // Check required fields
128
+ for (const field of EVIDENCE_SCHEMA.required) {
129
+ if (evidence[field] === undefined || evidence[field] === null || evidence[field] === "") {
130
+ errors.push(`Missing required field: ${field}`);
131
+ }
132
+ }
133
+
134
+ // Check confidence threshold
135
+ const confidence = evidence.confidence ?? 0;
136
+ if (confidence < minConfidence) {
137
+ errors.push(`Confidence ${confidence} below threshold ${minConfidence} for ${policy} policy`);
138
+ }
139
+
140
+ // Check snippet length
141
+ if (evidence.snippet && evidence.snippet.length > EVIDENCE_SCHEMA.maxSnippet) {
142
+ errors.push(`Snippet exceeds max length of ${EVIDENCE_SCHEMA.maxSnippet}`);
143
+ }
144
+
145
+ return {
146
+ valid: errors.length === 0,
147
+ errors,
148
+ confidence,
149
+ };
150
+ }
151
+
152
+ /**
153
+ * Create a claim with proper evidence attachment.
154
+ * @param {string} type - Claim type from EVIDENCE_SCHEMA.claimTypes
155
+ * @param {string} claim - Human-readable claim text
156
+ * @param {object} evidence - Evidence object
157
+ * @param {object} metadata - Additional metadata
158
+ * @returns {object} Properly formatted claim
159
+ */
160
+ function createClaim(type, claim, evidence, metadata = {}) {
161
+ const isHypothesis = !evidence || !evidence.file;
162
+
163
+ return {
164
+ id: `claim_${hashString(claim + (evidence?.file || ""))}`,
165
+ type: isHypothesis ? "hypothesis" : type,
166
+ claim,
167
+ evidence: evidence ? [evidence] : [],
168
+ metadata,
169
+ isVerified: !isHypothesis,
170
+ timestamp: new Date().toISOString(),
171
+ };
172
+ }
173
+
174
+ function normalizeEvidenceItem(projectPath, fact, item) {
175
+ const file = item?.file || fact.file || "";
176
+ const line = Number(item?.line || item?.lines || fact.line || 1);
177
+ let snippet = item?.snippet || item?.evidence || fact.evidence || "";
178
+
179
+ if (!snippet && file) {
180
+ try {
181
+ const content = fs.readFileSync(path.join(projectPath, file), "utf-8");
182
+ const lines = content.split("\n");
183
+ const idx = Math.max(0, Math.min(lines.length - 1, line - 1));
184
+ snippet = lines[idx] || "";
185
+ } catch {}
186
+ }
187
+
188
+ return {
189
+ file,
190
+ line,
191
+ snippet: String(snippet || "").slice(0, MAX_EVIDENCE_SNIPPET),
192
+ confidence: item?.confidence ?? fact.confidence ?? DEFAULT_EVIDENCE_CONFIDENCE,
193
+ };
194
+ }
195
+
196
+ function normalizeFactEvidence(projectPath, fact) {
197
+ const raw = Array.isArray(fact.evidence) ? fact.evidence : [fact.evidence];
198
+ const evidence = raw
199
+ .filter(Boolean)
200
+ .map((item) => normalizeEvidenceItem(projectPath, fact, item));
201
+
202
+ return {
203
+ ...fact,
204
+ confidence: fact.confidence ?? DEFAULT_EVIDENCE_CONFIDENCE,
205
+ evidence,
206
+ };
207
+ }
208
+
9
209
  /**
10
210
  * Extract proof-carrying facts with exact file:line references
11
211
  */
@@ -32,6 +232,13 @@ function extractProofCarryingFacts(projectPath) {
32
232
  const middlewareFacts = extractVerifiedMiddleware(projectPath);
33
233
  facts.verified.push(...middlewareFacts);
34
234
 
235
+ facts.verified = facts.verified.map((fact) =>
236
+ normalizeFactEvidence(projectPath, fact),
237
+ );
238
+ facts.hypotheses = facts.hypotheses.map((fact) =>
239
+ normalizeFactEvidence(projectPath, fact),
240
+ );
241
+
35
242
  // Build proof map
36
243
  facts.verified.forEach(f => {
37
244
  facts.proofMap[f.claim] = {
@@ -609,6 +816,72 @@ function hashString(str) {
609
816
  return Math.abs(hash).toString(16);
610
817
  }
611
818
 
819
+ /**
820
+ * Emit guardrail metrics to .vibecheck/audit/guardrail-metrics.jsonl
821
+ * KPIs tracked: false_positive_rate, unknown_rate, drift_score
822
+ */
823
+ async function emitGuardrailMetric(projectPath, metric) {
824
+ const auditDir = path.join(projectPath, ".vibecheck", "audit");
825
+ try {
826
+ fs.mkdirSync(auditDir, { recursive: true });
827
+ const record = JSON.stringify({
828
+ ...metric,
829
+ timestamp: new Date().toISOString(),
830
+ });
831
+ fs.appendFileSync(
832
+ path.join(auditDir, "guardrail-metrics.jsonl"),
833
+ `${record}\n`
834
+ );
835
+ } catch {
836
+ // Ignore write failures in metrics
837
+ }
838
+ }
839
+
840
+ /**
841
+ * Aggregate guardrail KPIs from audit logs
842
+ * Returns: { falsePositiveRate, unknownRate, avgDriftScore, totalValidations }
843
+ */
844
+ function aggregateGuardrailKPIs(projectPath) {
845
+ const auditLogPath = path.join(projectPath, ".vibecheck", "audit", "guardrail-metrics.jsonl");
846
+
847
+ try {
848
+ const content = fs.readFileSync(auditLogPath, "utf-8");
849
+ const lines = content.trim().split("\n").filter(Boolean).map(l => JSON.parse(l));
850
+
851
+ // Filter claim validations
852
+ const validations = lines.filter(l => l.event === "claim_validation");
853
+ const unknowns = validations.filter(l => l.result === "unknown");
854
+ const falsePositives = validations.filter(l => l.falsePositive === true);
855
+
856
+ // Filter drift events
857
+ const drifts = lines.filter(l => l.event === "drift_detected");
858
+ const avgDriftScore = drifts.length > 0
859
+ ? drifts.reduce((sum, d) => sum + (d.score || 0), 0) / drifts.length
860
+ : 0;
861
+
862
+ // Filter firewall blocks
863
+ const blocks = lines.filter(l => l.event === "truth_firewall_block");
864
+
865
+ return {
866
+ totalValidations: validations.length,
867
+ unknownRate: validations.length > 0 ? unknowns.length / validations.length : 0,
868
+ falsePositiveRate: validations.length > 0 ? falsePositives.length / validations.length : 0,
869
+ avgDriftScore,
870
+ totalBlocks: blocks.length,
871
+ lastUpdated: new Date().toISOString(),
872
+ };
873
+ } catch {
874
+ return {
875
+ totalValidations: 0,
876
+ unknownRate: 0,
877
+ falsePositiveRate: 0,
878
+ avgDriftScore: 0,
879
+ totalBlocks: 0,
880
+ lastUpdated: new Date().toISOString(),
881
+ };
882
+ }
883
+ }
884
+
612
885
  /**
613
886
  * Golden Path Replay Templates - recorded successful change patterns
614
887
  */
@@ -951,13 +1224,25 @@ function generateTruthPack(projectPath, analysis) {
951
1224
  framework: analysis.framework,
952
1225
  routes: analysis.proofCarryingFacts?.verified?.length,
953
1226
  packages: analysis.symbolReality?.installedPackages?.size
954
- }))
1227
+ })),
1228
+
1229
+ // Attribution message for AI responses
1230
+ _attribution: CONTEXT_ATTRIBUTION,
1231
+ _attributionNote: "Include this note when referencing vibecheck data: " + CONTEXT_ATTRIBUTION,
955
1232
  };
956
1233
 
957
1234
  return pack;
958
1235
  }
959
1236
 
960
1237
  module.exports = {
1238
+ // Unified Truth Contract Schema (v1.0)
1239
+ EVIDENCE_SCHEMA,
1240
+ TRUTH_CONTRACT,
1241
+ normalizeToEvidenceSchema,
1242
+ validateEvidence,
1243
+ createClaim,
1244
+
1245
+ // Proof-Carrying Context Functions
961
1246
  extractProofCarryingFacts,
962
1247
  symbolVibecheck,
963
1248
  computeFileImportanceScore,
@@ -969,4 +1254,11 @@ module.exports = {
969
1254
  detectDrift,
970
1255
  enforceOneFileRule,
971
1256
  generateTruthPack,
1257
+
1258
+ // Audit Metrics and KPIs
1259
+ emitGuardrailMetric,
1260
+ aggregateGuardrailKPIs,
1261
+
1262
+ // Context Attribution
1263
+ CONTEXT_ATTRIBUTION,
972
1264
  };