@vibecheckai/cli 3.2.0 → 3.2.2

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 (60) 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/cli-output.js +236 -210
  42. package/bin/runners/lib/detectors-v2.js +547 -785
  43. package/bin/runners/lib/fingerprint.js +377 -0
  44. package/bin/runners/lib/route-truth.js +1167 -322
  45. package/bin/runners/lib/scan-output.js +144 -738
  46. package/bin/runners/lib/ship-output-enterprise.js +239 -0
  47. package/bin/runners/lib/terminal-ui.js +188 -770
  48. package/bin/runners/lib/truth.js +1004 -321
  49. package/bin/runners/lib/unified-output.js +162 -158
  50. package/bin/runners/runAgent.js +161 -0
  51. package/bin/runners/runFirewall.js +134 -0
  52. package/bin/runners/runFirewallHook.js +56 -0
  53. package/bin/runners/runScan.js +113 -10
  54. package/bin/runners/runShip.js +7 -8
  55. package/bin/runners/runTruth.js +89 -0
  56. package/mcp-server/agent-firewall-interceptor.js +164 -0
  57. package/mcp-server/index.js +347 -313
  58. package/mcp-server/truth-context.js +131 -90
  59. package/mcp-server/truth-firewall-tools.js +1412 -1045
  60. package/package.json +1 -1
@@ -27,14 +27,37 @@ import {
27
27
  } from "@modelcontextprotocol/sdk/types.js";
28
28
 
29
29
  import fs from "fs/promises";
30
+ import fsSync from "fs";
30
31
  import path from "path";
31
32
  import { fileURLToPath } from "url";
32
- import { execSync } from "child_process";
33
+ import { execFile } from "child_process";
34
+ import { promisify } from "util";
35
+
36
+ const execFileAsync = promisify(execFile);
33
37
 
34
38
  const __filename = fileURLToPath(import.meta.url);
35
39
  const __dirname = path.dirname(__filename);
36
40
 
37
- const VERSION = "2.1.0";
41
+ // ============================================================================
42
+ // CENTRALIZED CONFIGURATION
43
+ // ============================================================================
44
+ const CONFIG = {
45
+ VERSION: "2.1.0",
46
+ BIN_PATH: path.join(__dirname, "..", "bin", "vibecheck.js"),
47
+ OUTPUT_DIR: ".vibecheck",
48
+ ENV_DEFAULTS: { VIBECHECK_SKIP_AUTH: "1" },
49
+ TIMEOUTS: {
50
+ DEFAULT: 30000, // 30 seconds
51
+ SCAN: 120000, // 2 minutes
52
+ VERIFY: 180000, // 3 minutes
53
+ REALITY: 300000, // 5 minutes
54
+ PROVE: 600000, // 10 minutes
55
+ AUTOPILOT: 300000, // 5 minutes
56
+ },
57
+ MAX_BUFFER: 10 * 1024 * 1024, // 10MB
58
+ };
59
+
60
+ const VERSION = CONFIG.VERSION;
38
61
 
39
62
  // Import intelligence tools
40
63
  import {
@@ -106,6 +129,12 @@ import { MCP_TOOLS_V3, handleToolV3, TOOL_TIERS as V3_TOOL_TIERS } from "./tools
106
129
  // Import tier auth for entitlement checking
107
130
  import { checkFeatureAccess } from "./tier-auth.js";
108
131
 
132
+ // Import Agent Firewall Interceptor
133
+ import {
134
+ AGENT_FIREWALL_TOOL,
135
+ handleAgentFirewallIntercept,
136
+ } from "./agent-firewall-interceptor.js";
137
+
109
138
  /**
110
139
  * TRUTH FIREWALL CONFIGURATION
111
140
  *
@@ -204,11 +233,15 @@ const USE_CONSOLIDATED_TOOLS = process.env.VIBECHECK_MCP_CONSOLIDATED !== 'false
204
233
  const TOOLS = USE_V3_TOOLS ? [
205
234
  // v3: 10 focused tools for STARTER+ (no free MCP tools)
206
235
  ...MCP_TOOLS_V3,
236
+ AGENT_FIREWALL_TOOL, // Agent Firewall - intercepts file writes
207
237
  ] : USE_CONSOLIDATED_TOOLS ? [
208
238
  // Curated tools for agents (legacy)
209
239
  ...CONSOLIDATED_TOOLS,
240
+ AGENT_FIREWALL_TOOL, // Agent Firewall - intercepts file writes
210
241
  ] : [
211
242
  // Legacy: Full tool set (50+ tools) - for backward compatibility
243
+ // PRIORITY: Agent Firewall - intercepts ALL file writes
244
+ AGENT_FIREWALL_TOOL,
212
245
  // PRIORITY: Truth Firewall tools (Hallucination Stopper) - agents MUST use these
213
246
  ...TRUTH_FIREWALL_TOOLS, // vibecheck.get_truthpack, vibecheck.validate_claim, vibecheck.compile_context, etc.
214
247
 
@@ -824,16 +857,122 @@ class VibecheckMCP {
824
857
  { name: "vibecheck", version: VERSION },
825
858
  { capabilities: { tools: {}, resources: {} } },
826
859
  );
860
+ this.toolRegistry = this.buildToolRegistry();
827
861
  this.setupHandlers();
828
862
  }
829
863
 
864
+ // ============================================================================
865
+ // TOOL REGISTRY - Maps tool names to handlers for cleaner dispatch
866
+ // ============================================================================
867
+ buildToolRegistry() {
868
+ return {
869
+ // Agent Firewall - intercepts file writes
870
+ "vibecheck_agent_firewall_intercept": handleAgentFirewallIntercept,
871
+ // Core CLI tools
872
+ "vibecheck.ship": this.handleShip.bind(this),
873
+ "vibecheck.scan": this.handleScan.bind(this),
874
+ "vibecheck.verify": this.handleVerify.bind(this),
875
+ "vibecheck.reality": this.handleReality.bind(this),
876
+ "vibecheckai.dev-test": this.handleAITest.bind(this),
877
+ "vibecheck.gate": this.handleGate.bind(this),
878
+ "vibecheck.fix": this.handleFix.bind(this),
879
+ "vibecheck.share": this.handleShare.bind(this),
880
+ "vibecheck.ctx": this.handleCtx.bind(this),
881
+ "vibecheck.prove": this.handleProve.bind(this),
882
+ "vibecheck.proof": this.handleProof.bind(this),
883
+ "vibecheck.validate": this.handleValidate.bind(this),
884
+ "vibecheck.report": this.handleReport.bind(this),
885
+ "vibecheck.status": this.handleStatus.bind(this),
886
+ "vibecheck.autopilot": this.handleAutopilot.bind(this),
887
+ "vibecheck.autopilot_plan": this.handleAutopilotPlan.bind(this),
888
+ "vibecheck.autopilot_apply": this.handleAutopilotApply.bind(this),
889
+ "vibecheck.badge": this.handleBadge.bind(this),
890
+ "vibecheck.context": this.handleContext.bind(this),
891
+ };
892
+ }
893
+
894
+ // ============================================================================
895
+ // CLI RUNNER - Secure, async execution with array args (prevents injection)
896
+ // ============================================================================
897
+ async runCLI(command, args = [], cwd, options = {}) {
898
+ const {
899
+ env = {},
900
+ timeout = CONFIG.TIMEOUTS.DEFAULT,
901
+ skipAuth = true,
902
+ } = options;
903
+
904
+ // Build argument array - this prevents command injection
905
+ const finalArgs = [CONFIG.BIN_PATH, command, ...args];
906
+
907
+ const execEnv = {
908
+ ...process.env,
909
+ ...CONFIG.ENV_DEFAULTS,
910
+ ...(skipAuth ? { VIBECHECK_SKIP_AUTH: "1" } : {}),
911
+ ...env,
912
+ };
913
+
914
+ try {
915
+ const { stdout, stderr } = await execFileAsync(process.execPath, finalArgs, {
916
+ cwd,
917
+ encoding: "utf8",
918
+ maxBuffer: CONFIG.MAX_BUFFER,
919
+ timeout,
920
+ env: execEnv,
921
+ });
922
+ return { stdout, stderr, success: true };
923
+ } catch (error) {
924
+ // Attach partial output for graceful degradation
925
+ error.partialOutput = error.stdout || "";
926
+ error.partialStderr = error.stderr || "";
927
+ throw error;
928
+ }
929
+ }
930
+
931
+ // ============================================================================
932
+ // UTILITY HELPERS
933
+ // ============================================================================
934
+
935
+ // Strip ANSI escape codes from output
936
+ stripAnsi(str) {
937
+ return str ? str.replace(/\x1b\[[0-9;]*m/g, "") : "";
938
+ }
939
+
940
+ // Parse summary from disk (for graceful degradation on CLI errors)
941
+ async parseSummaryFromDisk(projectPath) {
942
+ const summaryPath = path.join(projectPath, CONFIG.OUTPUT_DIR, "summary.json");
943
+ try {
944
+ const content = await fs.readFile(summaryPath, "utf-8");
945
+ return JSON.parse(content);
946
+ } catch {
947
+ return null;
948
+ }
949
+ }
950
+
951
+ // Format scan output from summary object
952
+ formatScanOutput(summary, projectPath) {
953
+ let output = `## Score: ${summary.score}/100 (${summary.grade})\n\n`;
954
+ output += `**Verdict:** ${summary.canShip ? "āœ… SHIP" : "🚫 NO-SHIP"}\n\n`;
955
+
956
+ if (summary.counts) {
957
+ output += "### Checks\n\n";
958
+ output += "| Category | Issues |\n|----------|--------|\n";
959
+ for (const [key, count] of Object.entries(summary.counts)) {
960
+ const icon = count === 0 ? "āœ…" : "āš ļø";
961
+ output += `| ${icon} ${key} | ${count} |\n`;
962
+ }
963
+ }
964
+
965
+ output += `\nšŸ“„ **Report:** ${CONFIG.OUTPUT_DIR}/report.html\n`;
966
+ return output;
967
+ }
968
+
830
969
  setupHandlers() {
831
970
  // List tools
832
971
  this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
833
972
  tools: TOOLS,
834
973
  }));
835
974
 
836
- // Call tool
975
+ // Call tool - main dispatch handler
837
976
  this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
838
977
  const { name, arguments: args } = request.params;
839
978
  const projectPath = path.resolve(args?.projectPath || ".");
@@ -862,7 +1001,6 @@ class VibecheckMCP {
862
1001
 
863
1002
  // Handle v3 tools (10 consolidated tools, STARTER+ only)
864
1003
  if (USE_V3_TOOLS && V3_TOOL_TIERS[name]) {
865
- // Get user tier from context or args
866
1004
  const userTier = args?.tier || process.env.VIBECHECK_TIER || 'free';
867
1005
  const result = await handleToolV3(name, args, { tier: userTier });
868
1006
 
@@ -875,12 +1013,17 @@ class VibecheckMCP {
875
1013
  };
876
1014
  }
877
1015
 
878
- // Handle intelligence tools first
1016
+ // 1. Check tool registry first (local CLI handlers)
1017
+ if (this.toolRegistry[name]) {
1018
+ return await this.toolRegistry[name](projectPath, args);
1019
+ }
1020
+
1021
+ // 2. Handle external module tools by prefix/pattern
879
1022
  if (name.startsWith("vibecheck.intelligence.")) {
880
1023
  return await handleIntelligenceTool(name, args, __dirname);
881
1024
  }
882
1025
 
883
- // Handle AI vibecheck tools (verify, quality, smells, hallucination, breaking, mdc, coverage)
1026
+ // Handle AI vibecheck tools
884
1027
  if (["vibecheck.verify", "vibecheck.quality", "vibecheck.smells",
885
1028
  "vibecheck.hallucination", "vibecheck.breaking", "vibecheck.mdc",
886
1029
  "vibecheck.coverage"].includes(name)) {
@@ -924,64 +1067,24 @@ class VibecheckMCP {
924
1067
  }
925
1068
  }
926
1069
 
927
- switch (name) {
928
- case "vibecheck.ship":
929
- return await this.handleShip(projectPath, args);
930
- case "vibecheck.scan":
931
- return await this.handleScan(projectPath, args);
932
- case "vibecheck.verify":
933
- return await this.handleVerify(projectPath, args);
934
- case "vibecheck.reality":
935
- return await this.handleReality(projectPath, args);
936
- case "vibecheckai.dev-test":
937
- return await this.handleAITest(projectPath, args);
938
- case "vibecheck.gate":
939
- return await this.handleGate(projectPath, args);
940
- case "vibecheck.fix":
941
- return await this.handleFix(projectPath, args);
942
- case "vibecheck.share":
943
- return await this.handleShare(projectPath, args);
944
- case "vibecheck.ctx":
945
- return await this.handleCtx(projectPath, args);
946
- case "vibecheck.prove":
947
- return await this.handleProve(projectPath, args);
948
- case "vibecheck.proof":
949
- return await this.handleProof(projectPath, args);
950
- case "vibecheck.validate":
951
- return await this.handleValidate(projectPath, args);
952
- case "vibecheck.report":
953
- return await this.handleReport(projectPath, args);
954
- case "vibecheck.status":
955
- return await this.handleStatus(projectPath, args);
956
- case "vibecheck.autopilot":
957
- return await this.handleAutopilot(projectPath, args);
958
- case "vibecheck.autopilot_plan":
959
- return await this.handleAutopilotPlan(projectPath, args);
960
- case "vibecheck.autopilot_apply":
961
- return await this.handleAutopilotApply(projectPath, args);
962
- case "vibecheck.badge":
963
- return await this.handleBadge(projectPath, args);
964
- case "vibecheck.context":
965
- return await this.handleContext(projectPath, args);
966
- case "generate_mdc":
967
- return await handleMDCGeneration(args);
968
- // Truth Context tools (Evidence Pack / Truth Pack)
969
- case "vibecheck.verify_claim":
970
- case "vibecheck.evidence":
971
- return await handleTruthContextTool(name, args);
972
- // Truth Firewall tools (Hallucination Stopper)
973
- case "vibecheck.get_truthpack":
974
- case "vibecheck.validate_claim":
975
- case "vibecheck.compile_context":
976
- case "vibecheck.search_evidence":
977
- case "vibecheck.find_counterexamples":
978
- case "vibecheck.propose_patch":
979
- case "vibecheck.check_invariants":
980
- case "vibecheck.add_assumption":
981
- return await handleTruthFirewallTool(name, args, projectPath);
982
- default:
983
- return this.error(`Unknown tool: ${name}`);
1070
+ // Handle MDC generator
1071
+ if (name === "generate_mdc") {
1072
+ return await handleMDCGeneration(args);
984
1073
  }
1074
+
1075
+ // Handle Truth Context tools (Evidence Pack / Truth Pack)
1076
+ if (["vibecheck.verify_claim", "vibecheck.evidence"].includes(name)) {
1077
+ return await handleTruthContextTool(name, args);
1078
+ }
1079
+
1080
+ // Handle Truth Firewall tools (Hallucination Stopper)
1081
+ if (["vibecheck.get_truthpack", "vibecheck.validate_claim", "vibecheck.compile_context",
1082
+ "vibecheck.search_evidence", "vibecheck.find_counterexamples", "vibecheck.propose_patch",
1083
+ "vibecheck.check_invariants", "vibecheck.add_assumption"].includes(name)) {
1084
+ return await handleTruthFirewallTool(name, args, projectPath);
1085
+ }
1086
+
1087
+ return this.error(`Unknown tool: ${name}`);
985
1088
  } catch (err) {
986
1089
  // Emit audit event for tool error
987
1090
  emitToolComplete(name, "error", {
@@ -1209,10 +1312,10 @@ class VibecheckMCP {
1209
1312
  return { content: [{ type: "text", text: errorText }], isError: true };
1210
1313
  }
1211
1314
 
1212
- // Validate project path exists and is accessible
1315
+ // Validate project path exists and is accessible (sync for simple validation)
1213
1316
  validateProjectPath(projectPath) {
1214
1317
  try {
1215
- const stats = require("fs").statSync(projectPath);
1318
+ const stats = fsSync.statSync(projectPath);
1216
1319
  if (!stats.isDirectory()) {
1217
1320
  return {
1218
1321
  valid: false,
@@ -1255,46 +1358,35 @@ class VibecheckMCP {
1255
1358
  }
1256
1359
 
1257
1360
  const profile = args?.profile || "quick";
1258
- const format = args?.format || "text";
1259
1361
  const only = args?.only;
1260
1362
 
1261
1363
  let output = "# šŸ” vibecheck Scan\n\n";
1262
1364
  output += `**Profile:** ${profile}\n`;
1263
1365
  output += `**Path:** ${projectPath}\n\n`;
1264
1366
 
1265
- try {
1266
- // Build CLI command
1267
- let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" scan`;
1268
- cmd += ` --profile=${profile}`;
1269
- if (only?.length) cmd += ` --only=${only.join(",")}`;
1270
- cmd += ` --json`;
1271
-
1272
- const result = execSync(cmd, {
1273
- cwd: projectPath,
1274
- encoding: "utf8",
1275
- maxBuffer: 10 * 1024 * 1024,
1276
- });
1277
-
1278
- // Read summary
1279
- const summaryPath = path.join(projectPath, ".vibecheck", "summary.json");
1280
- const summary = JSON.parse(await fs.readFile(summaryPath, "utf-8"));
1367
+ // Build CLI arguments array (secure - no injection possible)
1368
+ const cliArgs = [`--profile=${profile}`, "--json"];
1369
+ if (only?.length) cliArgs.push(`--only=${only.join(",")}`);
1281
1370
 
1282
- output += `## Score: ${summary.score}/100 (${summary.grade})\n\n`;
1283
- output += `**Verdict:** ${summary.canShip ? "āœ… SHIP" : "🚫 NO-SHIP"}\n\n`;
1371
+ try {
1372
+ await this.runCLI("scan", cliArgs, projectPath, { timeout: CONFIG.TIMEOUTS.SCAN });
1284
1373
 
1285
- if (summary.counts) {
1286
- output += "### Checks\n\n";
1287
- output += "| Category | Issues |\n|----------|--------|\n";
1288
- for (const [key, count] of Object.entries(summary.counts)) {
1289
- const icon = count === 0 ? "āœ…" : "āš ļø";
1290
- output += `| ${icon} ${key} | ${count} |\n`;
1291
- }
1374
+ // Read summary from disk
1375
+ const summary = await this.parseSummaryFromDisk(projectPath);
1376
+ if (summary) {
1377
+ output += this.formatScanOutput(summary, projectPath);
1292
1378
  }
1293
-
1294
- output += `\nšŸ“„ **Report:** .vibecheck/report.html\n`;
1295
1379
  output += "\n---\n_Context Enhanced by vibecheck AI_\n";
1296
1380
  return this.success(output);
1297
1381
  } catch (err) {
1382
+ // Graceful degradation: check if scan completed but found issues (exit code 1)
1383
+ const summary = await this.parseSummaryFromDisk(projectPath);
1384
+ if (summary) {
1385
+ output += this.formatScanOutput(summary, projectPath);
1386
+ output += `\nāš ļø Scan completed with findings (exit code ${err.code || 1})\n`;
1387
+ return this.success(output);
1388
+ }
1389
+
1298
1390
  return this.error(`Scan failed: ${err.message}`, {
1299
1391
  code: "SCAN_ERROR",
1300
1392
  suggestion: "Check that the project path is valid and contains scanable code",
@@ -1329,16 +1421,12 @@ class VibecheckMCP {
1329
1421
  let output = "# 🚦 vibecheck Gate\n\n";
1330
1422
  output += `**Policy:** ${policy}\n\n`;
1331
1423
 
1332
- try {
1333
- let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" gate`;
1334
- cmd += ` --policy=${policy}`;
1335
- if (args?.sarif) cmd += ` --sarif`;
1424
+ // Build CLI arguments array (secure)
1425
+ const cliArgs = [`--policy=${policy}`];
1426
+ if (args?.sarif) cliArgs.push("--sarif");
1336
1427
 
1337
- execSync(cmd, {
1338
- cwd: projectPath,
1339
- encoding: "utf8",
1340
- maxBuffer: 10 * 1024 * 1024,
1341
- });
1428
+ try {
1429
+ await this.runCLI("gate", cliArgs, projectPath);
1342
1430
 
1343
1431
  output += "## āœ… GATE PASSED\n\n";
1344
1432
  output += "All checks passed. Clear to merge.\n";
@@ -1371,35 +1459,35 @@ class VibecheckMCP {
1371
1459
 
1372
1460
  const mode = args?.autopilot ? "Autopilot" :
1373
1461
  args?.apply ? "Apply" :
1374
- args?.promptOnly ? "Prompt Only" : "Plan";
1462
+ args?.promptOnly ? "Prompt Only" :
1463
+ args?.dryRun ? "Dry Run" : "Plan";
1375
1464
 
1376
1465
  let output = "# šŸ›  vibecheck Fix Missions v1\n\n";
1377
1466
  output += `**Mode:** ${mode}\n`;
1378
1467
  output += `**Max Missions:** ${args?.maxMissions || 8}\n`;
1379
1468
  if (args?.autopilot) output += `**Max Steps:** ${args?.maxSteps || 10}\n`;
1469
+ if (args?.dryRun) output += `**Dry Run:** Yes (no changes will be made)\n`;
1380
1470
  output += "\n";
1381
1471
 
1472
+ // Build CLI arguments array (secure)
1473
+ const cliArgs = [];
1474
+ if (args?.promptOnly) cliArgs.push("--prompt-only");
1475
+ if (args?.apply) cliArgs.push("--apply");
1476
+ if (args?.autopilot) cliArgs.push("--autopilot");
1477
+ if (args?.share) cliArgs.push("--share");
1478
+ if (args?.dryRun) cliArgs.push("--dry-run");
1479
+ if (args?.maxMissions) cliArgs.push("--max-missions", String(args.maxMissions));
1480
+ if (args?.maxSteps) cliArgs.push("--max-steps", String(args.maxSteps));
1481
+
1382
1482
  try {
1383
- let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" fix`;
1384
- if (args?.promptOnly) cmd += ` --prompt-only`;
1385
- if (args?.apply) cmd += ` --apply`;
1386
- if (args?.autopilot) cmd += ` --autopilot`;
1387
- if (args?.share) cmd += ` --share`;
1388
- if (args?.maxMissions) cmd += ` --max-missions ${args.maxMissions}`;
1389
- if (args?.maxSteps) cmd += ` --max-steps ${args.maxSteps}`;
1390
-
1391
- const result = execSync(cmd, {
1392
- cwd: projectPath,
1393
- encoding: "utf8",
1394
- maxBuffer: 10 * 1024 * 1024,
1395
- timeout: 300000, // 5 min timeout for autopilot
1396
- env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
1483
+ const result = await this.runCLI("fix", cliArgs, projectPath, {
1484
+ timeout: CONFIG.TIMEOUTS.AUTOPILOT
1397
1485
  });
1398
1486
 
1399
- output += result.replace(/\x1b\[[0-9;]*m/g, ""); // Strip ANSI codes
1487
+ output += this.stripAnsi(result.stdout);
1400
1488
 
1401
1489
  // Read mission pack if available
1402
- const missionsDir = path.join(projectPath, ".vibecheck", "missions");
1490
+ const missionsDir = path.join(projectPath, CONFIG.OUTPUT_DIR, "missions");
1403
1491
  try {
1404
1492
  const dirs = await fs.readdir(missionsDir);
1405
1493
  const latest = dirs.sort().reverse()[0];
@@ -1413,12 +1501,12 @@ class VibecheckMCP {
1413
1501
  const m = missions.missions[i];
1414
1502
  output += `| ${i + 1} | ${m.title} | ${m.targetFindingIds.length} |\n`;
1415
1503
  }
1416
- output += `\nšŸ“ **Mission Pack:** .vibecheck/missions/${latest}\n`;
1504
+ output += `\nšŸ“ **Mission Pack:** ${CONFIG.OUTPUT_DIR}/missions/${latest}\n`;
1417
1505
  }
1418
1506
  } catch {}
1419
1507
  } catch (err) {
1420
1508
  output += `\nāš ļø Fix error: ${err.message}\n`;
1421
- if (err.stdout) output += `\n${err.stdout.replace(/\x1b\[[0-9;]*m/g, "")}\n`;
1509
+ if (err.partialOutput) output += `\n${this.stripAnsi(err.partialOutput)}\n`;
1422
1510
  }
1423
1511
 
1424
1512
  output += "\n---\n_Fix Missions v1 — Reality Firewall Protected_\n";
@@ -1431,22 +1519,18 @@ class VibecheckMCP {
1431
1519
  async handleShare(projectPath, args) {
1432
1520
  let output = "# šŸ“¦ vibecheck Share Bundle\n\n";
1433
1521
 
1434
- try {
1435
- let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" share`;
1436
- if (args?.prComment) cmd += ` --pr-comment`;
1437
- if (args?.out) cmd += ` --out "${args.out}"`;
1522
+ // Build CLI arguments array (secure)
1523
+ const cliArgs = [];
1524
+ if (args?.prComment) cliArgs.push("--pr-comment");
1525
+ if (args?.out) cliArgs.push("--out", args.out);
1438
1526
 
1439
- const result = execSync(cmd, {
1440
- cwd: projectPath,
1441
- encoding: "utf8",
1442
- maxBuffer: 10 * 1024 * 1024,
1443
- env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
1444
- });
1527
+ try {
1528
+ const result = await this.runCLI("share", cliArgs, projectPath);
1445
1529
 
1446
- output += result.replace(/\x1b\[[0-9;]*m/g, "");
1530
+ output += this.stripAnsi(result.stdout);
1447
1531
 
1448
1532
  // Read share pack if available
1449
- const missionsDir = path.join(projectPath, ".vibecheck", "missions");
1533
+ const missionsDir = path.join(projectPath, CONFIG.OUTPUT_DIR, "missions");
1450
1534
  try {
1451
1535
  const dirs = await fs.readdir(missionsDir);
1452
1536
  const latest = dirs.sort().reverse()[0];
@@ -1469,14 +1553,14 @@ class VibecheckMCP {
1469
1553
  }
1470
1554
  }
1471
1555
 
1472
- output += `\nšŸ“ **Share Pack:** .vibecheck/missions/${latest}/share/\n`;
1473
- output += `šŸ“„ **PR Comment:** .vibecheck/missions/${latest}/share/pr_comment.md\n`;
1556
+ output += `\nšŸ“ **Share Pack:** ${CONFIG.OUTPUT_DIR}/missions/${latest}/share/\n`;
1557
+ output += `šŸ“„ **PR Comment:** ${CONFIG.OUTPUT_DIR}/missions/${latest}/share/pr_comment.md\n`;
1474
1558
  } catch {}
1475
1559
  }
1476
1560
  } catch {}
1477
1561
  } catch (err) {
1478
1562
  output += `\nāš ļø Share error: ${err.message}\n`;
1479
- if (err.stdout) output += `\n${err.stdout.replace(/\x1b\[[0-9;]*m/g, "")}\n`;
1563
+ if (err.partialOutput) output += `\n${this.stripAnsi(err.partialOutput)}\n`;
1480
1564
  }
1481
1565
 
1482
1566
  output += "\n---\n_Fix Missions Share Bundle_\n";
@@ -1490,28 +1574,23 @@ class VibecheckMCP {
1490
1574
  let output = "# šŸ“¦ vibecheck Truth Pack\n\n";
1491
1575
  output += `**Path:** ${projectPath}\n\n`;
1492
1576
 
1493
- try {
1494
- let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" ctx`;
1495
- if (args?.snapshot) cmd += ` --snapshot`;
1496
- if (args?.json) cmd += ` --json`;
1577
+ // Build CLI arguments array (secure)
1578
+ const cliArgs = [];
1579
+ if (args?.snapshot) cliArgs.push("--snapshot");
1580
+ if (args?.json) cliArgs.push("--json");
1497
1581
 
1498
- const result = execSync(cmd, {
1499
- cwd: projectPath,
1500
- encoding: "utf8",
1501
- maxBuffer: 10 * 1024 * 1024,
1502
- env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
1503
- });
1582
+ try {
1583
+ const result = await this.runCLI("ctx", cliArgs, projectPath);
1504
1584
 
1505
1585
  if (args?.json) {
1506
1586
  // Return raw JSON
1507
- output = result;
1508
- return this.success(output);
1587
+ return this.success(result.stdout);
1509
1588
  }
1510
1589
 
1511
- output += result.replace(/\x1b\[[0-9;]*m/g, ""); // Strip ANSI codes
1590
+ output += this.stripAnsi(result.stdout);
1512
1591
 
1513
1592
  // Read truthpack summary
1514
- const truthpackPath = path.join(projectPath, ".vibecheck", "truth", "truthpack.json");
1593
+ const truthpackPath = path.join(projectPath, CONFIG.OUTPUT_DIR, "truth", "truthpack.json");
1515
1594
  try {
1516
1595
  const truthpack = JSON.parse(await fs.readFile(truthpackPath, "utf-8"));
1517
1596
 
@@ -1526,7 +1605,7 @@ class VibecheckMCP {
1526
1605
  output += `| Stripe Detected | ${truthpack.billing?.hasStripe ? "āœ…" : "āŒ"} |\n`;
1527
1606
  output += `| Webhooks | ${truthpack.billing?.summary?.webhookHandlersFound || 0} |\n`;
1528
1607
 
1529
- output += `\nšŸ“ **Saved:** .vibecheck/truth/truthpack.json\n`;
1608
+ output += `\nšŸ“ **Saved:** ${CONFIG.OUTPUT_DIR}/truth/truthpack.json\n`;
1530
1609
  } catch {}
1531
1610
  } catch (err) {
1532
1611
  output += `\nāš ļø Truth pack error: ${err.message}\n`;
@@ -1556,27 +1635,24 @@ class VibecheckMCP {
1556
1635
  output += `**URL:** ${args?.url || "(static only)"}\n`;
1557
1636
  output += `**Max Fix Rounds:** ${args?.maxFixRounds || 3}\n\n`;
1558
1637
 
1638
+ // Build CLI arguments array (secure)
1639
+ const cliArgs = [];
1640
+ if (args?.url) cliArgs.push("--url", args.url);
1641
+ if (args?.auth) cliArgs.push("--auth", args.auth);
1642
+ if (args?.storageState) cliArgs.push("--storage-state", args.storageState);
1643
+ if (args?.skipReality) cliArgs.push("--skip-reality");
1644
+ if (args?.skipFix) cliArgs.push("--skip-fix");
1645
+ if (args?.maxFixRounds) cliArgs.push("--max-fix-rounds", String(args.maxFixRounds));
1646
+
1559
1647
  try {
1560
- let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" prove`;
1561
- if (args?.url) cmd += ` --url ${args.url}`;
1562
- if (args?.auth) cmd += ` --auth ${args.auth}`;
1563
- if (args?.storageState) cmd += ` --storage-state ${args.storageState}`;
1564
- if (args?.skipReality) cmd += ` --skip-reality`;
1565
- if (args?.skipFix) cmd += ` --skip-fix`;
1566
- if (args?.maxFixRounds) cmd += ` --max-fix-rounds ${args.maxFixRounds}`;
1567
-
1568
- const result = execSync(cmd, {
1569
- cwd: projectPath,
1570
- encoding: "utf8",
1571
- maxBuffer: 10 * 1024 * 1024,
1572
- timeout: 600000, // 10 min timeout for full prove loop
1573
- env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
1648
+ const result = await this.runCLI("prove", cliArgs, projectPath, {
1649
+ timeout: CONFIG.TIMEOUTS.PROVE
1574
1650
  });
1575
1651
 
1576
- output += result.replace(/\x1b\[[0-9;]*m/g, "");
1652
+ output += this.stripAnsi(result.stdout);
1577
1653
 
1578
1654
  // Read prove report
1579
- const provePath = path.join(projectPath, ".vibecheck", "prove", "last_prove.json");
1655
+ const provePath = path.join(projectPath, CONFIG.OUTPUT_DIR, "prove", "last_prove.json");
1580
1656
  try {
1581
1657
  const report = JSON.parse(await fs.readFile(provePath, "utf-8"));
1582
1658
 
@@ -1591,7 +1667,7 @@ class VibecheckMCP {
1591
1667
  } catch {}
1592
1668
  } catch (err) {
1593
1669
  output += `\nāš ļø Prove error: ${err.message}\n`;
1594
- if (err.stdout) output += `\n${err.stdout.replace(/\x1b\[[0-9;]*m/g, "")}\n`;
1670
+ if (err.partialOutput) output += `\n${this.stripAnsi(err.partialOutput)}\n`;
1595
1671
  }
1596
1672
 
1597
1673
  output += "\n---\n_One command to make it real_\n";
@@ -1610,19 +1686,18 @@ class VibecheckMCP {
1610
1686
 
1611
1687
  let output = `# šŸŽ¬ vibecheck Proof: ${mode.toUpperCase()}\n\n`;
1612
1688
 
1613
- try {
1614
- let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" proof ${mode}`;
1615
- if (mode === "reality" && args?.url) cmd += ` --url=${args.url}`;
1616
- if (mode === "reality" && args?.flow) cmd += ` --flow=${args.flow}`;
1689
+ // Build CLI arguments array (secure)
1690
+ const cliArgs = [mode];
1691
+ if (mode === "reality" && args?.url) cliArgs.push(`--url=${args.url}`);
1692
+ if (mode === "reality" && args?.flow) cliArgs.push(`--flow=${args.flow}`);
1617
1693
 
1618
- const result = execSync(cmd, {
1619
- cwd: projectPath,
1620
- encoding: "utf8",
1621
- maxBuffer: 10 * 1024 * 1024,
1622
- timeout: 120000, // 2 min timeout for reality mode
1694
+ try {
1695
+ const result = await this.runCLI("proof", cliArgs, projectPath, {
1696
+ timeout: CONFIG.TIMEOUTS.SCAN,
1697
+ skipAuth: false
1623
1698
  });
1624
1699
 
1625
- output += result;
1700
+ output += result.stdout;
1626
1701
  } catch (err) {
1627
1702
  if (mode === "mocks") {
1628
1703
  output += "## 🚫 MOCKPROOF: FAIL\n\n";
@@ -1631,7 +1706,7 @@ class VibecheckMCP {
1631
1706
  output += "## 🚫 REALITY MODE: FAIL\n\n";
1632
1707
  output += "Fake data or mock services detected at runtime.\n";
1633
1708
  }
1634
- output += `\n${err.stdout || err.message}\n`;
1709
+ output += `\n${err.partialOutput || err.message}\n`;
1635
1710
  }
1636
1711
 
1637
1712
  return this.success(output);
@@ -1751,21 +1826,17 @@ class VibecheckMCP {
1751
1826
  let output = "# šŸš€ vibecheck Ship\n\n";
1752
1827
  output += `**Path:** ${projectPath}\n\n`;
1753
1828
 
1754
- try {
1755
- let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" ship`;
1756
- if (args?.fix) cmd += ` --fix`;
1829
+ // Build CLI arguments array (secure)
1830
+ const cliArgs = [];
1831
+ if (args?.fix) cliArgs.push("--fix");
1757
1832
 
1758
- const result = execSync(cmd, {
1759
- cwd: projectPath,
1760
- encoding: "utf8",
1761
- maxBuffer: 10 * 1024 * 1024,
1762
- env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
1763
- });
1833
+ try {
1834
+ const result = await this.runCLI("ship", cliArgs, projectPath);
1764
1835
 
1765
- // Parse the output for key information
1766
- output += result.replace(/\x1b\[[0-9;]*m/g, ""); // Strip ANSI codes
1836
+ output += this.stripAnsi(result.stdout);
1767
1837
  } catch (err) {
1768
1838
  output += `\nāš ļø Ship check failed: ${err.message}\n`;
1839
+ if (err.partialOutput) output += `\n${this.stripAnsi(err.partialOutput)}\n`;
1769
1840
  }
1770
1841
 
1771
1842
  output += "\n---\n_Context Enhanced by vibecheck AI_\n";
@@ -1786,25 +1857,22 @@ class VibecheckMCP {
1786
1857
  if (args?.record) output += `**Recording:** Enabled\n`;
1787
1858
  output += "\n";
1788
1859
 
1860
+ // Build CLI arguments array (secure)
1861
+ const cliArgs = ["--url", url];
1862
+ if (args?.auth) cliArgs.push("--auth", args.auth);
1863
+ if (args?.flows?.length) cliArgs.push("--flows", args.flows.join(","));
1864
+ if (args?.headed) cliArgs.push("--headed");
1865
+ if (args?.record) cliArgs.push("--record");
1866
+
1789
1867
  try {
1790
- let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" verify --url "${url}"`;
1791
- if (args?.auth) cmd += ` --auth "${args.auth}"`;
1792
- if (args?.flows?.length) cmd += ` --flows ${args.flows.join(",")}`;
1793
- if (args?.headed) cmd += ` --headed`;
1794
- if (args?.record) cmd += ` --record`;
1795
-
1796
- const result = execSync(cmd, {
1797
- cwd: projectPath,
1798
- encoding: "utf8",
1799
- maxBuffer: 10 * 1024 * 1024,
1800
- timeout: 180000, // 3 min timeout
1801
- env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
1868
+ const result = await this.runCLI("verify", cliArgs, projectPath, {
1869
+ timeout: CONFIG.TIMEOUTS.VERIFY
1802
1870
  });
1803
1871
 
1804
- output += result.replace(/\x1b\[[0-9;]*m/g, "");
1872
+ output += this.stripAnsi(result.stdout);
1805
1873
 
1806
1874
  // Try to read reality results
1807
- const realityPath = path.join(projectPath, ".vibecheck", "reality", "last_reality.json");
1875
+ const realityPath = path.join(projectPath, CONFIG.OUTPUT_DIR, "reality", "last_reality.json");
1808
1876
  try {
1809
1877
  const reality = JSON.parse(await fs.readFile(realityPath, "utf-8"));
1810
1878
 
@@ -1827,11 +1895,11 @@ class VibecheckMCP {
1827
1895
  }
1828
1896
  }
1829
1897
 
1830
- output += `\nšŸ“ **Full Report:** .vibecheck/reality/last_reality.json\n`;
1898
+ output += `\nšŸ“ **Full Report:** ${CONFIG.OUTPUT_DIR}/reality/last_reality.json\n`;
1831
1899
  } catch {}
1832
1900
  } catch (err) {
1833
1901
  output += `\nāš ļø Verify failed: ${err.message}\n`;
1834
- if (err.stdout) output += `\n${err.stdout.replace(/\x1b\[[0-9;]*m/g, "")}\n`;
1902
+ if (err.partialOutput) output += `\n${this.stripAnsi(err.partialOutput)}\n`;
1835
1903
  }
1836
1904
 
1837
1905
  output += "\n---\n_Runtime Verification by vibecheck_\n";
@@ -1854,30 +1922,27 @@ class VibecheckMCP {
1854
1922
  if (args?.danger) output += `**Danger Mode:** Enabled (risky clicks allowed)\n`;
1855
1923
  output += "\n";
1856
1924
 
1925
+ // Build CLI arguments array (secure)
1926
+ const cliArgs = ["--url", url];
1927
+ if (args?.auth) cliArgs.push("--auth", args.auth);
1928
+ if (args?.verifyAuth) cliArgs.push("--verify-auth");
1929
+ if (args?.storageState) cliArgs.push("--storage-state", args.storageState);
1930
+ if (args?.saveStorageState) cliArgs.push("--save-storage-state", args.saveStorageState);
1931
+ if (args?.truthpack) cliArgs.push("--truthpack", args.truthpack);
1932
+ if (args?.headed) cliArgs.push("--headed");
1933
+ if (args?.maxPages) cliArgs.push("--max-pages", String(args.maxPages));
1934
+ if (args?.maxDepth) cliArgs.push("--max-depth", String(args.maxDepth));
1935
+ if (args?.danger) cliArgs.push("--danger");
1936
+
1857
1937
  try {
1858
- let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" reality --url "${url}"`;
1859
- if (args?.auth) cmd += ` --auth "${args.auth}"`;
1860
- if (args?.verifyAuth) cmd += ` --verify-auth`;
1861
- if (args?.storageState) cmd += ` --storage-state "${args.storageState}"`;
1862
- if (args?.saveStorageState) cmd += ` --save-storage-state "${args.saveStorageState}"`;
1863
- if (args?.truthpack) cmd += ` --truthpack "${args.truthpack}"`;
1864
- if (args?.headed) cmd += ` --headed`;
1865
- if (args?.maxPages) cmd += ` --max-pages ${args.maxPages}`;
1866
- if (args?.maxDepth) cmd += ` --max-depth ${args.maxDepth}`;
1867
- if (args?.danger) cmd += ` --danger`;
1868
-
1869
- const result = execSync(cmd, {
1870
- cwd: projectPath,
1871
- encoding: "utf8",
1872
- maxBuffer: 10 * 1024 * 1024,
1873
- timeout: 300000, // 5 min timeout for two-pass
1874
- env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
1938
+ const result = await this.runCLI("reality", cliArgs, projectPath, {
1939
+ timeout: CONFIG.TIMEOUTS.REALITY
1875
1940
  });
1876
1941
 
1877
- output += result.replace(/\x1b\[[0-9;]*m/g, "");
1942
+ output += this.stripAnsi(result.stdout);
1878
1943
 
1879
1944
  // Read reality results
1880
- const realityPath = path.join(projectPath, ".vibecheck", "reality", "last_reality.json");
1945
+ const realityPath = path.join(projectPath, CONFIG.OUTPUT_DIR, "reality", "last_reality.json");
1881
1946
  try {
1882
1947
  const reality = JSON.parse(await fs.readFile(realityPath, "utf-8"));
1883
1948
 
@@ -1928,7 +1993,7 @@ class VibecheckMCP {
1928
1993
  const verdict = blocks.length > 0 ? "šŸ›‘ BLOCK" : warns.length > 0 ? "āš ļø WARN" : "āœ… CLEAN";
1929
1994
  output += `\n## Verdict: ${verdict}\n`;
1930
1995
 
1931
- output += `\nšŸ“ **Full Report:** .vibecheck/reality/last_reality.json\n`;
1996
+ output += `\nšŸ“ **Full Report:** ${CONFIG.OUTPUT_DIR}/reality/last_reality.json\n`;
1932
1997
 
1933
1998
  if (reality.meta?.savedStorageState) {
1934
1999
  output += `šŸ“ **Saved Auth State:** ${reality.meta.savedStorageState}\n`;
@@ -1936,7 +2001,7 @@ class VibecheckMCP {
1936
2001
  } catch {}
1937
2002
  } catch (err) {
1938
2003
  output += `\nāš ļø Reality failed: ${err.message}\n`;
1939
- if (err.stdout) output += `\n${err.stdout.replace(/\x1b\[[0-9;]*m/g, "")}\n`;
2004
+ if (err.partialOutput) output += `\n${this.stripAnsi(err.partialOutput)}\n`;
1940
2005
  }
1941
2006
 
1942
2007
  output += "\n---\n_Reality Mode v2 — Two-Pass Auth Verification_\n";
@@ -1954,28 +2019,20 @@ class VibecheckMCP {
1954
2019
  output += `**URL:** ${url}\n`;
1955
2020
  output += `**Goal:** ${args?.goal || "Test all features"}\n\n`;
1956
2021
 
1957
- try {
1958
- let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" ai-test --url "${url}"`;
1959
- if (args?.goal) cmd += ` --goal "${args.goal}"`;
1960
- if (args?.headed) cmd += ` --headed`;
2022
+ // Build CLI arguments array (secure)
2023
+ const cliArgs = ["--url", url];
2024
+ if (args?.goal) cliArgs.push("--goal", args.goal);
2025
+ if (args?.headed) cliArgs.push("--headed");
1961
2026
 
1962
- const result = execSync(cmd, {
1963
- cwd: projectPath,
1964
- encoding: "utf8",
1965
- maxBuffer: 10 * 1024 * 1024,
1966
- timeout: 180000,
1967
- env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
2027
+ try {
2028
+ const result = await this.runCLI("ai-test", cliArgs, projectPath, {
2029
+ timeout: CONFIG.TIMEOUTS.VERIFY
1968
2030
  });
1969
2031
 
1970
- output += result.replace(/\x1b\[[0-9;]*m/g, "");
2032
+ output += this.stripAnsi(result.stdout);
1971
2033
 
1972
2034
  // Try to read fix prompts
1973
- const promptPath = path.join(
1974
- projectPath,
1975
- ".vibecheck",
1976
- "ai-agent",
1977
- "fix-prompt.md",
1978
- );
2035
+ const promptPath = path.join(projectPath, CONFIG.OUTPUT_DIR, "ai-agent", "fix-prompt.md");
1979
2036
  try {
1980
2037
  const prompts = await fs.readFile(promptPath, "utf-8");
1981
2038
  output += "\n## Fix Prompts Generated\n\n";
@@ -1999,19 +2056,15 @@ class VibecheckMCP {
1999
2056
  let output = "# šŸ¤– vibecheck Autopilot\n\n";
2000
2057
  output += `**Action:** ${action}\n\n`;
2001
2058
 
2002
- try {
2003
- let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" autopilot ${action}`;
2004
- if (args?.slack) cmd += ` --slack="${args.slack}"`;
2005
- if (args?.email) cmd += ` --email="${args.email}"`;
2059
+ // Build CLI arguments array (secure - no injection possible)
2060
+ const cliArgs = [action];
2061
+ if (args?.slack) cliArgs.push("--slack", args.slack);
2062
+ if (args?.email) cliArgs.push("--email", args.email);
2006
2063
 
2007
- const result = execSync(cmd, {
2008
- cwd: projectPath,
2009
- encoding: "utf8",
2010
- maxBuffer: 10 * 1024 * 1024,
2011
- env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
2012
- });
2064
+ try {
2065
+ const result = await this.runCLI("autopilot", cliArgs, projectPath);
2013
2066
 
2014
- output += result.replace(/\x1b\[[0-9;]*m/g, "");
2067
+ output += this.stripAnsi(result.stdout);
2015
2068
  } catch (err) {
2016
2069
  output += `\nāš ļø Autopilot failed: ${err.message}\n`;
2017
2070
  }
@@ -2048,20 +2101,17 @@ class VibecheckMCP {
2048
2101
  const core = await import(corePath);
2049
2102
  runAutopilot = core.runAutopilot;
2050
2103
  } catch {
2051
- // Fallback to CLI
2052
- let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" autopilot plan`;
2053
- cmd += ` --profile ${args?.profile || "ship"}`;
2054
- cmd += ` --max-fixes ${args?.maxFixes || 10}`;
2055
- cmd += ` --json`;
2056
-
2057
- const result = execSync(cmd, {
2058
- cwd: projectPath,
2059
- encoding: "utf8",
2060
- maxBuffer: 10 * 1024 * 1024,
2061
- env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
2062
- });
2104
+ // Fallback to CLI - build secure args array
2105
+ const cliArgs = [
2106
+ "plan",
2107
+ "--profile", args?.profile || "ship",
2108
+ "--max-fixes", String(args?.maxFixes || 10),
2109
+ "--json"
2110
+ ];
2063
2111
 
2064
- const jsonResult = JSON.parse(result);
2112
+ const result = await this.runCLI("autopilot", cliArgs, projectPath);
2113
+
2114
+ const jsonResult = JSON.parse(result.stdout);
2065
2115
  output += `## Scan Results\n\n`;
2066
2116
  output += `- **Total findings:** ${jsonResult.totalFindings}\n`;
2067
2117
  output += `- **Fixable:** ${jsonResult.fixableFindings}\n`;
@@ -2130,24 +2180,22 @@ class VibecheckMCP {
2130
2180
  output += `**Profile:** ${args?.profile || "ship"}\n`;
2131
2181
  output += `**Dry Run:** ${args?.dryRun ? "Yes" : "No"}\n\n`;
2132
2182
 
2183
+ // Build CLI arguments array (secure)
2184
+ const cliArgs = [
2185
+ "apply",
2186
+ "--profile", args?.profile || "ship",
2187
+ "--max-fixes", String(args?.maxFixes || 10),
2188
+ "--json"
2189
+ ];
2190
+ if (args?.verify === false) cliArgs.push("--no-verify");
2191
+ if (args?.dryRun) cliArgs.push("--dry-run");
2192
+
2133
2193
  try {
2134
- // Fallback to CLI
2135
- let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" autopilot apply`;
2136
- cmd += ` --profile ${args?.profile || "ship"}`;
2137
- cmd += ` --max-fixes ${args?.maxFixes || 10}`;
2138
- if (args?.verify === false) cmd += ` --no-verify`;
2139
- if (args?.dryRun) cmd += ` --dry-run`;
2140
- cmd += ` --json`;
2141
-
2142
- const result = execSync(cmd, {
2143
- cwd: projectPath,
2144
- encoding: "utf8",
2145
- maxBuffer: 10 * 1024 * 1024,
2146
- timeout: 300000, // 5 min timeout
2147
- env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
2194
+ const result = await this.runCLI("autopilot", cliArgs, projectPath, {
2195
+ timeout: CONFIG.TIMEOUTS.AUTOPILOT,
2148
2196
  });
2149
2197
 
2150
- const jsonResult = JSON.parse(result);
2198
+ const jsonResult = JSON.parse(result.stdout);
2151
2199
 
2152
2200
  output += `## Results\n\n`;
2153
2201
  output += `- **Packs attempted:** ${jsonResult.packsAttempted}\n`;
@@ -2192,26 +2240,17 @@ class VibecheckMCP {
2192
2240
 
2193
2241
  let output = "# šŸ… vibecheck Badge\n\n";
2194
2242
 
2195
- try {
2196
- let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" badge --format ${format}`;
2197
- if (args?.style) cmd += ` --style ${args.style}`;
2243
+ // Build CLI arguments array (secure)
2244
+ const cliArgs = ["--format", format];
2245
+ if (args?.style) cliArgs.push("--style", args.style);
2198
2246
 
2199
- const result = execSync(cmd, {
2200
- cwd: projectPath,
2201
- encoding: "utf8",
2202
- maxBuffer: 10 * 1024 * 1024,
2203
- env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
2204
- });
2247
+ try {
2248
+ const result = await this.runCLI("badge", cliArgs, projectPath);
2205
2249
 
2206
- output += result.replace(/\x1b\[[0-9;]*m/g, "");
2250
+ output += this.stripAnsi(result.stdout);
2207
2251
 
2208
2252
  // Read the badge file
2209
- const badgePath = path.join(
2210
- projectPath,
2211
- ".vibecheck",
2212
- "badges",
2213
- `badge.${format}`,
2214
- );
2253
+ const badgePath = path.join(projectPath, CONFIG.OUTPUT_DIR, "badges", `badge.${format}`);
2215
2254
  try {
2216
2255
  const badge = await fs.readFile(badgePath, "utf-8");
2217
2256
  if (format === "md") {
@@ -2239,19 +2278,15 @@ class VibecheckMCP {
2239
2278
  output += `**Project:** ${path.basename(projectPath)}\n`;
2240
2279
  output += `**Platform:** ${platform}\n\n`;
2241
2280
 
2242
- try {
2243
- let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" context`;
2244
- if (platform !== "all") cmd += ` --platform=${platform}`;
2281
+ // Build CLI arguments array (secure)
2282
+ const cliArgs = [];
2283
+ if (platform !== "all") cliArgs.push(`--platform=${platform}`);
2245
2284
 
2246
- execSync(cmd, {
2247
- cwd: projectPath,
2248
- encoding: "utf8",
2249
- maxBuffer: 10 * 1024 * 1024,
2250
- });
2285
+ try {
2286
+ await this.runCLI("context", cliArgs, projectPath, { skipAuth: false });
2251
2287
 
2252
2288
  output += "## āœ… Context Generated\n\n";
2253
- output +=
2254
- "Your AI coding assistants now have full project awareness.\n\n";
2289
+ output += "Your AI coding assistants now have full project awareness.\n\n";
2255
2290
 
2256
2291
  output += "### Generated Files\n\n";
2257
2292
 
@@ -2272,8 +2307,8 @@ class VibecheckMCP {
2272
2307
  }
2273
2308
 
2274
2309
  output += "**Universal (MCP):**\n";
2275
- output += "- `.vibecheck/context.json` - Full context\n";
2276
- output += "- `.vibecheck/project-map.json` - Project analysis\n\n";
2310
+ output += `- \`${CONFIG.OUTPUT_DIR}/context.json\` - Full context\n`;
2311
+ output += `- \`${CONFIG.OUTPUT_DIR}/project-map.json\` - Project analysis\n\n`;
2277
2312
 
2278
2313
  output += "### What Your AI Now Knows\n\n";
2279
2314
  output += "- Project architecture and structure\n";
@@ -2282,8 +2317,7 @@ class VibecheckMCP {
2282
2317
  output += "- Coding conventions and patterns\n";
2283
2318
  output += "- Dependencies and tech stack\n\n";
2284
2319
 
2285
- output +=
2286
- "> **Tip:** Regenerate after major codebase changes with `vibecheck context`\n";
2320
+ output += "> **Tip:** Regenerate after major codebase changes with `vibecheck context`\n";
2287
2321
  } catch (err) {
2288
2322
  output += `\nāš ļø Context generation failed: ${err.message}\n`;
2289
2323
  }