@vibecheckai/cli 3.2.0 → 3.2.1

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 (55) hide show
  1. package/bin/runners/lib/agent-firewall/change-packet/builder.js +214 -0
  2. package/bin/runners/lib/agent-firewall/change-packet/schema.json +228 -0
  3. package/bin/runners/lib/agent-firewall/change-packet/store.js +200 -0
  4. package/bin/runners/lib/agent-firewall/claims/claim-types.js +21 -0
  5. package/bin/runners/lib/agent-firewall/claims/extractor.js +214 -0
  6. package/bin/runners/lib/agent-firewall/claims/patterns.js +24 -0
  7. package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +88 -0
  8. package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +75 -0
  9. package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +118 -0
  10. package/bin/runners/lib/agent-firewall/evidence/resolver.js +102 -0
  11. package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +142 -0
  12. package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +145 -0
  13. package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +19 -0
  14. package/bin/runners/lib/agent-firewall/fs-hook/installer.js +87 -0
  15. package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +184 -0
  16. package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +163 -0
  17. package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +107 -0
  18. package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +68 -0
  19. package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +66 -0
  20. package/bin/runners/lib/agent-firewall/interceptor/base.js +304 -0
  21. package/bin/runners/lib/agent-firewall/interceptor/cursor.js +35 -0
  22. package/bin/runners/lib/agent-firewall/interceptor/vscode.js +35 -0
  23. package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +34 -0
  24. package/bin/runners/lib/agent-firewall/policy/default-policy.json +84 -0
  25. package/bin/runners/lib/agent-firewall/policy/engine.js +72 -0
  26. package/bin/runners/lib/agent-firewall/policy/loader.js +143 -0
  27. package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +50 -0
  28. package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +50 -0
  29. package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +61 -0
  30. package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +50 -0
  31. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +50 -0
  32. package/bin/runners/lib/agent-firewall/policy/rules/scope.js +93 -0
  33. package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +57 -0
  34. package/bin/runners/lib/agent-firewall/policy/schema.json +183 -0
  35. package/bin/runners/lib/agent-firewall/policy/verdict.js +54 -0
  36. package/bin/runners/lib/agent-firewall/truthpack/index.js +67 -0
  37. package/bin/runners/lib/agent-firewall/truthpack/loader.js +116 -0
  38. package/bin/runners/lib/agent-firewall/unblock/planner.js +337 -0
  39. package/bin/runners/lib/analysis-core.js +198 -180
  40. package/bin/runners/lib/analyzers.js +1119 -536
  41. package/bin/runners/lib/detectors-v2.js +547 -785
  42. package/bin/runners/lib/fingerprint.js +377 -0
  43. package/bin/runners/lib/route-truth.js +1167 -322
  44. package/bin/runners/lib/scan-output.js +93 -9
  45. package/bin/runners/lib/truth.js +1004 -321
  46. package/bin/runners/runAgent.js +161 -0
  47. package/bin/runners/runFirewall.js +134 -0
  48. package/bin/runners/runFirewallHook.js +56 -0
  49. package/bin/runners/runScan.js +113 -10
  50. package/bin/runners/runTruth.js +89 -0
  51. package/mcp-server/agent-firewall-interceptor.js +164 -0
  52. package/mcp-server/index.js +347 -313
  53. package/mcp-server/truth-context.js +131 -90
  54. package/mcp-server/truth-firewall-tools.js +1412 -1045
  55. package/package.json +1 -1
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Agent Management Command Handler
3
+ *
4
+ * Install/uninstall hooks, status checks.
5
+ */
6
+
7
+ "use strict";
8
+
9
+ const fs = require("fs");
10
+ const path = require("path");
11
+ const { loadPolicy } = require("./lib/agent-firewall/policy/loader");
12
+
13
+ /**
14
+ * Run agent command
15
+ * @param {object} options - Command options
16
+ * @param {string} options.action - Action: 'install', 'status', 'lock', 'unlock'
17
+ * @param {string} options.projectRoot - Project root directory
18
+ */
19
+ async function runAgent(options = {}) {
20
+ const projectRoot = options.projectRoot || process.cwd();
21
+ const action = options.action || "status";
22
+
23
+ switch (action) {
24
+ case "install":
25
+ return installHooks(projectRoot);
26
+
27
+ case "status":
28
+ return showAgentStatus(projectRoot);
29
+
30
+ case "lock":
31
+ return lockRepo(projectRoot);
32
+
33
+ case "unlock":
34
+ return unlockRepo(projectRoot);
35
+
36
+ default:
37
+ throw new Error(`Unknown action: ${action}`);
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Install IDE hooks
43
+ */
44
+ function installHooks(projectRoot) {
45
+ // Create .vibecheck directory if it doesn't exist
46
+ const vibecheckDir = path.join(projectRoot, ".vibecheck");
47
+ if (!fs.existsSync(vibecheckDir)) {
48
+ fs.mkdirSync(vibecheckDir, { recursive: true });
49
+ }
50
+
51
+ // Create hook marker file
52
+ const hookFile = path.join(vibecheckDir, "agent-firewall-enabled");
53
+ fs.writeFileSync(hookFile, JSON.stringify({
54
+ enabled: true,
55
+ installedAt: new Date().toISOString()
56
+ }, null, 2));
57
+
58
+ return {
59
+ success: true,
60
+ message: "Agent Firewall hooks installed. Firewall will intercept AI code changes."
61
+ };
62
+ }
63
+
64
+ /**
65
+ * Show agent status
66
+ */
67
+ function showAgentStatus(projectRoot) {
68
+ const hookFile = path.join(projectRoot, ".vibecheck", "agent-firewall-enabled");
69
+ const installed = fs.existsSync(hookFile);
70
+
71
+ let policy;
72
+ try {
73
+ policy = loadPolicy(projectRoot);
74
+ } catch {
75
+ policy = { mode: "observe", profile: "default" };
76
+ }
77
+
78
+ return {
79
+ installed,
80
+ mode: policy.mode,
81
+ profile: policy.profile,
82
+ locked: policy.mode === "enforce" && policy.profile === "repo-lock"
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Lock repo (enable repo-lock mode)
88
+ */
89
+ function lockRepo(projectRoot) {
90
+ const { loadPolicy, savePolicy, getDefaultPolicy } = require("./lib/agent-firewall/policy/loader");
91
+
92
+ let policy;
93
+ try {
94
+ policy = loadPolicy(projectRoot);
95
+ } catch {
96
+ policy = getDefaultPolicy();
97
+ }
98
+
99
+ policy.mode = "enforce";
100
+ policy.profile = "repo-lock";
101
+
102
+ // Enable all hard rules
103
+ if (!policy.rules) {
104
+ policy.rules = {};
105
+ }
106
+
107
+ const hardRules = [
108
+ "ghost_route",
109
+ "ghost_env",
110
+ "auth_drift",
111
+ "contract_drift",
112
+ "scope_explosion",
113
+ "unsafe_side_effect"
114
+ ];
115
+
116
+ for (const rule of hardRules) {
117
+ if (!policy.rules[rule]) {
118
+ policy.rules[rule] = {};
119
+ }
120
+ policy.rules[rule].enabled = true;
121
+ policy.rules[rule].severity = "block";
122
+ }
123
+
124
+ savePolicy(projectRoot, policy);
125
+
126
+ return {
127
+ success: true,
128
+ message: "Repo Lock Mode enabled. All hard rules enforced."
129
+ };
130
+ }
131
+
132
+ /**
133
+ * Unlock repo (disable repo-lock mode)
134
+ */
135
+ function unlockRepo(projectRoot) {
136
+ const { loadPolicy, savePolicy } = require("./lib/agent-firewall/policy/loader");
137
+
138
+ let policy;
139
+ try {
140
+ policy = loadPolicy(projectRoot);
141
+ } catch {
142
+ return {
143
+ success: false,
144
+ message: "No policy found to unlock"
145
+ };
146
+ }
147
+
148
+ policy.mode = "observe";
149
+ policy.profile = "balanced";
150
+
151
+ savePolicy(projectRoot, policy);
152
+
153
+ return {
154
+ success: true,
155
+ message: "Repo Lock Mode disabled. Firewall in observe mode."
156
+ };
157
+ }
158
+
159
+ module.exports = {
160
+ runAgent
161
+ };
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Firewall Command Handler
3
+ *
4
+ * Main firewall command handler.
5
+ * Enable/disable firewall, set mode, show status and statistics.
6
+ */
7
+
8
+ "use strict";
9
+
10
+ const fs = require("fs");
11
+ const path = require("path");
12
+ const { loadPolicy, savePolicy, getDefaultPolicy } = require("./lib/agent-firewall/policy/loader");
13
+ const { getPacketStats, queryPackets } = require("./lib/agent-firewall/change-packet/store");
14
+ const { isTruthpackFresh } = require("./lib/agent-firewall/truthpack/loader");
15
+
16
+ /**
17
+ * Run firewall command
18
+ * @param {object} options - Command options
19
+ * @param {string} options.mode - Set mode: 'observe' or 'enforce'
20
+ * @param {boolean} options.status - Show firewall status
21
+ * @param {boolean} options.stats - Show statistics
22
+ * @param {string} options.projectRoot - Project root directory
23
+ */
24
+ async function runFirewall(options = {}) {
25
+ const projectRoot = options.projectRoot || process.cwd();
26
+
27
+ if (options.status) {
28
+ return showStatus(projectRoot);
29
+ }
30
+
31
+ if (options.stats) {
32
+ return showStats(projectRoot);
33
+ }
34
+
35
+ if (options.mode) {
36
+ return setMode(projectRoot, options.mode);
37
+ }
38
+
39
+ // Default: show status
40
+ return showStatus(projectRoot);
41
+ }
42
+
43
+ /**
44
+ * Show firewall status
45
+ */
46
+ function showStatus(projectRoot) {
47
+ try {
48
+ const policy = loadPolicy(projectRoot);
49
+ const truthpackFresh = isTruthpackFresh(projectRoot);
50
+
51
+ const output = {
52
+ mode: policy.mode || "observe",
53
+ profile: policy.profile || "default",
54
+ truthpackFresh,
55
+ rulesEnabled: Object.keys(policy.rules || {}).filter(
56
+ key => policy.rules[key]?.enabled !== false
57
+ ).length
58
+ };
59
+
60
+ return output;
61
+ } catch (error) {
62
+ return {
63
+ error: error.message,
64
+ mode: "unknown",
65
+ truthpackFresh: false
66
+ };
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Show firewall statistics
72
+ */
73
+ function showStats(projectRoot) {
74
+ try {
75
+ const stats = getPacketStats(projectRoot);
76
+ const recentPackets = queryPackets(projectRoot, { limit: 10 });
77
+
78
+ return {
79
+ total: stats.total,
80
+ byAgent: stats.byAgent,
81
+ byVerdict: stats.byVerdict,
82
+ byDate: stats.byDate,
83
+ recent: recentPackets.map(p => ({
84
+ id: p.id,
85
+ timestamp: p.timestamp,
86
+ agentId: p.agentId,
87
+ verdict: p.verdict?.decision,
88
+ files: p.files.length
89
+ }))
90
+ };
91
+ } catch (error) {
92
+ return {
93
+ error: error.message,
94
+ total: 0
95
+ };
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Set firewall mode
101
+ */
102
+ function setMode(projectRoot, mode) {
103
+ if (mode !== "observe" && mode !== "enforce") {
104
+ throw new Error(`Invalid mode: ${mode}. Must be 'observe' or 'enforce'`);
105
+ }
106
+
107
+ try {
108
+ let policy;
109
+ try {
110
+ policy = loadPolicy(projectRoot);
111
+ } catch {
112
+ // Policy doesn't exist, use default
113
+ policy = getDefaultPolicy();
114
+ }
115
+
116
+ policy.mode = mode;
117
+ savePolicy(projectRoot, policy);
118
+
119
+ return {
120
+ success: true,
121
+ mode,
122
+ message: `Firewall mode set to: ${mode}`
123
+ };
124
+ } catch (error) {
125
+ return {
126
+ success: false,
127
+ error: error.message
128
+ };
129
+ }
130
+ }
131
+
132
+ module.exports = {
133
+ runFirewall
134
+ };
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Firewall Hook Manager
3
+ *
4
+ * Manages file system hook installation and control.
5
+ */
6
+
7
+ "use strict";
8
+
9
+ const { installFileSystemHook, startFileSystemHook, stopFileSystemHook } = require("./lib/agent-firewall/fs-hook/installer");
10
+ const fs = require("fs");
11
+ const path = require("path");
12
+
13
+ /**
14
+ * Run firewall hook command
15
+ * @param {object} options - Command options
16
+ * @param {string} options.action - Action: 'install', 'start', 'stop', 'status'
17
+ * @param {string} options.projectRoot - Project root directory
18
+ */
19
+ async function runFirewallHook(options = {}) {
20
+ const projectRoot = options.projectRoot || process.cwd();
21
+ const action = options.action || "status";
22
+
23
+ switch (action) {
24
+ case "install":
25
+ return installFileSystemHook(projectRoot);
26
+
27
+ case "start":
28
+ startFileSystemHook(projectRoot);
29
+ return {
30
+ success: true,
31
+ message: "File system hook started"
32
+ };
33
+
34
+ case "stop":
35
+ stopFileSystemHook();
36
+ return {
37
+ success: true,
38
+ message: "File system hook stopped"
39
+ };
40
+
41
+ case "status":
42
+ const markerFile = path.join(projectRoot, ".vibecheck", "fs-hook-enabled");
43
+ const installed = fs.existsSync(markerFile);
44
+ return {
45
+ installed,
46
+ running: false // Would need process management to check
47
+ };
48
+
49
+ default:
50
+ throw new Error(`Unknown action: ${action}`);
51
+ }
52
+ }
53
+
54
+ module.exports = {
55
+ runFirewallHook
56
+ };
@@ -42,6 +42,11 @@ const {
42
42
  calculateScore,
43
43
  } = require("./lib/scan-output");
44
44
 
45
+ const {
46
+ enrichFindings,
47
+ saveBaseline,
48
+ } = require("./lib/fingerprint");
49
+
45
50
  const BANNER = `
46
51
  ${ansi.rgb(0, 200, 255)} ██╗ ██╗██╗██████╗ ███████╗ ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗${ansi.reset}
47
52
  ${ansi.rgb(30, 180, 255)} ██║ ██║██║██╔══██╗██╔════╝██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝${ansi.reset}
@@ -103,6 +108,9 @@ function parseArgs(args) {
103
108
  noBanner: globalFlags.noBanner || false,
104
109
  ci: globalFlags.ci || false,
105
110
  quiet: globalFlags.quiet || false,
111
+ // Baseline tracking (fingerprints)
112
+ baseline: true, // Compare against baseline by default
113
+ updateBaseline: false, // --update-baseline to save current findings as baseline
106
114
  // Allowlist subcommand
107
115
  allowlist: null, // null = not using allowlist, or 'list' | 'add' | 'remove' | 'check'
108
116
  allowlistId: null,
@@ -124,6 +132,8 @@ function parseArgs(args) {
124
132
  else if (arg === '--sarif') opts.sarif = true;
125
133
  else if (arg === '--autofix' || arg === '--fix' || arg === '-f') opts.autofix = true;
126
134
  else if (arg === '--no-save') opts.save = false;
135
+ else if (arg === '--no-baseline') opts.baseline = false;
136
+ else if (arg === '--update-baseline' || arg === '--set-baseline') opts.updateBaseline = true;
127
137
  else if (arg === '--path' || arg === '-p') opts.path = cleanArgs[++i] || process.cwd();
128
138
  else if (arg.startsWith('--path=')) opts.path = arg.split('=')[1];
129
139
  // Allowlist subcommand support
@@ -753,6 +763,7 @@ async function runScan(args) {
753
763
  findDeprecatedApis,
754
764
  findEmptyCatch,
755
765
  findUnsafeRegex,
766
+ clearFileCache, // V3: Memory management
756
767
  } = require('./lib/analyzers');
757
768
 
758
769
  scanRouteIntegrity = async function({ projectPath, layers, baseUrl, verbose }) {
@@ -778,6 +789,9 @@ async function runScan(args) {
778
789
  findings.push(...findEmptyCatch(projectPath));
779
790
  findings.push(...findUnsafeRegex(projectPath));
780
791
 
792
+ // V3: Clear file cache to prevent memory leaks in large monorepos
793
+ clearFileCache();
794
+
781
795
  // Convert to scan format matching TypeScript scanner output
782
796
  const shipBlockers = findings.map((f, i) => ({
783
797
  id: f.id || `finding-${i}`,
@@ -1056,6 +1070,48 @@ async function runScan(args) {
1056
1070
  return getExitCodeFromUnified ? getExitCodeFromUnified(verdict) : getExitCode(verdict);
1057
1071
  }
1058
1072
 
1073
+ // ═══════════════════════════════════════════════════════════════════════════
1074
+ // FINGERPRINTING & BASELINE COMPARISON
1075
+ // ═══════════════════════════════════════════════════════════════════════════
1076
+
1077
+ let diff = null;
1078
+ if (opts.baseline) {
1079
+ try {
1080
+ const enrichResult = enrichFindings(normalizedFindings, projectPath, true);
1081
+ diff = enrichResult.diff;
1082
+
1083
+ // Update findings with fingerprints and status
1084
+ for (let i = 0; i < normalizedFindings.length; i++) {
1085
+ if (enrichResult.findings[i]) {
1086
+ normalizedFindings[i].fingerprint = enrichResult.findings[i].fingerprint;
1087
+ normalizedFindings[i].status = enrichResult.findings[i].status;
1088
+ normalizedFindings[i].firstSeen = enrichResult.findings[i].firstSeen;
1089
+ }
1090
+ }
1091
+ } catch (fpError) {
1092
+ if (opts.verbose) {
1093
+ console.warn(` ${ansi.dim}Fingerprinting skipped: ${fpError.message}${ansi.reset}`);
1094
+ }
1095
+ }
1096
+ }
1097
+
1098
+ // Update baseline if requested
1099
+ if (opts.updateBaseline) {
1100
+ try {
1101
+ saveBaseline(projectPath, normalizedFindings, {
1102
+ verdict: verdict?.verdict,
1103
+ scanTime: new Date().toISOString(),
1104
+ });
1105
+ if (!opts.json && !opts.quiet) {
1106
+ console.log(` ${colors.success}✓${ansi.reset} Baseline updated with ${normalizedFindings.length} findings`);
1107
+ }
1108
+ } catch (blError) {
1109
+ if (opts.verbose) {
1110
+ console.warn(` ${ansi.dim}Baseline save failed: ${blError.message}${ansi.reset}`);
1111
+ }
1112
+ }
1113
+ }
1114
+
1059
1115
  // ═══════════════════════════════════════════════════════════════════════════
1060
1116
  // ENHANCED OUTPUT
1061
1117
  // ═══════════════════════════════════════════════════════════════════════════
@@ -1069,6 +1125,7 @@ async function runScan(args) {
1069
1125
  breakdown: report.score?.breakdown,
1070
1126
  timings,
1071
1127
  cached,
1128
+ diff, // Include diff for display
1072
1129
  };
1073
1130
  console.log(formatScanOutput(resultForOutput, { verbose: opts.verbose }));
1074
1131
 
@@ -1164,6 +1221,60 @@ async function runScan(args) {
1164
1221
 
1165
1222
  const verdict = criticalCount > 0 ? 'BLOCK' : warningCount > 0 ? 'WARN' : 'SHIP';
1166
1223
 
1224
+ // ═══════════════════════════════════════════════════════════════════════════
1225
+ // FINGERPRINTING & BASELINE COMPARISON (Legacy path)
1226
+ // ═══════════════════════════════════════════════════════════════════════════
1227
+
1228
+ const normalizedLegacyFindings = findings.map(f => ({
1229
+ severity: f.severity === 'critical' || f.severity === 'BLOCK' ? 'critical' :
1230
+ f.severity === 'warning' || f.severity === 'WARN' ? 'medium' : 'low',
1231
+ category: f.category || 'ROUTE',
1232
+ title: f.title || f.message,
1233
+ message: f.message || f.title,
1234
+ file: f.file || f.evidence?.[0]?.file,
1235
+ line: f.line || parseInt(f.evidence?.[0]?.lines?.split('-')[0]) || 1,
1236
+ evidence: f.evidence,
1237
+ fix: f.fixSuggestion,
1238
+ }));
1239
+
1240
+ let diff = null;
1241
+ if (opts.baseline) {
1242
+ try {
1243
+ const enrichResult = enrichFindings(normalizedLegacyFindings, projectPath, true);
1244
+ diff = enrichResult.diff;
1245
+
1246
+ // Update findings with fingerprints and status
1247
+ for (let i = 0; i < normalizedLegacyFindings.length; i++) {
1248
+ if (enrichResult.findings[i]) {
1249
+ normalizedLegacyFindings[i].fingerprint = enrichResult.findings[i].fingerprint;
1250
+ normalizedLegacyFindings[i].status = enrichResult.findings[i].status;
1251
+ normalizedLegacyFindings[i].firstSeen = enrichResult.findings[i].firstSeen;
1252
+ }
1253
+ }
1254
+ } catch (fpError) {
1255
+ if (opts.verbose) {
1256
+ console.warn(` ${ansi.dim}Fingerprinting skipped: ${fpError.message}${ansi.reset}`);
1257
+ }
1258
+ }
1259
+ }
1260
+
1261
+ // Update baseline if requested
1262
+ if (opts.updateBaseline) {
1263
+ try {
1264
+ saveBaseline(projectPath, normalizedLegacyFindings, {
1265
+ verdict,
1266
+ scanTime: new Date().toISOString(),
1267
+ });
1268
+ if (!opts.json && !opts.quiet) {
1269
+ console.log(` ${colors.success}✓${ansi.reset} Baseline updated with ${normalizedLegacyFindings.length} findings`);
1270
+ }
1271
+ } catch (blError) {
1272
+ if (opts.verbose) {
1273
+ console.warn(` ${ansi.dim}Baseline save failed: ${blError.message}${ansi.reset}`);
1274
+ }
1275
+ }
1276
+ }
1277
+
1167
1278
  // Use enhanced output formatter for legacy fallback
1168
1279
  const severityCounts = {
1169
1280
  critical: criticalCount,
@@ -1175,18 +1286,10 @@ async function runScan(args) {
1175
1286
 
1176
1287
  const result = {
1177
1288
  verdict: { verdict, score },
1178
- findings: findings.map(f => ({
1179
- severity: f.severity === 'critical' || f.severity === 'BLOCK' ? 'critical' :
1180
- f.severity === 'warning' || f.severity === 'WARN' ? 'medium' : 'low',
1181
- category: f.category || 'ROUTE',
1182
- title: f.title || f.message,
1183
- message: f.message || f.title,
1184
- file: f.file,
1185
- line: f.line,
1186
- fix: f.fixSuggestion,
1187
- })),
1289
+ findings: normalizedLegacyFindings,
1188
1290
  layers: [],
1189
1291
  timings,
1292
+ diff,
1190
1293
  };
1191
1294
 
1192
1295
  console.log(formatScanOutput(result, { verbose: opts.verbose }));
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Truth Command Handler
3
+ *
4
+ * Enhanced truth command to generate truthpack files.
5
+ * Generates: routes.json, env.json, auth.json, contracts.json, ui.graph.json
6
+ */
7
+
8
+ "use strict";
9
+
10
+ const fs = require("fs");
11
+ const path = require("path");
12
+ const { buildTruthpackV2 } = require("./lib/extractors/truthpack-v2");
13
+
14
+ /**
15
+ * Run truth command
16
+ * @param {object} options - Command options
17
+ * @param {string} options.scope - Scope: 'routes', 'env', 'auth', 'contracts', 'all'
18
+ * @param {string} options.projectRoot - Project root directory
19
+ */
20
+ async function runTruth(options = {}) {
21
+ const projectRoot = options.projectRoot || process.cwd();
22
+ const scope = options.scope || "all";
23
+
24
+ const truthpackDir = path.join(projectRoot, ".vibecheck", "truthpack");
25
+
26
+ // Ensure directory exists
27
+ if (!fs.existsSync(truthpackDir)) {
28
+ fs.mkdirSync(truthpackDir, { recursive: true });
29
+ }
30
+
31
+ try {
32
+ // Build truthpack
33
+ const truthpack = await buildTruthpackV2({
34
+ repoRoot: projectRoot
35
+ });
36
+
37
+ // Write truthpack files based on scope
38
+ if (scope === "all" || scope === "routes") {
39
+ const routesFile = path.join(truthpackDir, "routes.json");
40
+ fs.writeFileSync(routesFile, JSON.stringify({
41
+ routes: truthpack.routes || [],
42
+ gaps: truthpack.gaps || [],
43
+ stack: truthpack.stack || {}
44
+ }, null, 2));
45
+ }
46
+
47
+ if (scope === "all" || scope === "env") {
48
+ const envFile = path.join(truthpackDir, "env.json");
49
+ fs.writeFileSync(envFile, JSON.stringify(truthpack.env || {}, null, 2));
50
+ }
51
+
52
+ if (scope === "all" || scope === "auth") {
53
+ const authFile = path.join(truthpackDir, "auth.json");
54
+ fs.writeFileSync(authFile, JSON.stringify(truthpack.auth || {}, null, 2));
55
+ }
56
+
57
+ if (scope === "all" || scope === "contracts") {
58
+ const contractsFile = path.join(truthpackDir, "contracts.json");
59
+ fs.writeFileSync(contractsFile, JSON.stringify(truthpack.contracts || {}, null, 2));
60
+ }
61
+
62
+ // UI graph (if Reality enabled)
63
+ if (scope === "all" && truthpack.uiGraph) {
64
+ const uiGraphFile = path.join(truthpackDir, "ui.graph.json");
65
+ fs.writeFileSync(uiGraphFile, JSON.stringify(truthpack.uiGraph, null, 2));
66
+ }
67
+
68
+ return {
69
+ success: true,
70
+ message: `Truthpack generated successfully (scope: ${scope})`,
71
+ files: {
72
+ routes: scope === "all" || scope === "routes" ? "routes.json" : null,
73
+ env: scope === "all" || scope === "env" ? "env.json" : null,
74
+ auth: scope === "all" || scope === "auth" ? "auth.json" : null,
75
+ contracts: scope === "all" || scope === "contracts" ? "contracts.json" : null,
76
+ uiGraph: scope === "all" && truthpack.uiGraph ? "ui.graph.json" : null
77
+ }
78
+ };
79
+ } catch (error) {
80
+ return {
81
+ success: false,
82
+ error: error.message
83
+ };
84
+ }
85
+ }
86
+
87
+ module.exports = {
88
+ runTruth
89
+ };