@vibecheckai/cli 3.5.0 → 3.5.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 (326) hide show
  1. package/bin/registry.js +174 -449
  2. package/bin/runners/cli-utils.js +33 -2
  3. package/bin/runners/context/generators/cursor.js +2 -49
  4. package/bin/runners/context/generators/mcp.js +13 -15
  5. package/bin/runners/context/proof-context.js +1 -248
  6. package/bin/runners/lib/analysis-core.js +180 -198
  7. package/bin/runners/lib/analyzers.js +241 -2212
  8. package/bin/runners/lib/cli-output.js +210 -242
  9. package/bin/runners/lib/detectors-v2.js +785 -547
  10. package/bin/runners/lib/entitlements-v2.js +431 -161
  11. package/bin/runners/lib/error-handler.js +9 -16
  12. package/bin/runners/lib/global-flags.js +0 -37
  13. package/bin/runners/lib/html-proof-report.js +700 -350
  14. package/bin/runners/lib/missions/plan.js +6 -46
  15. package/bin/runners/lib/missions/templates.js +0 -232
  16. package/bin/runners/lib/route-truth.js +322 -1167
  17. package/bin/runners/lib/scan-output.js +467 -493
  18. package/bin/runners/lib/ship-output.js +27 -280
  19. package/bin/runners/lib/terminal-ui.js +700 -310
  20. package/bin/runners/lib/truth.js +321 -1004
  21. package/bin/runners/lib/unified-output.js +158 -162
  22. package/bin/runners/lib/upsell.js +204 -104
  23. package/bin/runners/runAIAgent.js +10 -5
  24. package/bin/runners/runAllowlist.js +324 -0
  25. package/bin/runners/runAuth.js +94 -344
  26. package/bin/runners/runCheckpoint.js +45 -43
  27. package/bin/runners/runContext.js +24 -139
  28. package/bin/runners/runDoctor.js +101 -136
  29. package/bin/runners/runEvidencePack.js +219 -0
  30. package/bin/runners/runFix.js +71 -82
  31. package/bin/runners/runGuard.js +119 -606
  32. package/bin/runners/runInit.js +60 -22
  33. package/bin/runners/runInstall.js +281 -0
  34. package/bin/runners/runLabs.js +341 -0
  35. package/bin/runners/runMcp.js +62 -139
  36. package/bin/runners/runPolish.js +83 -282
  37. package/bin/runners/runPromptFirewall.js +12 -5
  38. package/bin/runners/runProve.js +58 -33
  39. package/bin/runners/runReality.js +58 -81
  40. package/bin/runners/runReport.js +7 -34
  41. package/bin/runners/runRuntime.js +8 -5
  42. package/bin/runners/runScan.js +844 -219
  43. package/bin/runners/runShip.js +59 -721
  44. package/bin/runners/runValidate.js +11 -24
  45. package/bin/runners/runWatch.js +76 -131
  46. package/bin/vibecheck.js +69 -295
  47. package/mcp-server/ARCHITECTURE.md +339 -0
  48. package/mcp-server/__tests__/cache.test.ts +313 -0
  49. package/mcp-server/__tests__/executor.test.ts +239 -0
  50. package/mcp-server/__tests__/fixtures/exclusion-test/.cache/webpack/cache.pack +1 -0
  51. package/mcp-server/__tests__/fixtures/exclusion-test/.next/server/chunk.js +3 -0
  52. package/mcp-server/__tests__/fixtures/exclusion-test/.turbo/cache.json +3 -0
  53. package/mcp-server/__tests__/fixtures/exclusion-test/.venv/lib/env.py +3 -0
  54. package/mcp-server/__tests__/fixtures/exclusion-test/dist/bundle.js +3 -0
  55. package/mcp-server/__tests__/fixtures/exclusion-test/package.json +5 -0
  56. package/mcp-server/__tests__/fixtures/exclusion-test/src/app.ts +5 -0
  57. package/mcp-server/__tests__/fixtures/exclusion-test/venv/lib/config.py +4 -0
  58. package/mcp-server/__tests__/ids.test.ts +345 -0
  59. package/mcp-server/__tests__/integration/tools.test.ts +410 -0
  60. package/mcp-server/__tests__/registry.test.ts +365 -0
  61. package/mcp-server/__tests__/sandbox.test.ts +323 -0
  62. package/mcp-server/__tests__/schemas.test.ts +372 -0
  63. package/mcp-server/benchmarks/run-benchmarks.ts +304 -0
  64. package/mcp-server/examples/doctor.request.json +14 -0
  65. package/mcp-server/examples/doctor.response.json +53 -0
  66. package/mcp-server/examples/error.response.json +15 -0
  67. package/mcp-server/examples/scan.request.json +14 -0
  68. package/mcp-server/examples/scan.response.json +108 -0
  69. package/mcp-server/handlers/tool-handler.ts +671 -0
  70. package/mcp-server/index-v1.js +698 -0
  71. package/mcp-server/index-v3.ts +293 -0
  72. package/mcp-server/index.js +1080 -1757
  73. package/mcp-server/index.old.js +4137 -0
  74. package/mcp-server/lib/cache.ts +341 -0
  75. package/mcp-server/lib/errors.ts +346 -0
  76. package/mcp-server/lib/executor.ts +792 -0
  77. package/mcp-server/lib/ids.ts +238 -0
  78. package/mcp-server/lib/logger.ts +368 -0
  79. package/mcp-server/lib/metrics.ts +365 -0
  80. package/mcp-server/lib/sandbox.ts +337 -0
  81. package/mcp-server/lib/validator.ts +229 -0
  82. package/mcp-server/package-lock.json +165 -0
  83. package/mcp-server/package.json +32 -7
  84. package/mcp-server/premium-tools.js +2 -2
  85. package/mcp-server/registry/tools.json +476 -0
  86. package/mcp-server/schemas/error-envelope.schema.json +125 -0
  87. package/mcp-server/schemas/finding.schema.json +167 -0
  88. package/mcp-server/schemas/report-artifact.schema.json +88 -0
  89. package/mcp-server/schemas/run-request.schema.json +75 -0
  90. package/mcp-server/schemas/verdict.schema.json +168 -0
  91. package/mcp-server/tier-auth.d.ts +71 -0
  92. package/mcp-server/tier-auth.js +371 -183
  93. package/mcp-server/truth-context.js +90 -131
  94. package/mcp-server/truth-firewall-tools.js +1000 -1611
  95. package/mcp-server/tsconfig.json +34 -0
  96. package/mcp-server/vibecheck-tools.js +2 -2
  97. package/mcp-server/vitest.config.ts +16 -0
  98. package/package.json +3 -4
  99. package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +0 -474
  100. package/bin/runners/lib/agent-firewall/change-packet/builder.js +0 -488
  101. package/bin/runners/lib/agent-firewall/change-packet/schema.json +0 -228
  102. package/bin/runners/lib/agent-firewall/change-packet/store.js +0 -200
  103. package/bin/runners/lib/agent-firewall/claims/claim-types.js +0 -21
  104. package/bin/runners/lib/agent-firewall/claims/extractor.js +0 -303
  105. package/bin/runners/lib/agent-firewall/claims/patterns.js +0 -24
  106. package/bin/runners/lib/agent-firewall/critic/index.js +0 -151
  107. package/bin/runners/lib/agent-firewall/critic/judge.js +0 -432
  108. package/bin/runners/lib/agent-firewall/critic/prompts.js +0 -305
  109. package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +0 -88
  110. package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +0 -75
  111. package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +0 -127
  112. package/bin/runners/lib/agent-firewall/evidence/resolver.js +0 -102
  113. package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +0 -213
  114. package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +0 -145
  115. package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +0 -19
  116. package/bin/runners/lib/agent-firewall/fs-hook/installer.js +0 -87
  117. package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +0 -184
  118. package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +0 -163
  119. package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +0 -107
  120. package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +0 -68
  121. package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +0 -66
  122. package/bin/runners/lib/agent-firewall/interceptor/base.js +0 -304
  123. package/bin/runners/lib/agent-firewall/interceptor/cursor.js +0 -35
  124. package/bin/runners/lib/agent-firewall/interceptor/vscode.js +0 -35
  125. package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +0 -34
  126. package/bin/runners/lib/agent-firewall/lawbook/distributor.js +0 -465
  127. package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +0 -604
  128. package/bin/runners/lib/agent-firewall/lawbook/index.js +0 -304
  129. package/bin/runners/lib/agent-firewall/lawbook/registry.js +0 -514
  130. package/bin/runners/lib/agent-firewall/lawbook/schema.js +0 -420
  131. package/bin/runners/lib/agent-firewall/learning/learning-engine.js +0 -849
  132. package/bin/runners/lib/agent-firewall/logger.js +0 -141
  133. package/bin/runners/lib/agent-firewall/policy/default-policy.json +0 -90
  134. package/bin/runners/lib/agent-firewall/policy/engine.js +0 -103
  135. package/bin/runners/lib/agent-firewall/policy/loader.js +0 -451
  136. package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +0 -50
  137. package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +0 -50
  138. package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +0 -86
  139. package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +0 -162
  140. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +0 -189
  141. package/bin/runners/lib/agent-firewall/policy/rules/scope.js +0 -93
  142. package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +0 -57
  143. package/bin/runners/lib/agent-firewall/policy/schema.json +0 -183
  144. package/bin/runners/lib/agent-firewall/policy/verdict.js +0 -54
  145. package/bin/runners/lib/agent-firewall/proposal/extractor.js +0 -394
  146. package/bin/runners/lib/agent-firewall/proposal/index.js +0 -212
  147. package/bin/runners/lib/agent-firewall/proposal/schema.js +0 -251
  148. package/bin/runners/lib/agent-firewall/proposal/validator.js +0 -386
  149. package/bin/runners/lib/agent-firewall/reality/index.js +0 -332
  150. package/bin/runners/lib/agent-firewall/reality/state.js +0 -625
  151. package/bin/runners/lib/agent-firewall/reality/watcher.js +0 -322
  152. package/bin/runners/lib/agent-firewall/risk/index.js +0 -173
  153. package/bin/runners/lib/agent-firewall/risk/scorer.js +0 -328
  154. package/bin/runners/lib/agent-firewall/risk/thresholds.js +0 -321
  155. package/bin/runners/lib/agent-firewall/risk/vectors.js +0 -421
  156. package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +0 -472
  157. package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +0 -346
  158. package/bin/runners/lib/agent-firewall/simulator/index.js +0 -181
  159. package/bin/runners/lib/agent-firewall/simulator/route-validator.js +0 -380
  160. package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +0 -661
  161. package/bin/runners/lib/agent-firewall/time-machine/index.js +0 -267
  162. package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +0 -436
  163. package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +0 -490
  164. package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +0 -530
  165. package/bin/runners/lib/agent-firewall/truthpack/index.js +0 -67
  166. package/bin/runners/lib/agent-firewall/truthpack/loader.js +0 -137
  167. package/bin/runners/lib/agent-firewall/unblock/planner.js +0 -337
  168. package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +0 -118
  169. package/bin/runners/lib/api-client.js +0 -269
  170. package/bin/runners/lib/audit-logger.js +0 -532
  171. package/bin/runners/lib/authority/authorities/architecture.js +0 -364
  172. package/bin/runners/lib/authority/authorities/compliance.js +0 -341
  173. package/bin/runners/lib/authority/authorities/human.js +0 -343
  174. package/bin/runners/lib/authority/authorities/quality.js +0 -420
  175. package/bin/runners/lib/authority/authorities/security.js +0 -228
  176. package/bin/runners/lib/authority/index.js +0 -293
  177. package/bin/runners/lib/authority-badge.js +0 -425
  178. package/bin/runners/lib/bundle/bundle-intelligence.js +0 -846
  179. package/bin/runners/lib/cli-charts.js +0 -368
  180. package/bin/runners/lib/cli-config-display.js +0 -405
  181. package/bin/runners/lib/cli-demo.js +0 -275
  182. package/bin/runners/lib/cli-errors.js +0 -438
  183. package/bin/runners/lib/cli-help-formatter.js +0 -439
  184. package/bin/runners/lib/cli-interactive-menu.js +0 -509
  185. package/bin/runners/lib/cli-prompts.js +0 -441
  186. package/bin/runners/lib/cli-scan-cards.js +0 -362
  187. package/bin/runners/lib/compliance-reporter.js +0 -710
  188. package/bin/runners/lib/conductor/index.js +0 -671
  189. package/bin/runners/lib/easy/README.md +0 -123
  190. package/bin/runners/lib/easy/index.js +0 -140
  191. package/bin/runners/lib/easy/interactive-wizard.js +0 -788
  192. package/bin/runners/lib/easy/one-click-firewall.js +0 -564
  193. package/bin/runners/lib/easy/zero-config-reality.js +0 -714
  194. package/bin/runners/lib/engines/accessibility-engine.js +0 -390
  195. package/bin/runners/lib/engines/api-consistency-engine.js +0 -467
  196. package/bin/runners/lib/engines/ast-cache.js +0 -99
  197. package/bin/runners/lib/engines/async-patterns-engine.js +0 -444
  198. package/bin/runners/lib/engines/bundle-size-engine.js +0 -433
  199. package/bin/runners/lib/engines/code-quality-engine.js +0 -255
  200. package/bin/runners/lib/engines/confidence-scoring.js +0 -276
  201. package/bin/runners/lib/engines/console-logs-engine.js +0 -115
  202. package/bin/runners/lib/engines/context-detection.js +0 -264
  203. package/bin/runners/lib/engines/cross-file-analysis-engine.js +0 -533
  204. package/bin/runners/lib/engines/database-patterns-engine.js +0 -429
  205. package/bin/runners/lib/engines/dead-code-engine.js +0 -198
  206. package/bin/runners/lib/engines/deprecated-api-engine.js +0 -226
  207. package/bin/runners/lib/engines/duplicate-code-engine.js +0 -354
  208. package/bin/runners/lib/engines/empty-catch-engine.js +0 -260
  209. package/bin/runners/lib/engines/env-variables-engine.js +0 -458
  210. package/bin/runners/lib/engines/error-handling-engine.js +0 -437
  211. package/bin/runners/lib/engines/false-positive-prevention.js +0 -630
  212. package/bin/runners/lib/engines/file-filter.js +0 -131
  213. package/bin/runners/lib/engines/framework-adapters/index.js +0 -607
  214. package/bin/runners/lib/engines/framework-detection.js +0 -508
  215. package/bin/runners/lib/engines/hardcoded-secrets-engine.js +0 -251
  216. package/bin/runners/lib/engines/import-order-engine.js +0 -429
  217. package/bin/runners/lib/engines/mock-data-engine.js +0 -315
  218. package/bin/runners/lib/engines/naming-conventions-engine.js +0 -544
  219. package/bin/runners/lib/engines/noise-reduction-engine.js +0 -452
  220. package/bin/runners/lib/engines/orchestrator.js +0 -334
  221. package/bin/runners/lib/engines/parallel-processor.js +0 -71
  222. package/bin/runners/lib/engines/performance-issues-engine.js +0 -405
  223. package/bin/runners/lib/engines/react-patterns-engine.js +0 -457
  224. package/bin/runners/lib/engines/security-vulnerabilities-engine.js +0 -571
  225. package/bin/runners/lib/engines/todo-fixme-engine.js +0 -115
  226. package/bin/runners/lib/engines/type-aware-engine.js +0 -376
  227. package/bin/runners/lib/engines/unsafe-regex-engine.js +0 -225
  228. package/bin/runners/lib/engines/vibecheck-engines/README.md +0 -53
  229. package/bin/runners/lib/engines/vibecheck-engines/index.js +0 -124
  230. package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +0 -806
  231. package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +0 -439
  232. package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +0 -577
  233. package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +0 -543
  234. package/bin/runners/lib/engines/vibecheck-engines/package.json +0 -13
  235. package/bin/runners/lib/engines/vibecheck-engines.js +0 -514
  236. package/bin/runners/lib/enhanced-features/index.js +0 -305
  237. package/bin/runners/lib/enhanced-output.js +0 -631
  238. package/bin/runners/lib/enterprise.js +0 -300
  239. package/bin/runners/lib/exit-codes.js +0 -275
  240. package/bin/runners/lib/fingerprint.js +0 -377
  241. package/bin/runners/lib/firewall/command-validator.js +0 -351
  242. package/bin/runners/lib/firewall/config.js +0 -341
  243. package/bin/runners/lib/firewall/content-validator.js +0 -519
  244. package/bin/runners/lib/firewall/index.js +0 -101
  245. package/bin/runners/lib/firewall/path-validator.js +0 -256
  246. package/bin/runners/lib/help-formatter.js +0 -413
  247. package/bin/runners/lib/intelligence/cross-repo-intelligence.js +0 -817
  248. package/bin/runners/lib/logger.js +0 -38
  249. package/bin/runners/lib/mcp-utils.js +0 -425
  250. package/bin/runners/lib/output/index.js +0 -1022
  251. package/bin/runners/lib/policy-engine.js +0 -652
  252. package/bin/runners/lib/polish/autofix/accessibility-fixes.js +0 -333
  253. package/bin/runners/lib/polish/autofix/async-handlers.js +0 -273
  254. package/bin/runners/lib/polish/autofix/dead-code.js +0 -280
  255. package/bin/runners/lib/polish/autofix/imports-optimizer.js +0 -344
  256. package/bin/runners/lib/polish/autofix/index.js +0 -200
  257. package/bin/runners/lib/polish/autofix/remove-consoles.js +0 -209
  258. package/bin/runners/lib/polish/autofix/strengthen-types.js +0 -245
  259. package/bin/runners/lib/polish/backend-checks.js +0 -148
  260. package/bin/runners/lib/polish/documentation-checks.js +0 -111
  261. package/bin/runners/lib/polish/frontend-checks.js +0 -168
  262. package/bin/runners/lib/polish/index.js +0 -71
  263. package/bin/runners/lib/polish/infrastructure-checks.js +0 -131
  264. package/bin/runners/lib/polish/library-detection.js +0 -175
  265. package/bin/runners/lib/polish/performance-checks.js +0 -100
  266. package/bin/runners/lib/polish/security-checks.js +0 -148
  267. package/bin/runners/lib/polish/utils.js +0 -203
  268. package/bin/runners/lib/prompt-builder.js +0 -540
  269. package/bin/runners/lib/proof-certificate.js +0 -634
  270. package/bin/runners/lib/reality/accessibility-audit.js +0 -946
  271. package/bin/runners/lib/reality/api-contract-validator.js +0 -1012
  272. package/bin/runners/lib/reality/chaos-engineering.js +0 -1084
  273. package/bin/runners/lib/reality/performance-tracker.js +0 -1077
  274. package/bin/runners/lib/reality/scenario-generator.js +0 -1404
  275. package/bin/runners/lib/reality/visual-regression.js +0 -852
  276. package/bin/runners/lib/reality-profiler.js +0 -717
  277. package/bin/runners/lib/replay/flight-recorder-viewer.js +0 -1160
  278. package/bin/runners/lib/review/ai-code-review.js +0 -832
  279. package/bin/runners/lib/rules/custom-rule-engine.js +0 -985
  280. package/bin/runners/lib/sbom-generator.js +0 -641
  281. package/bin/runners/lib/scan-output-enhanced.js +0 -512
  282. package/bin/runners/lib/security/owasp-scanner.js +0 -939
  283. package/bin/runners/lib/ship-output-enterprise.js +0 -239
  284. package/bin/runners/lib/unified-cli-output.js +0 -777
  285. package/bin/runners/lib/validators/contract-validator.js +0 -283
  286. package/bin/runners/lib/validators/dead-export-detector.js +0 -279
  287. package/bin/runners/lib/validators/dep-audit.js +0 -245
  288. package/bin/runners/lib/validators/env-validator.js +0 -319
  289. package/bin/runners/lib/validators/index.js +0 -120
  290. package/bin/runners/lib/validators/license-checker.js +0 -252
  291. package/bin/runners/lib/validators/route-validator.js +0 -290
  292. package/bin/runners/runAgent.d.ts +0 -5
  293. package/bin/runners/runAgent.js +0 -164
  294. package/bin/runners/runApprove.js +0 -1233
  295. package/bin/runners/runAuthority.js +0 -528
  296. package/bin/runners/runClassify.js +0 -862
  297. package/bin/runners/runConductor.js +0 -772
  298. package/bin/runners/runContainer.js +0 -366
  299. package/bin/runners/runContext.d.ts +0 -4
  300. package/bin/runners/runEasy.js +0 -410
  301. package/bin/runners/runFirewall.d.ts +0 -5
  302. package/bin/runners/runFirewall.js +0 -137
  303. package/bin/runners/runFirewallHook.d.ts +0 -5
  304. package/bin/runners/runFirewallHook.js +0 -59
  305. package/bin/runners/runIaC.js +0 -372
  306. package/bin/runners/runPolish.d.ts +0 -4
  307. package/bin/runners/runProof.zip +0 -0
  308. package/bin/runners/runTruth.d.ts +0 -5
  309. package/bin/runners/runTruth.js +0 -104
  310. package/bin/runners/runVibe.js +0 -791
  311. package/mcp-server/HARDENING_SUMMARY.md +0 -299
  312. package/mcp-server/agent-firewall-interceptor.js +0 -500
  313. package/mcp-server/authority-tools.js +0 -569
  314. package/mcp-server/conductor/conflict-resolver.js +0 -588
  315. package/mcp-server/conductor/execution-planner.js +0 -544
  316. package/mcp-server/conductor/index.js +0 -377
  317. package/mcp-server/conductor/lock-manager.js +0 -615
  318. package/mcp-server/conductor/request-queue.js +0 -550
  319. package/mcp-server/conductor/session-manager.js +0 -500
  320. package/mcp-server/conductor/tools.js +0 -510
  321. package/mcp-server/lib/api-client.cjs +0 -13
  322. package/mcp-server/lib/logger.cjs +0 -30
  323. package/mcp-server/logger.js +0 -173
  324. package/mcp-server/tools-v3.js +0 -1039
  325. package/mcp-server/tools.js +0 -495
  326. package/mcp-server/vibecheck-mcp-server-3.2.0.tgz +0 -0
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * vibecheck MCP Server v2.1.0 - Hardened Production Build
4
+ * vibecheck MCP Server v2.0 - Clean Product Surface
5
5
  *
6
6
  * Curated Tools for AI Agents:
7
7
  * vibecheck.ctx - Build truthpack/context
@@ -15,16 +15,6 @@
15
15
  * vibecheck.check_invariants - Invariant checks
16
16
  *
17
17
  * Everything else is parameters on these tools.
18
- *
19
- * HARDENING FEATURES (v2.1.0):
20
- * - Input validation: URL, path, string, array, number sanitization
21
- * - Output sanitization: Redaction of secrets, truncation of large outputs
22
- * - Rate limiting: 120 calls/minute per server instance
23
- * - Path security: Traversal prevention, project root sandboxing
24
- * - Safe JSON parsing: Size limits, error handling
25
- * - Error handling: Consistent error codes, sanitized messages
26
- * - Resource safety: Bounded timeouts, memory limits
27
- * - Graceful degradation: Partial output on failures
28
18
  */
29
19
 
30
20
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -37,424 +27,14 @@ import {
37
27
  } from "@modelcontextprotocol/sdk/types.js";
38
28
 
39
29
  import fs from "fs/promises";
40
- import fsSync from "fs";
41
30
  import path from "path";
42
31
  import { fileURLToPath } from "url";
43
- import { execFile } from "child_process";
44
- import { promisify } from "util";
45
-
46
- // Import API client for dashboard integration (optional - may use CommonJS)
47
- import { createRequire } from "module";
48
- const require = createRequire(import.meta.url);
49
- const {
50
- createScan,
51
- updateScanProgress,
52
- submitScanResults,
53
- reportScanError,
54
- isApiAvailable
55
- } = require("./lib/api-client.cjs");
56
-
57
- const execFileAsync = promisify(execFile);
32
+ import { execSync } from "child_process";
58
33
 
59
34
  const __filename = fileURLToPath(import.meta.url);
60
35
  const __dirname = path.dirname(__filename);
61
36
 
62
- // ============================================================================
63
- // CENTRALIZED CONFIGURATION
64
- // ============================================================================
65
- const CONFIG = {
66
- VERSION: "2.2.0",
67
- BIN_PATH: path.join(__dirname, "..", "bin", "vibecheck.js"),
68
- OUTPUT_DIR: ".vibecheck",
69
- ENV_DEFAULTS: { VIBECHECK_SKIP_AUTH: "1" },
70
- TIMEOUTS: {
71
- DEFAULT: 30000, // 30 seconds
72
- SCAN: 120000, // 2 minutes
73
- VERIFY: 180000, // 3 minutes
74
- REALITY: 300000, // 5 minutes
75
- PROVE: 600000, // 10 minutes
76
- AUTOPILOT: 300000, // 5 minutes
77
- },
78
- MAX_BUFFER: 10 * 1024 * 1024, // 10MB
79
- // Hardening limits
80
- LIMITS: {
81
- MAX_OUTPUT_LENGTH: 500000, // 500KB max output text
82
- MAX_PATH_LENGTH: 4096, // Max path string length
83
- MAX_URL_LENGTH: 2048, // Max URL length
84
- MAX_STRING_ARG: 10000, // Max string argument length
85
- MAX_ARRAY_ITEMS: 100, // Max array items in args
86
- RATE_LIMIT_WINDOW_MS: 60000, // 1 minute window
87
- RATE_LIMIT_MAX_CALLS: 120, // Max calls per window
88
- },
89
- // Sensitive patterns to redact from output
90
- SENSITIVE_PATTERNS: [
91
- /(?:sk_live_|sk_test_)[a-zA-Z0-9]{24,}/g, // Stripe keys
92
- /(?:AKIA|ASIA)[A-Z0-9]{16}/g, // AWS keys
93
- /ghp_[a-zA-Z0-9]{36}/g, // GitHub tokens
94
- /xox[baprs]-[0-9A-Za-z\-]{10,}/g, // Slack tokens
95
- /eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*/g, // JWTs
96
- /(?:password|secret|token|apikey|api_key)["']?\s*[:=]\s*["'][^"']{8,}["']/gi, // Generic secrets
97
- ],
98
- };
99
-
100
- // ============================================================================
101
- // HARDENING: Input Validation & Sanitization Utilities
102
- // ============================================================================
103
-
104
- /**
105
- * Validate and sanitize a URL
106
- * @param {string} url - URL to validate
107
- * @returns {{ valid: boolean, url?: string, error?: string }}
108
- */
109
- function validateUrl(url) {
110
- if (!url || typeof url !== 'string') {
111
- return { valid: false, error: 'URL is required and must be a string' };
112
- }
113
-
114
- const trimmed = url.trim();
115
-
116
- if (trimmed.length > CONFIG.LIMITS.MAX_URL_LENGTH) {
117
- return { valid: false, error: `URL exceeds maximum length of ${CONFIG.LIMITS.MAX_URL_LENGTH} characters` };
118
- }
119
-
120
- try {
121
- const parsed = new URL(trimmed);
122
-
123
- // Only allow http/https protocols
124
- if (!['http:', 'https:'].includes(parsed.protocol)) {
125
- return { valid: false, error: 'URL must use http or https protocol' };
126
- }
127
-
128
- // Block localhost/internal IPs in production contexts (can be overridden)
129
- const hostname = parsed.hostname.toLowerCase();
130
- if (process.env.VIBECHECK_BLOCK_INTERNAL !== 'false') {
131
- // Allow localhost for development
132
- if (hostname === 'localhost' || hostname === '127.0.0.1') {
133
- // This is OK for local testing
134
- }
135
- }
136
-
137
- return { valid: true, url: trimmed };
138
- } catch {
139
- return { valid: false, error: 'Invalid URL format' };
140
- }
141
- }
142
-
143
- /**
144
- * Sanitize a file path to prevent path traversal.
145
- *
146
- * SECURITY FIX: Previous implementation used path.sep which differs between
147
- * Windows (\) and Unix (/). Attackers could bypass the check on Windows by
148
- * using forward slashes in paths. Now we normalize all separators before comparing.
149
- *
150
- * @param {string} inputPath - Path to sanitize
151
- * @param {string} projectRoot - Project root directory
152
- * @returns {{ valid: boolean, path?: string, error?: string }}
153
- */
154
- function sanitizePath(inputPath, projectRoot) {
155
- if (!inputPath || typeof inputPath !== 'string') {
156
- return { valid: false, error: 'Path is required and must be a string' };
157
- }
158
-
159
- if (inputPath.length > CONFIG.LIMITS.MAX_PATH_LENGTH) {
160
- return { valid: false, error: `Path exceeds maximum length of ${CONFIG.LIMITS.MAX_PATH_LENGTH} characters` };
161
- }
162
-
163
- // Reject null bytes which can truncate paths in some systems
164
- if (inputPath.includes('\0')) {
165
- return { valid: false, error: 'Path contains invalid characters (null byte)' };
166
- }
167
-
168
- try {
169
- const resolvedRoot = path.resolve(projectRoot);
170
- const resolvedPath = path.resolve(projectRoot, inputPath);
171
-
172
- // SECURITY: Normalize path separators for cross-platform comparison
173
- // On Windows, path.sep is '\' but paths can use '/' which would bypass the check
174
- const normalizedRoot = resolvedRoot.replace(/\\/g, '/').toLowerCase();
175
- const normalizedPath = resolvedPath.replace(/\\/g, '/').toLowerCase();
176
-
177
- // Ensure the resolved path is within or equal to the project root
178
- // Use normalized paths for comparison, add trailing slash to prevent
179
- // prefix attacks (e.g., /project-malicious matching /project)
180
- const rootWithSep = normalizedRoot.endsWith('/') ? normalizedRoot : normalizedRoot + '/';
181
-
182
- if (!normalizedPath.startsWith(rootWithSep) && normalizedPath !== normalizedRoot) {
183
- return { valid: false, error: 'Path traversal detected - path must be within project root' };
184
- }
185
-
186
- return { valid: true, path: resolvedPath };
187
- } catch (err) {
188
- return { valid: false, error: `Invalid path format: ${err.message}` };
189
- }
190
- }
191
-
192
- /**
193
- * Validate and sanitize string arguments
194
- * @param {*} value - Value to validate
195
- * @param {number} maxLength - Maximum allowed length
196
- * @returns {string}
197
- */
198
- function sanitizeString(value, maxLength = CONFIG.LIMITS.MAX_STRING_ARG) {
199
- if (value === null || value === undefined) {
200
- return '';
201
- }
202
-
203
- const str = String(value);
204
- return str.length > maxLength ? str.slice(0, maxLength) + '...[truncated]' : str;
205
- }
206
-
207
- /**
208
- * Validate array arguments
209
- * @param {*} arr - Array to validate
210
- * @param {number} maxItems - Maximum allowed items
211
- * @returns {any[]}
212
- */
213
- function sanitizeArray(arr, maxItems = CONFIG.LIMITS.MAX_ARRAY_ITEMS) {
214
- if (!Array.isArray(arr)) {
215
- return [];
216
- }
217
- return arr.slice(0, maxItems);
218
- }
219
-
220
- /**
221
- * Validate numeric arguments
222
- * @param {*} value - Value to validate
223
- * @param {number} min - Minimum value
224
- * @param {number} max - Maximum value
225
- * @param {number} defaultValue - Default if invalid
226
- * @returns {number}
227
- */
228
- function sanitizeNumber(value, min, max, defaultValue) {
229
- const num = Number(value);
230
- if (isNaN(num) || num < min || num > max) {
231
- return defaultValue;
232
- }
233
- return num;
234
- }
235
-
236
- /**
237
- * Redact sensitive information from output
238
- * @param {string} text - Text to redact
239
- * @returns {string}
240
- */
241
- function redactSensitive(text) {
242
- if (!text || typeof text !== 'string') {
243
- return text;
244
- }
245
-
246
- let result = text;
247
- for (const pattern of CONFIG.SENSITIVE_PATTERNS) {
248
- result = result.replace(pattern, '[REDACTED]');
249
- }
250
- return result;
251
- }
252
-
253
- /**
254
- * Truncate output to prevent memory issues
255
- * @param {string} text - Text to truncate
256
- * @param {number} maxLength - Maximum length
257
- * @returns {string}
258
- */
259
- function truncateOutput(text, maxLength = CONFIG.LIMITS.MAX_OUTPUT_LENGTH) {
260
- if (!text || typeof text !== 'string') {
261
- return '';
262
- }
263
-
264
- if (text.length <= maxLength) {
265
- return text;
266
- }
267
-
268
- const truncated = text.slice(0, maxLength);
269
- return truncated + `\n\n...[Output truncated at ${maxLength} characters]`;
270
- }
271
-
272
- // ============================================================================
273
- // HARDENING: Rate Limiting (Per-API-Key)
274
- //
275
- // SECURITY FIX: Previous implementation used a global rate limit, allowing
276
- // a single attacker to exhaust the rate limit budget for ALL users.
277
- // Now we rate limit per-API-key hash to isolate abuse.
278
- // ============================================================================
279
-
280
- const rateLimitState = {
281
- callsByKey: new Map(), // Map<keyHash, timestamp[]>
282
- globalCalls: [], // Fallback for unauthenticated requests
283
- cleanupInterval: null,
284
- maxKeys: 10000, // Prevent unbounded growth
285
- };
286
-
287
- /**
288
- * Hash API key for rate limit tracking (don't store raw keys)
289
- */
290
- function hashKeyForRateLimit(apiKey) {
291
- if (!apiKey) return '__anonymous__';
292
- const crypto = require('crypto');
293
- return crypto.createHash('sha256').update(apiKey).digest('hex').slice(0, 16);
294
- }
295
-
296
- /**
297
- * Cleanup expired rate limit entries
298
- */
299
- function cleanupRateLimitState() {
300
- const now = Date.now();
301
- const windowStart = now - CONFIG.LIMITS.RATE_LIMIT_WINDOW_MS;
302
-
303
- // Clean per-key entries
304
- for (const [keyHash, calls] of rateLimitState.callsByKey) {
305
- const validCalls = calls.filter(t => t > windowStart);
306
- if (validCalls.length === 0) {
307
- rateLimitState.callsByKey.delete(keyHash);
308
- } else {
309
- rateLimitState.callsByKey.set(keyHash, validCalls);
310
- }
311
- }
312
-
313
- // Clean global calls
314
- rateLimitState.globalCalls = rateLimitState.globalCalls.filter(t => t > windowStart);
315
-
316
- // Enforce max keys to prevent memory exhaustion
317
- if (rateLimitState.callsByKey.size > rateLimitState.maxKeys) {
318
- // Evict oldest keys (those with oldest last call)
319
- const entries = Array.from(rateLimitState.callsByKey.entries());
320
- entries.sort((a, b) => Math.max(...(a[1] || [0])) - Math.max(...(b[1] || [0])));
321
- const toDelete = entries.slice(0, rateLimitState.callsByKey.size - rateLimitState.maxKeys);
322
- for (const [key] of toDelete) {
323
- rateLimitState.callsByKey.delete(key);
324
- }
325
- }
326
- }
327
-
328
- /**
329
- * Check rate limit for a specific API key
330
- *
331
- * @param {string} apiKey - The API key (optional, falls back to global limit)
332
- * @returns {{ allowed: boolean, remaining: number, resetIn: number, keyHash?: string }}
333
- */
334
- function checkRateLimit(apiKey = null) {
335
- const now = Date.now();
336
- const windowStart = now - CONFIG.LIMITS.RATE_LIMIT_WINDOW_MS;
337
- const keyHash = hashKeyForRateLimit(apiKey);
338
-
339
- // Get or create call array for this key
340
- let calls = rateLimitState.callsByKey.get(keyHash);
341
- if (!calls) {
342
- calls = [];
343
- rateLimitState.callsByKey.set(keyHash, calls);
344
- }
345
-
346
- // Clean old entries for this key
347
- const validCalls = calls.filter(t => t > windowStart);
348
- rateLimitState.callsByKey.set(keyHash, validCalls);
349
-
350
- // Determine limit based on authentication
351
- // Anonymous gets stricter limit (10/min), authenticated gets full limit
352
- const maxCalls = apiKey ? CONFIG.LIMITS.RATE_LIMIT_MAX_CALLS : Math.min(10, CONFIG.LIMITS.RATE_LIMIT_MAX_CALLS);
353
- const remaining = maxCalls - validCalls.length;
354
-
355
- if (remaining <= 0) {
356
- const oldestCall = Math.min(...validCalls);
357
- const resetIn = Math.max(0, (oldestCall + CONFIG.LIMITS.RATE_LIMIT_WINDOW_MS) - now);
358
- return { allowed: false, remaining: 0, resetIn, keyHash };
359
- }
360
-
361
- // Record this call
362
- validCalls.push(now);
363
-
364
- // Periodic cleanup (every ~100 calls)
365
- if (Math.random() < 0.01) {
366
- cleanupRateLimitState();
367
- }
368
-
369
- return { allowed: true, remaining: remaining - 1, resetIn: CONFIG.LIMITS.RATE_LIMIT_WINDOW_MS, keyHash };
370
- }
371
-
372
- // ============================================================================
373
- // HARDENING: Circuit Breaker for API Integration
374
- // ============================================================================
375
-
376
- const circuitBreakerState = {
377
- failures: 0,
378
- lastFailureTime: 0,
379
- state: 'CLOSED', // CLOSED = normal, OPEN = failing, HALF_OPEN = testing
380
- failureThreshold: 5,
381
- resetTimeout: 60000, // 1 minute
382
- };
383
-
384
- /**
385
- * Check if API calls should be allowed
386
- * @returns {{ allowed: boolean, reason?: string }}
387
- */
388
- function checkCircuitBreaker() {
389
- const now = Date.now();
390
-
391
- // If circuit is open, check if we should attempt reset
392
- if (circuitBreakerState.state === 'OPEN') {
393
- if (now - circuitBreakerState.lastFailureTime >= circuitBreakerState.resetTimeout) {
394
- circuitBreakerState.state = 'HALF_OPEN';
395
- console.error('[MCP] Circuit breaker entering HALF_OPEN state - testing API');
396
- } else {
397
- return {
398
- allowed: false,
399
- reason: `Circuit breaker OPEN - API calls disabled for ${Math.ceil((circuitBreakerState.resetTimeout - (now - circuitBreakerState.lastFailureTime)) / 1000)}s`
400
- };
401
- }
402
- }
403
-
404
- return { allowed: true };
405
- }
406
-
407
- /**
408
- * Record API call result
409
- * @param {boolean} success - Whether the API call succeeded
410
- */
411
- function recordApiResult(success) {
412
- if (success) {
413
- // Reset on success
414
- if (circuitBreakerState.state === 'HALF_OPEN') {
415
- console.error('[MCP] Circuit breaker CLOSED - API recovered');
416
- }
417
- circuitBreakerState.failures = 0;
418
- circuitBreakerState.state = 'CLOSED';
419
- } else {
420
- circuitBreakerState.failures++;
421
- circuitBreakerState.lastFailureTime = Date.now();
422
-
423
- if (circuitBreakerState.failures >= circuitBreakerState.failureThreshold) {
424
- circuitBreakerState.state = 'OPEN';
425
- console.error(`[MCP] Circuit breaker OPEN after ${circuitBreakerState.failures} failures - disabling API calls`);
426
- }
427
- }
428
- }
429
-
430
- // ============================================================================
431
- // HARDENING: Safe JSON Parsing
432
- // ============================================================================
433
-
434
- /**
435
- * Safely parse JSON with size limits
436
- * @param {string} text - JSON string to parse
437
- * @param {number} maxSize - Maximum allowed size in bytes
438
- * @returns {{ success: boolean, data?: any, error?: string }}
439
- */
440
- function safeJsonParse(text, maxSize = 5 * 1024 * 1024) {
441
- if (!text || typeof text !== 'string') {
442
- return { success: false, error: 'Invalid input' };
443
- }
444
-
445
- if (text.length > maxSize) {
446
- return { success: false, error: `JSON exceeds maximum size of ${maxSize} bytes` };
447
- }
448
-
449
- try {
450
- const data = JSON.parse(text);
451
- return { success: true, data };
452
- } catch (e) {
453
- return { success: false, error: `JSON parse error: ${e.message}` };
454
- }
455
- }
456
-
457
- const VERSION = CONFIG.VERSION;
37
+ const VERSION = "2.1.0";
458
38
 
459
39
  // Import intelligence tools
460
40
  import {
@@ -480,12 +60,6 @@ import {
480
60
  handleArchitectTool,
481
61
  } from "./architect-tools.js";
482
62
 
483
- // Import authority system tools
484
- import {
485
- AUTHORITY_TOOLS,
486
- handleAuthorityTool,
487
- } from "./authority-tools.js";
488
-
489
63
  // Import codebase architect tools
490
64
  import {
491
65
  CODEBASE_ARCHITECT_TOOLS,
@@ -517,779 +91,827 @@ import {
517
91
  TRUTH_FIREWALL_TOOLS,
518
92
  handleTruthFirewallTool,
519
93
  hasRecentClaimValidation,
520
- getContextAttribution,
521
94
  } from "./truth-firewall-tools.js";
522
95
 
523
- // Context attribution message
524
- const CONTEXT_ATTRIBUTION = "🧠 Context enhanced by vibecheck";
525
-
526
96
  // Import Consolidated Tools (15 focused tools - recommended surface)
527
97
  import { CONSOLIDATED_TOOLS, handleConsolidatedTool } from "./consolidated-tools.js";
528
98
 
529
- // Import v3 Tools (10 focused tools - STARTER+ only, no free tools)
530
- import { MCP_TOOLS_V3, handleToolV3, TOOL_TIERS as V3_TOOL_TIERS } from "./tools-v3.js";
531
-
532
99
  // Import tier auth for entitlement checking
533
- import { getFeatureAccessStatus } from "./tier-auth.js";
100
+ import { checkFeatureAccess } from "./tier-auth.js";
534
101
 
535
- // Import Agent Firewall Interceptor - ENABLED BY DEFAULT
536
- // The Agent Firewall is the core gatekeeper that validates AI changes against reality
537
- import {
538
- AGENT_FIREWALL_TOOL,
539
- handleAgentFirewallIntercept,
540
- } from "./agent-firewall-interceptor.js";
541
-
542
- // Import Conductor tools - Multi-Agent Coordination (Phase 2)
543
- import {
544
- CONDUCTOR_TOOLS,
545
- handleConductorToolCall,
546
- getConductorTools,
547
- } from "./conductor/tools.js";
548
-
549
- /**
550
- * TRUTH FIREWALL CONFIGURATION
551
- *
552
- * Tools that make assertions or change code MUST have recent claim validation.
553
- * Policy modes: strict (default for agents), balanced, permissive
554
- */
555
102
  const STRICT_GUARDRAIL_TOOLS = new Set([
556
103
  "vibecheck.scan",
557
104
  "vibecheck.ship",
558
105
  "vibecheck.ctx",
559
- "vibecheck.fix",
560
- "vibecheck.prove",
561
- "vibecheck.autopilot_apply",
562
106
  ]);
563
107
 
564
- // Tools that modify code or make assertions - require truth firewall
565
- const CODE_CHANGING_TOOLS = new Set([
566
- "vibecheck.fix",
567
- "vibecheck.autopilot_apply",
568
- "vibecheck.propose_patch",
569
- ]);
570
-
571
- // Policy thresholds (aligned with proof-context.js EVIDENCE_SCHEMA)
572
- const POLICY_THRESHOLDS = {
573
- strict: { minConfidence: 0.8, allowUnknown: false, requireValidation: true },
574
- balanced: { minConfidence: 0.6, allowUnknown: false, requireValidation: true },
575
- permissive: { minConfidence: 0.4, allowUnknown: true, requireValidation: false },
576
- };
577
-
578
108
  function getTruthPolicy(args) {
579
- const policy = args?.policy || "strict";
580
- return POLICY_THRESHOLDS[policy] ? policy : "strict";
109
+ return args?.policy || "strict";
581
110
  }
582
111
 
583
- function getPolicyConfig(policy) {
584
- return POLICY_THRESHOLDS[policy] || POLICY_THRESHOLDS.strict;
585
- }
586
-
587
- /**
588
- * Emit guardrail metric to audit log.
589
- *
590
- * SECURITY FIX: Previous implementation silently ignored all failures.
591
- * Now we log failures to stderr for security monitoring - an attacker
592
- * filling disk or manipulating permissions would have gone undetected.
593
- */
594
112
  async function emitGuardrailMetric(projectPath, metric) {
595
113
  try {
596
114
  const auditDir = path.join(projectPath, ".vibecheck", "audit");
597
115
  await fs.mkdir(auditDir, { recursive: true });
598
116
  const record = JSON.stringify({ ...metric, timestamp: new Date().toISOString() });
599
117
  await fs.appendFile(path.join(auditDir, "guardrail-metrics.jsonl"), `${record}\n`);
600
- } catch (err) {
601
- // SECURITY: Log failures - silent failure could hide attacks
602
- // (e.g., attacker fills disk to prevent audit logging)
603
- console.error(`[SECURITY] Guardrail metric write failed: ${err.message}`);
604
- console.error(`[SECURITY] Failed metric: ${JSON.stringify(metric)}`);
605
-
606
- // Attempt fallback to stderr-only logging for critical metrics
607
- if (metric.event === 'truth_firewall_block' || metric.event === 'security_violation') {
608
- console.error(`[SECURITY-CRITICAL] ${metric.event}: ${JSON.stringify(metric)}`);
609
- }
610
- }
611
- }
612
-
613
- /**
614
- * Check if a code-changing tool should be blocked due to missing validation.
615
- * Returns { blocked: boolean, reason?: string, suggestion?: string }
616
- */
617
- function checkTruthFirewallBlock(toolName, args, projectPath) {
618
- const policy = getTruthPolicy(args);
619
- const policyConfig = getPolicyConfig(policy);
620
-
621
- // Skip validation check if permissive mode and validation not required
622
- if (!policyConfig.requireValidation) {
623
- return { blocked: false };
624
- }
625
-
626
- // Check if this is a code-changing tool that requires validation
627
- if (!CODE_CHANGING_TOOLS.has(toolName) && !STRICT_GUARDRAIL_TOOLS.has(toolName)) {
628
- return { blocked: false };
629
- }
630
-
631
- // Check for recent claim validation
632
- if (!hasRecentClaimValidation(projectPath)) {
633
- return {
634
- blocked: true,
635
- reason: `Truth firewall requires claim validation before ${toolName}`,
636
- code: "TRUTH_FIREWALL_REQUIRED",
637
- suggestion: "Call vibecheck.validate_claim or vibecheck.get_truthpack before proceeding",
638
- nextSteps: [
639
- "Call vibecheck.get_truthpack with refresh=true for current evidence",
640
- "Call vibecheck.validate_claim for critical assumptions",
641
- `Re-run ${toolName} after validation`,
642
- ],
643
- };
118
+ } catch {
119
+ // ignore metrics write failures
644
120
  }
645
-
646
- return { blocked: false };
647
121
  }
648
122
 
649
123
  // ============================================================================
650
124
  // TOOL DEFINITIONS - Public Tools (Clean Product Surface)
651
125
  // ============================================================================
652
126
 
653
- // ============================================================================
654
- // TOOL REGISTRATION - V3 Tools Only (2-tier: FREE / PRO)
655
- // ============================================================================
656
- // V3 tools are the canonical tool surface with 2-tier model:
657
- // - FREE (10 tools): Inspect & Observe
658
- // - PRO (15 tools): Fix, Prove & Enforce (includes Authority, Conductor, Firewall)
127
+ // RECOMMENDED: Use consolidated tools (15 focused, evidence-backed tools)
128
+ // These map directly to CLI commands and return file/line citations
129
+ const USE_CONSOLIDATED_TOOLS = process.env.VIBECHECK_MCP_CONSOLIDATED !== 'false';
130
+
131
+ const TOOLS = USE_CONSOLIDATED_TOOLS ? [
132
+ // Curated tools for agents
133
+ ...CONSOLIDATED_TOOLS,
134
+ ] : [
135
+ // Legacy: Full tool set (50+ tools) - for backward compatibility
136
+ // PRIORITY: Truth Firewall tools (Hallucination Stopper) - agents MUST use these
137
+ ...TRUTH_FIREWALL_TOOLS, // vibecheck.get_truthpack, vibecheck.validate_claim, vibecheck.compile_context, etc.
138
+
139
+ // Truth Context tools (Evidence-Backed AI)
140
+ ...TRUTH_CONTEXT_TOOLS, // vibecheck.ctx, vibecheck.verify_claim, vibecheck.evidence
141
+
142
+ ...INTELLIGENCE_TOOLS, // Add all intelligence suite tools
143
+ ...VIBECHECK_TOOLS, // Add AI vibecheck tools (verify, quality, smells, etc.)
144
+ ...AGENT_CHECKPOINT_TOOLS, // Add agent checkpoint tools
145
+ ...ARCHITECT_TOOLS, // Add architect review/suggest tools
146
+ ...CODEBASE_ARCHITECT_TOOLS, // Add codebase-aware architect tools
147
+ ...VIBECHECK_2_TOOLS, // Add vibecheck 2.0 consolidated tools
148
+ ...intentDriftTools, // Add intent drift guard tools
149
+ mdcGeneratorTool, // Add MDC generator tool
150
+ // 1. SHIP - Quick health check (vibe coder friendly)
151
+ {
152
+ name: "vibecheck.ship",
153
+ description:
154
+ "šŸš€ Quick health check — 'Is my app ready?' Plain English, traffic light score",
155
+ inputSchema: {
156
+ type: "object",
157
+ properties: {
158
+ projectPath: {
159
+ type: "string",
160
+ description: "Path to project root",
161
+ default: ".",
162
+ },
163
+ fix: {
164
+ type: "boolean",
165
+ description: "Auto-fix problems where possible",
166
+ default: false,
167
+ },
168
+ },
169
+ },
170
+ },
659
171
 
660
- const TOOLS = [
661
- // V3 tools include all FREE and PRO tools
662
- // Authority, Conductor, and Agent Firewall are included in MCP_TOOLS_V3
663
- ...MCP_TOOLS_V3,
664
- ].filter(t => t !== null);
172
+ // 2. SCAN - Deep technical analysis
173
+ {
174
+ name: "vibecheck.scan",
175
+ description:
176
+ "šŸ” Deep scan — technical analysis of secrets, auth, mocks, routes (detailed output)",
177
+ inputSchema: {
178
+ type: "object",
179
+ properties: {
180
+ projectPath: {
181
+ type: "string",
182
+ description: "Path to project root",
183
+ default: ".",
184
+ },
185
+ profile: {
186
+ type: "string",
187
+ enum: ["quick", "full", "ship", "ci", "security", "compliance", "ai"],
188
+ description:
189
+ "Check profile: quick, full, ship, ci, security, compliance, ai",
190
+ default: "quick",
191
+ },
192
+ only: {
193
+ type: "array",
194
+ items: { type: "string" },
195
+ description:
196
+ "Run only specific checks: integrity, security, hygiene, contracts, auth, routes, mocks, compliance, ai",
197
+ },
198
+ format: {
199
+ type: "string",
200
+ enum: ["text", "json", "html", "sarif"],
201
+ description: "Output format",
202
+ default: "text",
203
+ },
204
+ },
205
+ },
206
+ },
665
207
 
666
- // Legacy tool definitions removed - V3 is the only supported mode
667
- // All tools are now defined in tools-v3.js
208
+ // 3. VERIFY - Runtime verification with Playwright
209
+ {
210
+ name: "vibecheck.verify",
211
+ description:
212
+ "🧪 Runtime Verify — clicks buttons, fills forms, finds Dead UI with Playwright",
213
+ inputSchema: {
214
+ type: "object",
215
+ properties: {
216
+ url: {
217
+ type: "string",
218
+ description: "Target URL to test (required)",
219
+ },
220
+ auth: {
221
+ type: "string",
222
+ description: "Auth credentials (email:password)",
223
+ },
224
+ flows: {
225
+ type: "array",
226
+ items: { type: "string" },
227
+ description: "Flow packs to test: auth, ui, forms, billing",
228
+ },
229
+ headed: {
230
+ type: "boolean",
231
+ description: "Run browser in visible mode",
232
+ default: false,
233
+ },
234
+ record: {
235
+ type: "boolean",
236
+ description: "Record video of test run",
237
+ default: false,
238
+ },
239
+ },
240
+ required: ["url"],
241
+ },
242
+ },
668
243
 
244
+ // 3b. REALITY v2 - Two-Pass Auth Verification + Dead UI Crawler
245
+ {
246
+ name: "vibecheck.reality",
247
+ description:
248
+ "🧪 Reality Mode v2 — Two-pass auth verification: crawl anon, then auth. Finds Dead UI, HTTP errors, auth coverage gaps.",
249
+ inputSchema: {
250
+ type: "object",
251
+ properties: {
252
+ url: {
253
+ type: "string",
254
+ description: "Target URL to test (required)",
255
+ },
256
+ auth: {
257
+ type: "string",
258
+ description: "Auth credentials (email:password) for login attempt",
259
+ },
260
+ verifyAuth: {
261
+ type: "boolean",
262
+ description: "Enable two-pass auth verification (anon + auth)",
263
+ default: false,
264
+ },
265
+ storageState: {
266
+ type: "string",
267
+ description: "Path to Playwright storageState.json for pre-authenticated session",
268
+ },
269
+ saveStorageState: {
270
+ type: "string",
271
+ description: "Path to save storageState after successful login",
272
+ },
273
+ truthpack: {
274
+ type: "string",
275
+ description: "Path to truthpack.json for auth matcher verification",
276
+ },
277
+ headed: {
278
+ type: "boolean",
279
+ description: "Run browser in visible mode",
280
+ default: false,
281
+ },
282
+ maxPages: {
283
+ type: "number",
284
+ description: "Max pages to visit per pass (default: 18)",
285
+ default: 18,
286
+ },
287
+ maxDepth: {
288
+ type: "number",
289
+ description: "Max link depth to crawl (default: 2)",
290
+ default: 2,
291
+ },
292
+ danger: {
293
+ type: "boolean",
294
+ description: "Allow clicking risky buttons (delete, cancel, etc.)",
295
+ default: false,
296
+ },
297
+ },
298
+ required: ["url"],
299
+ },
300
+ },
669
301
 
302
+ // 4. AI-TEST - AI Agent testing
303
+ {
304
+ name: "vibecheckai.dev-test",
305
+ description:
306
+ "šŸ¤– AI Agent — autonomous testing that explores your app and generates fix prompts",
307
+ inputSchema: {
308
+ type: "object",
309
+ properties: {
310
+ url: {
311
+ type: "string",
312
+ description: "Target URL to test (required)",
313
+ },
314
+ goal: {
315
+ type: "string",
316
+ description: "Natural language goal for the AI agent",
317
+ default: "Test all features and find issues",
318
+ },
319
+ headed: {
320
+ type: "boolean",
321
+ description: "Run browser in visible mode",
322
+ default: false,
323
+ },
324
+ },
325
+ required: ["url"],
326
+ },
327
+ },
670
328
 
671
- // ============================================================================
672
- // SERVER IMPLEMENTATION
673
- // ============================================================================
329
+ // 3. GATE - Enforce truth in CI
330
+ {
331
+ name: "vibecheck.gate",
332
+ description: "🚦 Enforce truth in CI — fail builds on policy violations (STARTER tier)",
333
+ inputSchema: {
334
+ type: "object",
335
+ properties: {
336
+ projectPath: {
337
+ type: "string",
338
+ default: ".",
339
+ },
340
+ policy: {
341
+ type: "string",
342
+ enum: ["default", "strict", "ci"],
343
+ description: "Policy strictness level",
344
+ default: "strict",
345
+ },
346
+ sarif: {
347
+ type: "boolean",
348
+ description: "Generate SARIF for GitHub Code Scanning",
349
+ default: true,
350
+ },
351
+ },
352
+ },
353
+ },
674
354
 
675
- class VibecheckMCP {
676
- constructor() {
677
- this.server = new Server(
678
- { name: "vibecheck", version: VERSION },
679
- { capabilities: { tools: {}, resources: {} } },
680
- );
681
- this.toolRegistry = this.buildToolRegistry();
682
- this.setupHandlers();
683
- }
355
+ // 5. FIX - Fix Missions v1
356
+ {
357
+ name: "vibecheck.fix",
358
+ description:
359
+ "šŸ”§ Fix Missions v1 — AI-powered surgical fixes with proof verification loop. --apply and --autopilot require PRO tier ($99/mo).",
360
+ inputSchema: {
361
+ type: "object",
362
+ properties: {
363
+ projectPath: {
364
+ type: "string",
365
+ default: ".",
366
+ },
367
+ promptOnly: {
368
+ type: "boolean",
369
+ description: "Generate mission prompts only (no edits)",
370
+ default: false,
371
+ },
372
+ apply: {
373
+ type: "boolean",
374
+ description: "Apply patches returned by the model",
375
+ default: false,
376
+ },
377
+ autopilot: {
378
+ type: "boolean",
379
+ description: "Loop: fix → verify → fix until SHIP or stuck",
380
+ default: false,
381
+ },
382
+ share: {
383
+ type: "boolean",
384
+ description: "Generate share bundle for review",
385
+ default: false,
386
+ },
387
+ maxMissions: {
388
+ type: "number",
389
+ description: "Max missions to plan (default: 8)",
390
+ default: 8,
391
+ },
392
+ maxSteps: {
393
+ type: "number",
394
+ description: "Max autopilot steps (default: 10)",
395
+ default: 10,
396
+ },
397
+ },
398
+ },
399
+ },
684
400
 
685
- // ============================================================================
686
- // TOOL REGISTRY - Maps tool names to handlers for cleaner dispatch
687
- // HARDENING: Validates all handlers are functions
688
- // ============================================================================
689
- buildToolRegistry() {
690
- const registry = {};
691
-
692
- // Helper to safely add handler with validation
693
- const addHandler = (name, handler) => {
694
- if (typeof handler !== 'function') {
695
- console.error(`[MCP] Warning: Tool ${name} handler is not a function`);
696
- return;
697
- }
698
- registry[name] = handler;
699
- };
700
-
701
- // Agent Firewall - intercepts file writes (if available)
702
- if (handleAgentFirewallIntercept && typeof handleAgentFirewallIntercept === 'function') {
703
- addHandler("vibecheck_agent_firewall_intercept", handleAgentFirewallIntercept);
704
- }
705
-
706
- // Conductor - multi-agent coordination tools
707
- addHandler("vibecheck_conductor_register", (projectPath, args) => handleConductorToolCall("vibecheck_conductor_register", args, projectPath));
708
- addHandler("vibecheck_conductor_acquire_lock", (projectPath, args) => handleConductorToolCall("vibecheck_conductor_acquire_lock", args, projectPath));
709
- addHandler("vibecheck_conductor_release_lock", (projectPath, args) => handleConductorToolCall("vibecheck_conductor_release_lock", args, projectPath));
710
- addHandler("vibecheck_conductor_propose", (projectPath, args) => handleConductorToolCall("vibecheck_conductor_propose", args, projectPath));
711
- addHandler("vibecheck_conductor_status", (projectPath, args) => handleConductorToolCall("vibecheck_conductor_status", args, projectPath));
712
- addHandler("vibecheck_conductor_terminate", (projectPath, args) => handleConductorToolCall("vibecheck_conductor_terminate", args, projectPath));
713
-
714
- // Core CLI tools
715
- addHandler("vibecheck.ship", this.handleShip.bind(this));
716
- addHandler("vibecheck.scan", this.handleScan.bind(this));
717
- addHandler("vibecheck.verify", this.handleVerify.bind(this));
718
- addHandler("vibecheck.reality", this.handleReality.bind(this));
719
- addHandler("vibecheckai.dev-test", this.handleAITest.bind(this));
720
- addHandler("vibecheck.gate", this.handleGate.bind(this));
721
- addHandler("vibecheck.fix", this.handleFix.bind(this));
722
- addHandler("vibecheck.share", this.handleShare.bind(this));
723
- addHandler("vibecheck.ctx", this.handleCtx.bind(this));
724
- addHandler("vibecheck.prove", this.handleProve.bind(this));
725
- addHandler("vibecheck.proof", this.handleProof.bind(this));
726
- addHandler("vibecheck.validate", this.handleValidate.bind(this));
727
- addHandler("vibecheck.report", this.handleReport.bind(this));
728
- addHandler("vibecheck.status", this.handleStatus.bind(this));
729
- addHandler("vibecheck.autopilot", this.handleAutopilot.bind(this));
730
- addHandler("vibecheck.autopilot_plan", this.handleAutopilotPlan.bind(this));
731
- addHandler("vibecheck.autopilot_apply", this.handleAutopilotApply.bind(this));
732
- addHandler("vibecheck.badge", this.handleBadge.bind(this));
733
- addHandler("vibecheck.context", this.handleContext.bind(this));
734
-
735
- console.error(`[MCP] Tool registry built with ${Object.keys(registry).length} handlers`);
736
- return registry;
737
- }
401
+ // 6. SHARE - Generate share bundle from fix missions
402
+ {
403
+ name: "vibecheck.share",
404
+ description: "šŸ“¦ Share Bundle — generate PR comment / review bundle from latest fix missions",
405
+ inputSchema: {
406
+ type: "object",
407
+ properties: {
408
+ projectPath: {
409
+ type: "string",
410
+ default: ".",
411
+ },
412
+ prComment: {
413
+ type: "boolean",
414
+ description: "Output GitHub PR comment format",
415
+ default: false,
416
+ },
417
+ out: {
418
+ type: "string",
419
+ description: "Write output to file path",
420
+ },
421
+ },
422
+ },
423
+ },
738
424
 
739
- // ============================================================================
740
- // CLI RUNNER - Secure, async execution with array args (prevents injection)
741
- // ============================================================================
742
- async runCLI(command, args = [], cwd, options = {}) {
743
- const {
744
- env = {},
745
- timeout = CONFIG.TIMEOUTS.DEFAULT,
746
- skipAuth = true,
747
- } = options;
748
-
749
- // ========================================================================
750
- // HARDENING: Validate command
751
- // ========================================================================
752
- const sanitizedCommand = sanitizeString(command, 50);
753
- if (!sanitizedCommand || !/^[a-z0-9_-]+$/i.test(sanitizedCommand)) {
754
- throw new Error(`Invalid CLI command: ${sanitizedCommand}`);
755
- }
756
-
757
- // ========================================================================
758
- // HARDENING: Validate and sanitize arguments
759
- // ========================================================================
760
- const sanitizedArgs = sanitizeArray(args, 50).map(arg => {
761
- const str = String(arg);
762
- // Validate argument format (must be simple flags or values)
763
- if (str.length > 1000) {
764
- return str.slice(0, 1000);
765
- }
766
- return str;
767
- });
425
+ // 7. PROVE - One Command Reality Proof (orchestrates ctx → reality → ship → fix)
426
+ {
427
+ name: "vibecheck.prove",
428
+ description: "šŸ”¬ One Command Reality Proof — orchestrates ctx → reality → ship → fix loop until SHIP or stuck (PRO tier)",
429
+ inputSchema: {
430
+ type: "object",
431
+ properties: {
432
+ projectPath: {
433
+ type: "string",
434
+ default: ".",
435
+ },
436
+ url: {
437
+ type: "string",
438
+ description: "Base URL for runtime testing",
439
+ },
440
+ auth: {
441
+ type: "string",
442
+ description: "Auth credentials (email:password)",
443
+ },
444
+ storageState: {
445
+ type: "string",
446
+ description: "Path to Playwright storageState.json",
447
+ },
448
+ maxFixRounds: {
449
+ type: "number",
450
+ description: "Max auto-fix attempts (default: 3)",
451
+ default: 3,
452
+ },
453
+ skipReality: {
454
+ type: "boolean",
455
+ description: "Skip runtime crawling (static only)",
456
+ default: false,
457
+ },
458
+ skipFix: {
459
+ type: "boolean",
460
+ description: "Don't auto-fix, just diagnose",
461
+ default: false,
462
+ },
463
+ headed: {
464
+ type: "boolean",
465
+ description: "Run browser in visible mode",
466
+ default: false,
467
+ },
468
+ danger: {
469
+ type: "boolean",
470
+ description: "Allow clicking risky buttons",
471
+ default: false,
472
+ },
473
+ },
474
+ },
475
+ },
768
476
 
769
- // ========================================================================
770
- // HARDENING: Validate working directory
771
- // ========================================================================
772
- const resolvedCwd = path.resolve(cwd || process.cwd());
773
- if (!fsSync.existsSync(resolvedCwd)) {
774
- throw new Error(`Working directory does not exist: ${resolvedCwd}`);
775
- }
477
+ // 8. CTX - Truth Pack Generator
478
+ {
479
+ name: "vibecheck.ctx",
480
+ description: "šŸ“¦ Truth Pack — generate ground truth for AI agents (routes, env, auth, billing)",
481
+ inputSchema: {
482
+ type: "object",
483
+ properties: {
484
+ projectPath: {
485
+ type: "string",
486
+ default: ".",
487
+ },
488
+ snapshot: {
489
+ type: "boolean",
490
+ description: "Save timestamped snapshot",
491
+ default: false,
492
+ },
493
+ json: {
494
+ type: "boolean",
495
+ description: "Output raw JSON",
496
+ default: false,
497
+ },
498
+ },
499
+ },
500
+ },
776
501
 
777
- // Build argument array - this prevents command injection
778
- const finalArgs = [CONFIG.BIN_PATH, sanitizedCommand, ...sanitizedArgs];
779
-
780
- // ========================================================================
781
- // HARDENING: Clean environment - don't leak sensitive vars
782
- // ========================================================================
783
- const safeEnv = { ...process.env };
784
- // Remove potentially sensitive env vars from being passed through
785
- const sensitiveEnvKeys = ['AWS_SECRET_ACCESS_KEY', 'STRIPE_SECRET_KEY', 'DATABASE_URL'];
786
- for (const key of sensitiveEnvKeys) {
787
- if (safeEnv[key] && !env[key]) {
788
- // Only remove if not explicitly set in options
789
- delete safeEnv[key];
790
- }
791
- }
502
+ // 9. PROOF - Premium verification
503
+ {
504
+ name: "vibecheck.proof",
505
+ description:
506
+ "šŸŽ¬ Premium verification — mocks (static) or reality (runtime with Playwright)",
507
+ inputSchema: {
508
+ type: "object",
509
+ properties: {
510
+ projectPath: {
511
+ type: "string",
512
+ default: ".",
513
+ },
514
+ mode: {
515
+ type: "string",
516
+ enum: ["mocks", "reality"],
517
+ description:
518
+ "Proof mode: mocks (import graph + fake domains) or reality (Playwright runtime)",
519
+ },
520
+ url: {
521
+ type: "string",
522
+ description: "Base URL for reality mode",
523
+ default: "http://localhost:3000",
524
+ },
525
+ flow: {
526
+ type: "string",
527
+ enum: ["auth", "checkout", "dashboard"],
528
+ description: "Flow to test in reality mode",
529
+ default: "auth",
530
+ },
531
+ },
532
+ required: ["mode"],
533
+ },
534
+ },
792
535
 
793
- const execEnv = {
794
- ...safeEnv,
795
- ...CONFIG.ENV_DEFAULTS,
796
- ...(skipAuth ? { VIBECHECK_SKIP_AUTH: "1" } : {}),
797
- ...env,
798
- // Ensure Node.js doesn't prompt for anything
799
- NODE_NO_READLINE: "1",
800
- FORCE_COLOR: "0",
801
- };
802
-
803
- // ========================================================================
804
- // HARDENING: Bounded timeout
805
- // ========================================================================
806
- const boundedTimeout = sanitizeNumber(timeout, 1000, 900000, CONFIG.TIMEOUTS.DEFAULT);
536
+ // 5. REPORT - Access artifacts
537
+ {
538
+ name: "vibecheck.validate",
539
+ description:
540
+ "šŸ¤– Validate AI-generated code. Checks for hallucinations, intent mismatch, and quality issues.",
541
+ inputSchema: {
542
+ type: "object",
543
+ properties: {
544
+ code: { type: "string", description: "The code content to validate" },
545
+ intent: {
546
+ type: "string",
547
+ description: "The user's original request/intent",
548
+ },
549
+ projectPath: { type: "string", default: "." },
550
+ },
551
+ required: ["code"],
552
+ },
553
+ },
554
+ // 10. REPORT - Access scan artifacts
555
+ {
556
+ name: "vibecheck.report",
557
+ description:
558
+ "šŸ“„ Access scan artifacts — summary, full report, SARIF export",
559
+ inputSchema: {
560
+ type: "object",
561
+ properties: {
562
+ projectPath: {
563
+ type: "string",
564
+ default: ".",
565
+ },
566
+ type: {
567
+ type: "string",
568
+ enum: ["summary", "full", "sarif", "html"],
569
+ description: "Report type to retrieve",
570
+ default: "summary",
571
+ },
572
+ runId: {
573
+ type: "string",
574
+ description: "Specific run ID (defaults to last run)",
575
+ },
576
+ },
577
+ },
578
+ },
807
579
 
808
- try {
809
- const { stdout, stderr } = await execFileAsync(process.execPath, finalArgs, {
810
- cwd: resolvedCwd,
811
- encoding: "utf8",
812
- maxBuffer: CONFIG.MAX_BUFFER,
813
- timeout: boundedTimeout,
814
- env: execEnv,
815
- // Don't inherit stdin - prevents hanging
816
- stdio: ['ignore', 'pipe', 'pipe'],
817
- });
818
-
819
- // Sanitize output before returning
820
- return {
821
- stdout: redactSensitive(truncateOutput(stdout || '')),
822
- stderr: redactSensitive(truncateOutput(stderr || '')),
823
- success: true
824
- };
825
- } catch (error) {
826
- // Attach partial output for graceful degradation (sanitized)
827
- error.partialOutput = redactSensitive(truncateOutput(error.stdout || ''));
828
- error.partialStderr = redactSensitive(truncateOutput(error.stderr || ''));
829
-
830
- // Add helpful error code for timeout
831
- if (error.killed && error.signal === 'SIGTERM') {
832
- error.code = 'TIMEOUT';
833
- error.message = `Command timed out after ${boundedTimeout}ms`;
834
- }
835
-
836
- throw error;
837
- }
838
- }
580
+ // 11. STATUS - Health and config
581
+ {
582
+ name: "vibecheck.status",
583
+ description: "šŸ“Š Server status — health, versions, config, last run info",
584
+ inputSchema: {
585
+ type: "object",
586
+ properties: {
587
+ projectPath: {
588
+ type: "string",
589
+ default: ".",
590
+ },
591
+ },
592
+ },
593
+ },
839
594
 
840
- // ============================================================================
841
- // HARDENED UTILITY HELPERS
842
- // ============================================================================
843
-
844
- /**
845
- * Strip ANSI escape codes from output with length validation
846
- * @param {string} str - String to strip
847
- * @returns {string}
848
- */
849
- stripAnsi(str) {
850
- if (!str || typeof str !== 'string') {
851
- return '';
852
- }
853
-
854
- // Truncate first to prevent DoS on very long strings
855
- const truncated = str.length > CONFIG.LIMITS.MAX_OUTPUT_LENGTH
856
- ? str.slice(0, CONFIG.LIMITS.MAX_OUTPUT_LENGTH)
857
- : str;
858
-
859
- return truncated.replace(/\x1b\[[0-9;]*m/g, "");
860
- }
595
+ // 12. AUTOPILOT - Continuous protection
596
+ {
597
+ name: "vibecheck.autopilot",
598
+ description:
599
+ "šŸ¤– Autopilot — continuous protection with weekly reports, auto-PRs, deploy blocking",
600
+ inputSchema: {
601
+ type: "object",
602
+ properties: {
603
+ projectPath: {
604
+ type: "string",
605
+ default: ".",
606
+ },
607
+ action: {
608
+ type: "string",
609
+ enum: ["status", "enable", "disable", "digest"],
610
+ description: "Autopilot action",
611
+ default: "status",
612
+ },
613
+ slack: {
614
+ type: "string",
615
+ description: "Slack webhook URL for notifications",
616
+ },
617
+ email: {
618
+ type: "string",
619
+ description: "Email for weekly digest",
620
+ },
621
+ },
622
+ },
623
+ },
861
624
 
862
- /**
863
- * Parse summary from disk with size limits and validation
864
- * @param {string} projectPath - Project root path
865
- * @returns {Promise<object|null>} Parsed summary or null
866
- */
867
- async parseSummaryFromDisk(projectPath) {
868
- const summaryPath = path.join(projectPath, CONFIG.OUTPUT_DIR, "summary.json");
869
-
870
- try {
871
- // Check file size first
872
- const stats = await fs.stat(summaryPath);
873
- if (stats.size > 5 * 1024 * 1024) { // 5MB limit
874
- console.error(`[MCP] Summary file too large: ${stats.size} bytes`);
875
- return null;
876
- }
877
-
878
- const content = await fs.readFile(summaryPath, "utf-8");
879
- const parsed = safeJsonParse(content);
880
-
881
- if (!parsed.success) {
882
- console.error(`[MCP] Invalid summary JSON: ${parsed.error}`);
883
- return null;
884
- }
885
-
886
- return parsed.data;
887
- } catch (err) {
888
- // Silent fail - this is for graceful degradation
889
- return null;
890
- }
891
- }
625
+ // 13. AUTOPILOT PLAN - Generate fix plan (PRO tier)
626
+ {
627
+ name: "vibecheck.autopilot_plan",
628
+ description:
629
+ "šŸ¤– Autopilot Plan — scan codebase, group issues into fix packs, estimate risk (PRO tier)",
630
+ inputSchema: {
631
+ type: "object",
632
+ properties: {
633
+ projectPath: {
634
+ type: "string",
635
+ default: ".",
636
+ },
637
+ profile: {
638
+ type: "string",
639
+ enum: ["quick", "full", "ship", "ci"],
640
+ description: "Scan profile",
641
+ default: "ship",
642
+ },
643
+ maxFixes: {
644
+ type: "number",
645
+ description: "Max fixes per category",
646
+ default: 10,
647
+ },
648
+ },
649
+ },
650
+ },
892
651
 
893
- /**
894
- * Format scan output from summary object with validation
895
- * @param {object} summary - Summary object
896
- * @param {string} projectPath - Project root path
897
- * @returns {string}
898
- */
899
- formatScanOutput(summary, projectPath) {
900
- if (!summary || typeof summary !== 'object') {
901
- return '## Error: Invalid summary data\n';
902
- }
903
-
904
- // Safely extract values with defaults - handle different output structures
905
- const score = sanitizeNumber(
906
- summary.score?.overall || summary.score || summary.verdict?.score || 0,
907
- 0, 100, 0
908
- );
909
- const grade = sanitizeString(summary.grade || summary.verdict?.grade, 10) || 'N/A';
910
- const verdict = summary.verdict?.verdict || summary.verdict || (score >= 80 ? 'SHIP' : score >= 50 ? 'WARN' : 'BLOCK');
911
- const canShip = verdict === 'SHIP' || Boolean(summary.canShip);
912
- const findings = sanitizeArray(summary.findings || summary.report?.findings || [], 500);
913
-
914
- // Get severity counts
915
- const sevCounts = { critical: 0, high: 0, medium: 0, low: 0 };
916
- for (const f of findings) {
917
- const sev = (f.severity || 'medium').toLowerCase();
918
- if (sev === 'block' || sev === 'critical') sevCounts.critical++;
919
- else if (sev === 'high') sevCounts.high++;
920
- else if (sev === 'warn' || sev === 'warning' || sev === 'medium') sevCounts.medium++;
921
- else sevCounts.low++;
922
- }
923
-
924
- // Build structured output
925
- let output = '';
926
-
927
- // Header with verdict
928
- const verdictEmoji = verdict === 'SHIP' ? 'āœ…' : verdict === 'WARN' ? 'āš ļø' : '🚫';
929
- output += `## ${verdictEmoji} VERDICT: ${verdict}\n\n`;
930
- output += `**Health Score:** ${score}/100 (${grade})\n\n`;
931
-
932
- // Summary stats
933
- output += `### Summary\n`;
934
- output += `- **Total Issues:** ${findings.length}\n`;
935
- output += `- **Critical:** ${sevCounts.critical}\n`;
936
- output += `- **High:** ${sevCounts.high}\n`;
937
- output += `- **Medium:** ${sevCounts.medium}\n`;
938
- output += `- **Low:** ${sevCounts.low}\n\n`;
939
-
940
- // Category breakdown
941
- if (summary.counts && typeof summary.counts === 'object') {
942
- output += "### Issue Categories\n\n";
943
- output += "| Category | Count | Status |\n|----------|-------|--------|\n";
944
-
945
- const entries = Object.entries(summary.counts).slice(0, 20);
946
- for (const [key, count] of entries) {
947
- const safeKey = sanitizeString(key, 50);
948
- const safeCount = sanitizeNumber(count, 0, 999999, 0);
949
- const icon = safeCount === 0 ? "āœ…" : "āš ļø";
950
- output += `| ${safeKey} | ${safeCount} | ${icon} |\n`;
951
- }
952
- output += '\n';
953
- }
652
+ // 14. AUTOPILOT APPLY - Apply fixes (PRO tier)
653
+ {
654
+ name: "vibecheck.autopilot_apply",
655
+ description:
656
+ "šŸ”§ Autopilot Apply — apply fix packs with verification, re-scan to confirm (PRO tier)",
657
+ inputSchema: {
658
+ type: "object",
659
+ properties: {
660
+ projectPath: {
661
+ type: "string",
662
+ default: ".",
663
+ },
664
+ profile: {
665
+ type: "string",
666
+ enum: ["quick", "full", "ship", "ci"],
667
+ description: "Scan profile",
668
+ default: "ship",
669
+ },
670
+ maxFixes: {
671
+ type: "number",
672
+ description: "Max fixes per category",
673
+ default: 10,
674
+ },
675
+ verify: {
676
+ type: "boolean",
677
+ description: "Run verification after apply",
678
+ default: true,
679
+ },
680
+ dryRun: {
681
+ type: "boolean",
682
+ description: "Preview changes without applying",
683
+ default: false,
684
+ },
685
+ },
686
+ },
687
+ },
954
688
 
955
- // Top findings with details (show up to 15)
956
- if (findings.length > 0) {
957
- output += "### Top Issues to Fix\n\n";
958
-
959
- // Sort by severity (critical first)
960
- const sortedFindings = findings
961
- .sort((a, b) => {
962
- const sevOrder = { critical: 0, block: 0, high: 1, warn: 2, warning: 2, medium: 2, low: 3, info: 4 };
963
- const aOrder = sevOrder[(a.severity || 'medium').toLowerCase()] || 2;
964
- const bOrder = sevOrder[(b.severity || 'medium').toLowerCase()] || 2;
965
- return aOrder - bOrder;
966
- })
967
- .slice(0, 15);
968
-
969
- for (let i = 0; i < sortedFindings.length; i++) {
970
- const f = sortedFindings[i];
971
- const sev = sanitizeString(f.severity || 'medium', 20).toUpperCase();
972
- const sevEmoji = sev === 'CRITICAL' || sev === 'BLOCK' ? 'šŸ”“' : sev === 'HIGH' ? '🟠' : '🟔';
973
- const title = sanitizeString(f.title || f.message || f.why || 'Unknown issue', 100);
974
- const category = sanitizeString(f.category || f.type || 'Other', 30);
975
- const file = sanitizeString(f.file || (f.evidence && f.evidence[0]?.file) || '', 150);
976
- const line = f.line || (f.evidence && f.evidence[0]?.lines) || '';
977
-
978
- output += `**${i + 1}. ${sevEmoji} [${sev}] ${title}**\n`;
979
- output += ` - Category: \`${category}\`\n`;
980
- if (file) {
981
- output += ` - File: \`${file}\`${line ? `:${line}` : ''}\n`;
982
- }
983
-
984
- // Include fix hints if available
985
- const fixHint = f.fixHints?.[0] || f.fixHint || f.fix;
986
- if (fixHint) {
987
- output += ` - **Fix:** ${sanitizeString(fixHint, 200)}\n`;
988
- }
989
- output += '\n';
990
- }
991
-
992
- if (findings.length > 15) {
993
- output += `_...and ${findings.length - 15} more issues. See full report for details._\n\n`;
994
- }
995
- }
689
+ // 15. BADGE - Generate ship badge
690
+ {
691
+ name: "vibecheck.badge",
692
+ description:
693
+ "šŸ… Ship Badge — generate a badge for README/PR showing scan status (STARTER tier)",
694
+ inputSchema: {
695
+ type: "object",
696
+ properties: {
697
+ projectPath: {
698
+ type: "string",
699
+ default: ".",
700
+ },
701
+ format: {
702
+ type: "string",
703
+ enum: ["svg", "md", "html"],
704
+ description: "Badge format",
705
+ default: "svg",
706
+ },
707
+ style: {
708
+ type: "string",
709
+ enum: ["flat", "flat-square"],
710
+ description: "Badge style",
711
+ default: "flat",
712
+ },
713
+ },
714
+ },
715
+ },
996
716
 
997
- // Next steps based on verdict
998
- output += "### Recommended Actions\n\n";
999
- if (verdict === 'SHIP') {
1000
- output += "- āœ… Code is clean and ready to ship\n";
1001
- output += "- Consider running `vibecheck ship --badge` to generate a status badge\n";
1002
- } else if (verdict === 'WARN') {
1003
- output += "- Review the warnings above before shipping\n";
1004
- output += "- Run `vibecheck fix --plan` to see fix suggestions\n";
1005
- output += "- Consider using `vibecheck fix --apply` (PRO) to auto-fix issues\n";
1006
- } else {
1007
- output += "- 🚫 Critical issues must be fixed before shipping\n";
1008
- output += "- Run `vibecheck fix --plan` to see detailed fix suggestions\n";
1009
- output += "- Address critical issues first, then re-scan\n";
1010
- }
1011
-
1012
- output += `\nšŸ“„ **Full Report:** ${CONFIG.OUTPUT_DIR}/report.html\n`;
1013
- output += `šŸ“Š **JSON Results:** ${CONFIG.OUTPUT_DIR}/results/latest.json\n`;
1014
-
1015
- return output;
1016
- }
1017
-
1018
- /**
1019
- * Safely read a file with size limits
1020
- * @param {string} filePath - Path to file
1021
- * @param {number} maxSize - Maximum file size in bytes
1022
- * @returns {Promise<string|null>} File contents or null
1023
- */
1024
- async safeReadFile(filePath, maxSize = 10 * 1024 * 1024) {
1025
- try {
1026
- const stats = await fs.stat(filePath);
1027
-
1028
- if (stats.size > maxSize) {
1029
- console.error(`[MCP] File too large: ${filePath} (${stats.size} bytes)`);
1030
- return null;
1031
- }
1032
-
1033
- const content = await fs.readFile(filePath, "utf-8");
1034
- return content;
1035
- } catch (err) {
1036
- return null;
1037
- }
1038
- }
717
+ // 16. CONTEXT - AI Rules Generator
718
+ {
719
+ name: "vibecheck.context",
720
+ description:
721
+ "🧠 AI Context — generate rules files for Cursor, Windsurf, Copilot to understand your codebase",
722
+ inputSchema: {
723
+ type: "object",
724
+ properties: {
725
+ projectPath: {
726
+ type: "string",
727
+ description: "Path to project root",
728
+ default: ".",
729
+ },
730
+ platform: {
731
+ type: "string",
732
+ enum: ["all", "cursor", "windsurf", "copilot", "claude"],
733
+ description: "Target platform (default: all)",
734
+ default: "all",
735
+ },
736
+ },
737
+ },
738
+ },
739
+ ];
1039
740
 
1040
- setupHandlers() {
1041
- // List tools
1042
- this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
1043
- tools: TOOLS,
1044
- }));
741
+ // ============================================================================
742
+ // SERVER IMPLEMENTATION
743
+ // ============================================================================
1045
744
 
1046
- // Call tool - main dispatch handler
1047
- this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
1048
- const startTime = Date.now();
1049
- let toolName = 'unknown';
1050
- let projectPath = '.';
1051
-
1052
- try {
1053
- // ====================================================================
1054
- // HARDENING: Input extraction with validation
1055
- // ====================================================================
1056
- const params = request?.params;
1057
- if (!params || typeof params !== 'object') {
1058
- return this.error('Invalid request: missing params', { code: 'INVALID_REQUEST' });
1059
- }
1060
-
1061
- toolName = sanitizeString(params.name, 100);
1062
- const args = params.arguments && typeof params.arguments === 'object' ? params.arguments : {};
1063
-
1064
- // Validate tool name
1065
- if (!toolName || toolName.length < 2) {
1066
- return this.error('Invalid tool name', { code: 'INVALID_TOOL_NAME' });
1067
- }
1068
-
1069
- // ====================================================================
1070
- // HARDENING: Rate limiting (per-API-key)
1071
- // ====================================================================
1072
- const apiKey = args?.apiKey || null;
1073
- const rateCheck = checkRateLimit(apiKey);
1074
- if (!rateCheck.allowed) {
1075
- return this.error(`Rate limit exceeded. Try again in ${Math.ceil(rateCheck.resetIn / 1000)} seconds`, {
1076
- code: 'RATE_LIMIT_EXCEEDED',
1077
- suggestion: 'Reduce the frequency of tool calls',
1078
- nextSteps: [`Wait ${Math.ceil(rateCheck.resetIn / 1000)} seconds before retrying`],
1079
- });
1080
- }
1081
-
1082
- // ====================================================================
1083
- // HARDENING: Project path validation
1084
- // ====================================================================
1085
- const rawProjectPath = args?.projectPath || '.';
1086
- const pathValidation = sanitizePath(rawProjectPath, process.cwd());
1087
-
1088
- if (!pathValidation.valid) {
1089
- return this.error(pathValidation.error, {
1090
- code: 'INVALID_PATH',
1091
- suggestion: 'Provide a valid path within the current working directory',
1092
- });
1093
- }
1094
- projectPath = pathValidation.path;
1095
-
1096
- // Emit audit event for tool invocation start
1097
- // SECURITY: Include apiKey hash for audit trail (never log raw key)
1098
- try {
1099
- const crypto = require('crypto');
1100
- const apiKeyHash = apiKey
1101
- ? crypto.createHash('sha256').update(apiKey).digest('hex').slice(0, 16)
1102
- : 'anonymous';
1103
-
1104
- emitToolInvoke(toolName, args, "success", {
1105
- projectPath,
1106
- apiKeyHash,
1107
- rateLimit: rateCheck,
1108
- timestamp: new Date().toISOString(),
1109
- });
1110
- } catch {
1111
- // Audit logging should never break the tool
1112
- }
745
+ class VibecheckMCP {
746
+ constructor() {
747
+ this.server = new Server(
748
+ { name: "vibecheck", version: VERSION },
749
+ { capabilities: { tools: {}, resources: {} } },
750
+ );
751
+ this.setupHandlers();
752
+ }
1113
753
 
1114
- // ====================================================================
1115
- // HARDENING: Truth firewall check with error handling
1116
- // ====================================================================
1117
- let firewallCheck = { blocked: false };
1118
- try {
1119
- firewallCheck = checkTruthFirewallBlock(toolName, args, projectPath);
1120
- } catch (firewallError) {
1121
- console.error(`[MCP] Firewall check error: ${firewallError.message}`);
1122
- // Continue - don't block on firewall errors
1123
- }
1124
-
1125
- if (firewallCheck.blocked) {
1126
- const policy = getTruthPolicy(args);
1127
- try {
1128
- await emitGuardrailMetric(projectPath, {
1129
- event: "truth_firewall_block",
1130
- tool: toolName,
1131
- policy,
1132
- reason: firewallCheck.code || "no_recent_claim_validation",
1133
- });
1134
- } catch {
1135
- // Metrics should never break the tool
1136
- }
1137
- return this.error(firewallCheck.reason, {
1138
- code: firewallCheck.code,
1139
- suggestion: firewallCheck.suggestion,
1140
- nextSteps: firewallCheck.nextSteps || [],
754
+ setupHandlers() {
755
+ // List tools
756
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
757
+ tools: TOOLS,
758
+ }));
759
+
760
+ // Call tool
761
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
762
+ const { name, arguments: args } = request.params;
763
+ const projectPath = path.resolve(args?.projectPath || ".");
764
+ const startTime = Date.now();
765
+
766
+ // Emit audit event for tool invocation start
767
+ emitToolInvoke(name, args, "success", { projectPath });
768
+
769
+ try {
770
+ const policy = getTruthPolicy(args);
771
+ if (
772
+ STRICT_GUARDRAIL_TOOLS.has(name) &&
773
+ policy !== "permissive" &&
774
+ !hasRecentClaimValidation(projectPath)
775
+ ) {
776
+ await emitGuardrailMetric(projectPath, {
777
+ event: "truth_firewall_block",
778
+ tool: name,
779
+ policy,
780
+ reason: "no_recent_claim_validation",
1141
781
  });
782
+ return this.error(
783
+ "Truth firewall requires claim validation before high-impact tools.",
784
+ {
785
+ code: "TRUTH_FIREWALL_REQUIRED",
786
+ suggestion: "Call vibecheck.validate_claim or vibecheck.get_truthpack before proceeding.",
787
+ nextSteps: [
788
+ "Call vibecheck.get_truthpack with refresh=true for current evidence",
789
+ "Call vibecheck.validate_claim for critical assumptions",
790
+ "Re-run the tool after validation",
791
+ ],
792
+ },
793
+ );
1142
794
  }
1143
-
1144
- // Handle v3 tools (10 consolidated tools, STARTER+ only)
1145
- if (USE_V3_TOOLS && V3_TOOL_TIERS[toolName]) {
1146
- // SECURITY FIX: Never trust client-provided tier - validate from API key
1147
- // Previous: const userTier = sanitizeString(args?.tier, 20) || ...
1148
- // This allowed privilege escalation via args.tier = "pro"
1149
- const { getMcpToolAccess } = await import('./tier-auth.js');
1150
- const access = await getMcpToolAccess(toolName, apiKey);
1151
- const userTier = access.tier || 'free';
1152
- const result = await handleToolV3(toolName, args, { tier: userTier });
1153
-
1154
- if (result.error) {
1155
- return this.error(result.error, { tier: result.tier, required: result.required });
1156
- }
1157
-
1158
- // Sanitize and truncate output
1159
- const outputText = truncateOutput(redactSensitive(JSON.stringify(result, null, 2)));
1160
- return {
1161
- content: [{ type: "text", text: outputText }],
1162
- };
1163
- }
1164
-
1165
- // 1. Check tool registry first (local CLI handlers)
1166
- if (this.toolRegistry[toolName]) {
1167
- return await this.toolRegistry[toolName](projectPath, args);
1168
- }
1169
-
1170
- // 2. Handle external module tools by prefix/pattern
1171
- if (toolName.startsWith("vibecheck.intelligence.")) {
1172
- return await handleIntelligenceTool(toolName, args, __dirname);
795
+ // Handle intelligence tools first
796
+ if (name.startsWith("vibecheck.intelligence.")) {
797
+ return await handleIntelligenceTool(name, args, __dirname);
1173
798
  }
1174
799
 
1175
- // Handle AI vibecheck tools
800
+ // Handle AI vibecheck tools (verify, quality, smells, hallucination, breaking, mdc, coverage)
1176
801
  if (["vibecheck.verify", "vibecheck.quality", "vibecheck.smells",
1177
802
  "vibecheck.hallucination", "vibecheck.breaking", "vibecheck.mdc",
1178
- "vibecheck.coverage"].includes(toolName)) {
1179
- const result = await handleVibecheckTool(toolName, args);
1180
- const outputText = truncateOutput(redactSensitive(JSON.stringify(result, null, 2)));
803
+ "vibecheck.coverage"].includes(name)) {
804
+ const result = await handleVibecheckTool(name, args);
1181
805
  return {
1182
- content: [{ type: "text", text: outputText }],
806
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
1183
807
  };
1184
808
  }
1185
809
 
1186
810
  // Handle agent checkpoint tools
1187
- if (["vibecheck_checkpoint", "vibecheck_set_strictness", "vibecheck_checkpoint_status"].includes(toolName)) {
1188
- return await handleCheckpointTool(toolName, args);
811
+ if (["vibecheck_checkpoint", "vibecheck_set_strictness", "vibecheck_checkpoint_status"].includes(name)) {
812
+ return await handleCheckpointTool(name, args);
1189
813
  }
1190
814
 
1191
815
  // Handle architect tools
1192
816
  if (["vibecheck_architect_review", "vibecheck_architect_suggest",
1193
- "vibecheck_architect_patterns", "vibecheck_architect_set_strictness"].includes(toolName)) {
1194
- return await handleArchitectTool(toolName, args);
1195
- }
1196
-
1197
- // Handle authority system tools
1198
- if (["authority.classify", "authority.approve", "authority.list"].includes(toolName)) {
1199
- const result = await handleAuthorityTool(toolName, args, userTier || "free");
1200
- const outputText = truncateOutput(redactSensitive(JSON.stringify(result, null, 2)));
1201
- return {
1202
- content: [{ type: "text", text: outputText }],
1203
- };
817
+ "vibecheck_architect_patterns", "vibecheck_architect_set_strictness"].includes(name)) {
818
+ return await handleArchitectTool(name, args);
1204
819
  }
1205
820
 
1206
821
  // Handle codebase architect tools
1207
822
  if (["vibecheck_architect_context", "vibecheck_architect_guide",
1208
823
  "vibecheck_architect_validate", "vibecheck_architect_patterns",
1209
- "vibecheck_architect_dependencies"].includes(toolName)) {
1210
- return await handleCodebaseArchitectTool(toolName, args);
824
+ "vibecheck_architect_dependencies"].includes(name)) {
825
+ return await handleCodebaseArchitectTool(name, args);
1211
826
  }
1212
827
 
1213
828
  // Handle vibecheck 2.0 tools
1214
- if (["checkpoint", "check", "ship", "fix", "status", "set_strictness"].includes(toolName)) {
1215
- return await handleVibecheck2Tool(toolName, args, __dirname);
829
+ if (["checkpoint", "check", "ship", "fix", "status", "set_strictness"].includes(name)) {
830
+ return await handleVibecheck2Tool(name, args, __dirname);
1216
831
  }
1217
832
 
1218
833
  // Handle intent drift tools
1219
- if (toolName.startsWith("vibecheck_intent_")) {
1220
- const tool = intentDriftTools.find(t => t.name === toolName);
834
+ if (name.startsWith("vibecheck_intent_")) {
835
+ const tool = intentDriftTools.find(t => t.name === name);
1221
836
  if (tool && tool.handler) {
1222
837
  const result = await tool.handler(args);
1223
- const outputText = truncateOutput(redactSensitive(JSON.stringify(result, null, 2)));
1224
838
  return {
1225
- content: [{ type: "text", text: outputText }],
839
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
1226
840
  };
1227
841
  }
1228
842
  }
1229
843
 
1230
- // Handle MDC generator
1231
- if (toolName === "generate_mdc") {
1232
- return await handleMDCGeneration(args);
1233
- }
1234
-
1235
- // Handle Truth Context tools (Evidence Pack / Truth Pack)
1236
- if (["vibecheck.verify_claim", "vibecheck.evidence"].includes(toolName)) {
1237
- return await handleTruthContextTool(toolName, args);
1238
- }
1239
-
1240
- // Handle Truth Firewall tools (Hallucination Stopper)
1241
- if (["vibecheck.get_truthpack", "vibecheck.validate_claim", "vibecheck.compile_context",
1242
- "vibecheck.search_evidence", "vibecheck.find_counterexamples", "vibecheck.propose_patch",
1243
- "vibecheck.check_invariants", "vibecheck.add_assumption"].includes(toolName)) {
1244
- return await handleTruthFirewallTool(toolName, args, projectPath);
844
+ switch (name) {
845
+ case "vibecheck.ship":
846
+ return await this.handleShip(projectPath, args);
847
+ case "vibecheck.scan":
848
+ return await this.handleScan(projectPath, args);
849
+ case "vibecheck.verify":
850
+ return await this.handleVerify(projectPath, args);
851
+ case "vibecheck.reality":
852
+ return await this.handleReality(projectPath, args);
853
+ case "vibecheckai.dev-test":
854
+ return await this.handleAITest(projectPath, args);
855
+ case "vibecheck.gate":
856
+ return await this.handleGate(projectPath, args);
857
+ case "vibecheck.fix":
858
+ return await this.handleFix(projectPath, args);
859
+ case "vibecheck.share":
860
+ return await this.handleShare(projectPath, args);
861
+ case "vibecheck.ctx":
862
+ return await this.handleCtx(projectPath, args);
863
+ case "vibecheck.prove":
864
+ return await this.handleProve(projectPath, args);
865
+ case "vibecheck.proof":
866
+ return await this.handleProof(projectPath, args);
867
+ case "vibecheck.validate":
868
+ return await this.handleValidate(projectPath, args);
869
+ case "vibecheck.report":
870
+ return await this.handleReport(projectPath, args);
871
+ case "vibecheck.status":
872
+ return await this.handleStatus(projectPath, args);
873
+ case "vibecheck.autopilot":
874
+ return await this.handleAutopilot(projectPath, args);
875
+ case "vibecheck.autopilot_plan":
876
+ return await this.handleAutopilotPlan(projectPath, args);
877
+ case "vibecheck.autopilot_apply":
878
+ return await this.handleAutopilotApply(projectPath, args);
879
+ case "vibecheck.badge":
880
+ return await this.handleBadge(projectPath, args);
881
+ case "vibecheck.context":
882
+ return await this.handleContext(projectPath, args);
883
+ case "generate_mdc":
884
+ return await handleMDCGeneration(args);
885
+ // Truth Context tools (Evidence Pack / Truth Pack)
886
+ case "vibecheck.verify_claim":
887
+ case "vibecheck.evidence":
888
+ return await handleTruthContextTool(name, args);
889
+ // Truth Firewall tools (Hallucination Stopper)
890
+ case "vibecheck.get_truthpack":
891
+ case "vibecheck.validate_claim":
892
+ case "vibecheck.compile_context":
893
+ case "vibecheck.search_evidence":
894
+ case "vibecheck.find_counterexamples":
895
+ case "vibecheck.propose_patch":
896
+ case "vibecheck.check_invariants":
897
+ case "vibecheck.add_assumption":
898
+ return await handleTruthFirewallTool(name, args, projectPath);
899
+ default:
900
+ return this.error(`Unknown tool: ${name}`);
1245
901
  }
1246
-
1247
- return this.error(`Unknown tool: ${toolName}`, {
1248
- code: 'UNKNOWN_TOOL',
1249
- suggestion: 'Check the tool name and try again',
1250
- nextSteps: ['Use vibecheck.status to see available tools'],
1251
- });
1252
902
  } catch (err) {
1253
- // ====================================================================
1254
- // HARDENING: Enhanced error handling with sanitization
1255
- // ====================================================================
1256
- const durationMs = Date.now() - startTime;
1257
- const errorMessage = sanitizeString(err?.message || 'Unknown error', 500);
1258
-
1259
- // Emit audit event for tool error (safely)
1260
- try {
1261
- emitToolComplete(toolName, "error", {
1262
- errorMessage: redactSensitive(errorMessage),
1263
- durationMs,
1264
- });
1265
- } catch {
1266
- // Audit logging should never break the response
1267
- }
1268
-
1269
- // Log error details to stderr (not stdout - preserves MCP protocol)
1270
- console.error(`[MCP] Tool ${toolName} failed after ${durationMs}ms: ${errorMessage}`);
1271
-
1272
- return this.error(`${toolName} failed: ${redactSensitive(errorMessage)}`, {
1273
- code: err?.code || 'TOOL_ERROR',
1274
- suggestion: 'Check the error message and try again',
1275
- nextSteps: [
1276
- 'Verify the tool arguments are correct',
1277
- 'Check that the project path is valid',
1278
- 'Try running with simpler arguments first',
1279
- ],
903
+ // Emit audit event for tool error
904
+ emitToolComplete(name, "error", {
905
+ errorMessage: err.message,
906
+ durationMs: Date.now() - startTime
1280
907
  });
908
+ return this.error(`${name} failed: ${err.message}`);
1281
909
  }
1282
910
  });
1283
911
 
1284
912
  // Resources
1285
913
  this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
1286
914
  resources: [
1287
- {
1288
- uri: "vibecheck://status",
1289
- name: "Server Status",
1290
- description: "Current MCP server health, version, and rate limits",
1291
- mimeType: "application/json",
1292
- },
1293
915
  {
1294
916
  uri: "vibecheck://config",
1295
917
  name: "vibecheck Config",
@@ -1338,53 +960,21 @@ class VibecheckMCP {
1338
960
  description: "Last prove loop results (ctx → reality → ship → fix)",
1339
961
  mimeType: "application/json",
1340
962
  },
1341
- {
1342
- uri: "vibecheck://registry",
1343
- name: "Command Registry",
1344
- description: "Available commands and their tiers",
1345
- mimeType: "application/json",
1346
- },
1347
963
  ],
1348
964
  }));
1349
965
 
1350
966
  this.server.setRequestHandler(
1351
967
  ReadResourceRequestSchema,
1352
968
  async (request) => {
1353
- // ====================================================================
1354
- // HARDENING: Resource request validation
1355
- // ====================================================================
1356
- const uri = sanitizeString(request?.params?.uri, 200);
1357
- if (!uri || !uri.startsWith('vibecheck://')) {
1358
- return { contents: [{ uri: uri || '', mimeType: "application/json", text: '{"error": "Invalid resource URI"}' }] };
1359
- }
1360
-
969
+ const { uri } = request.params;
1361
970
  const projectPath = process.cwd();
1362
-
1363
- // Helper to safely read and return JSON resource
1364
- const safeReadResource = async (filePath, defaultMessage) => {
1365
- try {
1366
- const content = await fs.readFile(filePath, "utf-8");
1367
- // Validate JSON and sanitize
1368
- const parsed = safeJsonParse(content);
1369
- if (!parsed.success) {
1370
- return { contents: [{ uri, mimeType: "application/json", text: JSON.stringify({ error: "Invalid JSON in resource file" }) }] };
1371
- }
1372
- // Redact any sensitive data and truncate
1373
- const sanitized = redactSensitive(truncateOutput(content, CONFIG.LIMITS.MAX_OUTPUT_LENGTH));
1374
- return { contents: [{ uri, mimeType: "application/json", text: sanitized }] };
1375
- } catch {
1376
- return { contents: [{ uri, mimeType: "application/json", text: JSON.stringify({ message: defaultMessage }) }] };
1377
- }
1378
- };
1379
971
 
1380
972
  if (uri === "vibecheck://config") {
1381
973
  const configPath = path.join(projectPath, "vibecheck.config.json");
1382
974
  try {
1383
975
  const content = await fs.readFile(configPath, "utf-8");
1384
- // Redact sensitive config values
1385
- const sanitized = redactSensitive(truncateOutput(content, CONFIG.LIMITS.MAX_OUTPUT_LENGTH));
1386
976
  return {
1387
- contents: [{ uri, mimeType: "application/json", text: sanitized }],
977
+ contents: [{ uri, mimeType: "application/json", text: content }],
1388
978
  };
1389
979
  } catch {
1390
980
  return {
@@ -1394,237 +984,138 @@ class VibecheckMCP {
1394
984
  }
1395
985
 
1396
986
  if (uri === "vibecheck://summary") {
1397
- const summaryPath = path.join(projectPath, ".vibecheck", "summary.json");
1398
- return await safeReadResource(summaryPath, "No scan found. Run vibecheck.scan first.");
987
+ const summaryPath = path.join(
988
+ projectPath,
989
+ ".vibecheck",
990
+ "summary.json",
991
+ );
992
+ try {
993
+ const content = await fs.readFile(summaryPath, "utf-8");
994
+ return {
995
+ contents: [{ uri, mimeType: "application/json", text: content }],
996
+ };
997
+ } catch {
998
+ return {
999
+ contents: [
1000
+ {
1001
+ uri,
1002
+ mimeType: "application/json",
1003
+ text: '{"message": "No scan found. Run vibecheck.scan first."}',
1004
+ },
1005
+ ],
1006
+ };
1007
+ }
1399
1008
  }
1400
1009
 
1401
1010
  if (uri === "vibecheck://truthpack") {
1402
1011
  const truthpackPath = path.join(projectPath, ".vibecheck", "truth", "truthpack.json");
1403
- return await safeReadResource(truthpackPath, "No truthpack. Run vibecheck.ctx first.");
1012
+ try {
1013
+ const content = await fs.readFile(truthpackPath, "utf-8");
1014
+ return { contents: [{ uri, mimeType: "application/json", text: content }] };
1015
+ } catch {
1016
+ return { contents: [{ uri, mimeType: "application/json", text: '{"message": "No truthpack. Run vibecheck.ctx first."}' }] };
1017
+ }
1404
1018
  }
1405
1019
 
1406
1020
  if (uri === "vibecheck://missions") {
1407
1021
  const missionsDir = path.join(projectPath, ".vibecheck", "missions");
1408
1022
  try {
1409
- // HARDENING: Validate directory read
1410
1023
  const dirs = await fs.readdir(missionsDir);
1411
- const safeDirs = sanitizeArray(dirs, 100).filter(d => typeof d === 'string' && d.length > 0);
1412
- const latest = safeDirs.sort().reverse()[0];
1413
-
1024
+ const latest = dirs.sort().reverse()[0];
1414
1025
  if (latest) {
1415
1026
  const missionPath = path.join(missionsDir, latest, "missions.json");
1416
- return await safeReadResource(missionPath, "No missions found in latest directory.");
1027
+ const content = await fs.readFile(missionPath, "utf-8");
1028
+ return { contents: [{ uri, mimeType: "application/json", text: content }] };
1417
1029
  }
1418
- } catch (err) {
1419
- console.error(`[MCP] Error reading missions: ${err.message}`);
1420
- }
1030
+ } catch {}
1421
1031
  return { contents: [{ uri, mimeType: "application/json", text: '{"message": "No missions. Run vibecheck.fix first."}' }] };
1422
1032
  }
1423
1033
 
1424
1034
  if (uri === "vibecheck://reality") {
1425
1035
  const realityPath = path.join(projectPath, ".vibecheck", "reality", "last_reality.json");
1426
- return await safeReadResource(realityPath, "No reality results. Run vibecheck verify first.");
1036
+ try {
1037
+ const content = await fs.readFile(realityPath, "utf-8");
1038
+ return { contents: [{ uri, mimeType: "application/json", text: content }] };
1039
+ } catch {
1040
+ return { contents: [{ uri, mimeType: "application/json", text: '{"message": "No reality results. Run vibecheck verify first."}' }] };
1041
+ }
1427
1042
  }
1428
1043
 
1429
1044
  if (uri === "vibecheck://findings") {
1430
1045
  const findingsPath = path.join(projectPath, ".vibecheck", "findings.json");
1431
-
1432
- // Try primary findings file
1433
- const content = await this.safeReadFile(findingsPath, 10 * 1024 * 1024);
1434
- if (content) {
1435
- const parsed = safeJsonParse(content);
1436
- if (parsed.success) {
1437
- const sanitized = redactSensitive(truncateOutput(content));
1438
- return { contents: [{ uri, mimeType: "application/json", text: sanitized }] };
1439
- }
1440
- }
1441
-
1442
- // HARDENING: Try summary.json as fallback with size limits
1443
- const summaryPath = path.join(projectPath, ".vibecheck", "summary.json");
1444
- const summaryContent = await this.safeReadFile(summaryPath, 10 * 1024 * 1024);
1445
-
1446
- if (summaryContent) {
1447
- const parsed = safeJsonParse(summaryContent);
1448
- if (parsed.success && parsed.data.findings) {
1449
- const findings = sanitizeArray(parsed.data.findings, 1000);
1046
+ try {
1047
+ const content = await fs.readFile(findingsPath, "utf-8");
1048
+ return { contents: [{ uri, mimeType: "application/json", text: content }] };
1049
+ } catch {
1050
+ // Try summary.json as fallback
1051
+ const summaryPath = path.join(projectPath, ".vibecheck", "summary.json");
1052
+ try {
1053
+ const summary = JSON.parse(await fs.readFile(summaryPath, "utf-8"));
1054
+ const findings = summary.findings || [];
1450
1055
  return { contents: [{ uri, mimeType: "application/json", text: JSON.stringify({ findings }, null, 2) }] };
1056
+ } catch {
1057
+ return { contents: [{ uri, mimeType: "application/json", text: '{"message": "No findings. Run vibecheck.scan first."}' }] };
1451
1058
  }
1452
1059
  }
1453
-
1454
- return { contents: [{ uri, mimeType: "application/json", text: '{"message": "No findings. Run vibecheck.scan first."}' }] };
1455
1060
  }
1456
1061
 
1457
1062
  if (uri === "vibecheck://share") {
1458
1063
  const missionsDir = path.join(projectPath, ".vibecheck", "missions");
1459
1064
  try {
1460
- // HARDENING: Safe directory read
1461
1065
  const dirs = await fs.readdir(missionsDir);
1462
- const safeDirs = sanitizeArray(dirs, 100).filter(d => typeof d === 'string' && d.length > 0);
1463
- const latest = safeDirs.sort().reverse()[0];
1464
-
1066
+ const latest = dirs.sort().reverse()[0];
1465
1067
  if (latest) {
1466
1068
  const sharePath = path.join(missionsDir, latest, "share", "share.json");
1467
-
1468
- // Try share.json first
1469
- const shareContent = await this.safeReadFile(sharePath, 10 * 1024 * 1024);
1470
- if (shareContent) {
1471
- const parsed = safeJsonParse(shareContent);
1472
- if (parsed.success) {
1473
- const sanitized = redactSensitive(truncateOutput(shareContent));
1474
- return { contents: [{ uri, mimeType: "application/json", text: sanitized }] };
1475
- }
1069
+ try {
1070
+ const content = await fs.readFile(sharePath, "utf-8");
1071
+ return { contents: [{ uri, mimeType: "application/json", text: content }] };
1072
+ } catch {
1073
+ // Fallback to missions.json
1074
+ const missionPath = path.join(missionsDir, latest, "missions.json");
1075
+ const content = await fs.readFile(missionPath, "utf-8");
1076
+ return { contents: [{ uri, mimeType: "application/json", text: content }] };
1476
1077
  }
1477
-
1478
- // HARDENING: Fallback to missions.json with safe read
1479
- const missionPath = path.join(missionsDir, latest, "missions.json");
1480
- return await safeReadResource(missionPath, "No share data available in latest mission.");
1481
1078
  }
1482
- } catch (err) {
1483
- console.error(`[MCP] Error reading share pack: ${err.message}`);
1484
- }
1079
+ } catch {}
1485
1080
  return { contents: [{ uri, mimeType: "application/json", text: '{"message": "No share pack. Run vibecheck.fix --share first."}' }] };
1486
1081
  }
1487
1082
 
1488
1083
  if (uri === "vibecheck://prove") {
1489
1084
  const provePath = path.join(projectPath, ".vibecheck", "prove", "last_prove.json");
1490
- return await safeReadResource(provePath, "No prove results. Run vibecheck.prove first.");
1491
- }
1492
-
1493
- // Server status resource - live health check
1494
- if (uri === "vibecheck://status") {
1495
- const rateCheck = checkRateLimit(null);
1496
- const circuitCheck = checkCircuitBreaker();
1497
-
1498
- const status = {
1499
- server: {
1500
- version: CONFIG.VERSION,
1501
- uptime: Math.floor(process.uptime()),
1502
- transport: 'stdio',
1503
- nodeVersion: process.version,
1504
- },
1505
- health: {
1506
- status: circuitCheck.allowed ? 'healthy' : 'degraded',
1507
- circuitBreaker: circuitBreakerState.state,
1508
- failureCount: circuitBreakerState.failures,
1509
- },
1510
- rateLimit: {
1511
- remaining: rateCheck.remaining,
1512
- resetIn: rateCheck.resetIn || 0,
1513
- limit: CONFIG.LIMITS.RATE_LIMIT_MAX_CALLS,
1514
- window: CONFIG.LIMITS.RATE_LIMIT_WINDOW_MS,
1515
- },
1516
- features: {
1517
- hardening: true,
1518
- auditLogging: true,
1519
- secretRedaction: true,
1520
- pathSecurity: true,
1521
- },
1522
- timestamp: new Date().toISOString(),
1523
- };
1524
-
1525
- return {
1526
- contents: [{ uri, mimeType: "application/json", text: JSON.stringify(status, null, 2) }],
1527
- };
1528
- }
1529
-
1530
- // Registry resource - available commands and tiers
1531
- if (uri === "vibecheck://registry") {
1532
- const registry = {
1533
- version: CONFIG.VERSION,
1534
- tiers: {
1535
- free: {
1536
- name: 'FREE',
1537
- price: '$0',
1538
- description: 'Inspect & Observe',
1539
- },
1540
- pro: {
1541
- name: 'PRO',
1542
- price: '$69/mo',
1543
- description: 'Fix, Prove & Enforce',
1544
- },
1545
- },
1546
- tools: Object.entries(V3_TOOL_TIERS).map(([name, tier]) => ({
1547
- name,
1548
- tier,
1549
- })),
1550
- resources: [
1551
- 'vibecheck://status',
1552
- 'vibecheck://config',
1553
- 'vibecheck://summary',
1554
- 'vibecheck://truthpack',
1555
- 'vibecheck://missions',
1556
- 'vibecheck://reality',
1557
- 'vibecheck://findings',
1558
- 'vibecheck://share',
1559
- 'vibecheck://prove',
1560
- 'vibecheck://registry',
1561
- ],
1562
- };
1563
-
1564
- return {
1565
- contents: [{ uri, mimeType: "application/json", text: JSON.stringify(registry, null, 2) }],
1566
- };
1085
+ try {
1086
+ const content = await fs.readFile(provePath, "utf-8");
1087
+ return { contents: [{ uri, mimeType: "application/json", text: content }] };
1088
+ } catch {
1089
+ return { contents: [{ uri, mimeType: "application/json", text: '{"message": "No prove results. Run vibecheck.prove first."}' }] };
1090
+ }
1567
1091
  }
1568
1092
 
1569
- return {
1570
- contents: [{
1571
- uri,
1572
- mimeType: "application/json",
1573
- text: JSON.stringify({ error: "Unknown resource URI" })
1574
- }]
1575
- };
1093
+ return { contents: [] };
1576
1094
  },
1577
1095
  );
1578
1096
  }
1579
1097
 
1580
- // ============================================================================
1581
- // HARDENED HELPERS
1582
- // ============================================================================
1583
-
1584
- /**
1585
- * Return a successful response with sanitization
1586
- * @param {string} text - Response text
1587
- * @param {boolean} includeAttribution - Include vibecheck attribution
1588
- * @returns {object} MCP response
1589
- */
1590
- success(text, includeAttribution = true) {
1591
- // Sanitize output: redact secrets and truncate
1592
- let sanitized = redactSensitive(sanitizeString(text, CONFIG.LIMITS.MAX_OUTPUT_LENGTH));
1593
-
1594
- const finalText = includeAttribution
1595
- ? `${sanitized}\n\n---\n_${CONTEXT_ATTRIBUTION}_`
1596
- : sanitized;
1597
-
1598
- return { content: [{ type: "text", text: finalText }] };
1098
+ // Helpers
1099
+ success(text) {
1100
+ return { content: [{ type: "text", text }] };
1599
1101
  }
1600
1102
 
1601
- /**
1602
- * Return an error response with sanitization
1603
- * @param {string} text - Error message
1604
- * @param {object} options - Additional options
1605
- * @returns {object} MCP error response
1606
- */
1607
1103
  error(text, options = {}) {
1608
1104
  const { code, suggestion, nextSteps = [] } = options;
1609
1105
 
1610
- // Sanitize all text inputs
1611
- const sanitizedText = redactSensitive(sanitizeString(text, 1000));
1612
- const sanitizedSuggestion = suggestion ? redactSensitive(sanitizeString(suggestion, 500)) : null;
1613
- const sanitizedSteps = sanitizeArray(nextSteps, 10).map(s => sanitizeString(s, 200));
1614
-
1615
- let errorText = `āŒ ${sanitizedText}`;
1106
+ let errorText = `āŒ ${text}`;
1616
1107
 
1617
1108
  if (code) {
1618
- errorText += `\n\n**Error Code:** \`${sanitizeString(code, 50)}\``;
1109
+ errorText += `\n\n**Error Code:** \`${code}\``;
1619
1110
  }
1620
1111
 
1621
- if (sanitizedSuggestion) {
1622
- errorText += `\n\nšŸ’” **Suggestion:** ${sanitizedSuggestion}`;
1112
+ if (suggestion) {
1113
+ errorText += `\n\nšŸ’” **Suggestion:** ${suggestion}`;
1623
1114
  }
1624
1115
 
1625
- if (sanitizedSteps.length > 0) {
1116
+ if (nextSteps.length > 0) {
1626
1117
  errorText += `\n\n**Next Steps:**\n`;
1627
- sanitizedSteps.forEach((step, i) => {
1118
+ nextSteps.forEach((step, i) => {
1628
1119
  errorText += `${i + 1}. ${step}\n`;
1629
1120
  });
1630
1121
  }
@@ -1632,10 +1123,10 @@ class VibecheckMCP {
1632
1123
  return { content: [{ type: "text", text: errorText }], isError: true };
1633
1124
  }
1634
1125
 
1635
- // Validate project path exists and is accessible (sync for simple validation)
1126
+ // Validate project path exists and is accessible
1636
1127
  validateProjectPath(projectPath) {
1637
1128
  try {
1638
- const stats = fsSync.statSync(projectPath);
1129
+ const stats = require("fs").statSync(projectPath);
1639
1130
  if (!stats.isDirectory()) {
1640
1131
  return {
1641
1132
  valid: false,
@@ -1677,157 +1168,47 @@ class VibecheckMCP {
1677
1168
  });
1678
1169
  }
1679
1170
 
1680
- // HARDENING: Validate and sanitize profile
1681
- const validProfiles = ["quick", "full", "ship", "ci", "security", "compliance", "ai"];
1682
- const profile = validProfiles.includes(args?.profile) ? args?.profile : "quick";
1683
-
1684
- // HARDENING: Sanitize only array
1685
- const only = sanitizeArray(args?.only, 20).map(item => sanitizeString(item, 50));
1686
-
1687
- // Initialize API integration with timeout and circuit breaker
1688
- let apiScan = null;
1689
- let apiConnected = false;
1690
-
1691
- // HARDENING: Check circuit breaker before attempting API calls
1692
- const circuitCheck = checkCircuitBreaker();
1693
-
1694
- // Try to connect to API for dashboard integration
1695
- if (circuitCheck.allowed) {
1696
- try {
1697
- // HARDENING: Add timeout to API availability check
1698
- const apiCheckPromise = isApiAvailable();
1699
- const timeoutPromise = new Promise((_, reject) =>
1700
- setTimeout(() => reject(new Error('API check timeout')), 5000)
1701
- );
1702
-
1703
- apiConnected = await Promise.race([apiCheckPromise, timeoutPromise]);
1704
-
1705
- if (apiConnected) {
1706
- // Create scan record in dashboard
1707
- const createScanPromise = createScan({
1708
- localPath: sanitizeString(projectPath, 500),
1709
- branch: sanitizeString(args?.branch, 100) || 'main',
1710
- enableLLM: false,
1711
- });
1712
- const scanTimeoutPromise = new Promise((_, reject) =>
1713
- setTimeout(() => reject(new Error('Create scan timeout')), 10000)
1714
- );
1715
-
1716
- apiScan = await Promise.race([createScanPromise, scanTimeoutPromise]);
1717
- console.error(`[MCP] Connected to dashboard (Scan ID: ${apiScan.scanId})`);
1718
- recordApiResult(true); // Record success
1719
- }
1720
- } catch (err) {
1721
- // API connection is optional, continue without it
1722
- console.error(`[MCP] Dashboard integration unavailable: ${err.message}`);
1723
- recordApiResult(false); // Record failure
1724
- }
1725
- } else {
1726
- console.error(`[MCP] ${circuitCheck.reason}`);
1727
- }
1171
+ const profile = args?.profile || "quick";
1172
+ const format = args?.format || "text";
1173
+ const only = args?.only;
1728
1174
 
1729
1175
  let output = "# šŸ” vibecheck Scan\n\n";
1730
1176
  output += `**Profile:** ${profile}\n`;
1731
1177
  output += `**Path:** ${projectPath}\n\n`;
1732
1178
 
1733
- // Build CLI arguments array (secure - no injection possible)
1734
- const cliArgs = [`--profile=${profile}`, "--json"];
1735
- if (only.length > 0) {
1736
- cliArgs.push(`--only=${only.join(",")}`);
1737
- }
1738
-
1739
1179
  try {
1740
- await this.runCLI("scan", cliArgs, projectPath, { timeout: CONFIG.TIMEOUTS.SCAN });
1180
+ // Build CLI command
1181
+ let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" scan`;
1182
+ cmd += ` --profile=${profile}`;
1183
+ if (only?.length) cmd += ` --only=${only.join(",")}`;
1184
+ cmd += ` --json`;
1185
+
1186
+ const result = execSync(cmd, {
1187
+ cwd: projectPath,
1188
+ encoding: "utf8",
1189
+ maxBuffer: 10 * 1024 * 1024,
1190
+ });
1741
1191
 
1742
- // Read summary from disk
1743
- const summary = await this.parseSummaryFromDisk(projectPath);
1744
- if (summary) {
1745
- output += this.formatScanOutput(summary, projectPath);
1746
-
1747
- // Submit results to dashboard if connected
1748
- if (apiConnected && apiScan) {
1749
- try {
1750
- // HARDENING: Add timeout to result submission
1751
- const submitPromise = submitScanResults(apiScan.scanId, {
1752
- verdict: sanitizeString(summary.verdict, 50) || 'UNKNOWN',
1753
- score: sanitizeNumber(summary.score?.overall, 0, 100, 0),
1754
- findings: sanitizeArray(summary.findings, 1000) || [],
1755
- filesScanned: sanitizeNumber(summary.stats?.filesScanned, 0, 1000000, 0),
1756
- linesScanned: sanitizeNumber(summary.stats?.linesScanned, 0, 100000000, 0),
1757
- durationMs: sanitizeNumber(summary.timings?.total, 0, 3600000, 0),
1758
- metadata: {
1759
- profile,
1760
- source: 'mcp-server',
1761
- version: CONFIG.VERSION,
1762
- },
1763
- });
1764
- const submitTimeout = new Promise((_, reject) =>
1765
- setTimeout(() => reject(new Error('Submit timeout')), 10000)
1766
- );
1767
-
1768
- await Promise.race([submitPromise, submitTimeout]);
1769
- console.error(`[MCP] Results sent to dashboard`);
1770
- } catch (err) {
1771
- console.error(`[MCP] Failed to send results to dashboard: ${err.message}`);
1772
- }
1192
+ // Read summary
1193
+ const summaryPath = path.join(projectPath, ".vibecheck", "summary.json");
1194
+ const summary = JSON.parse(await fs.readFile(summaryPath, "utf-8"));
1195
+
1196
+ output += `## Score: ${summary.score}/100 (${summary.grade})\n\n`;
1197
+ output += `**Verdict:** ${summary.canShip ? "āœ… SHIP" : "🚫 NO-SHIP"}\n\n`;
1198
+
1199
+ if (summary.counts) {
1200
+ output += "### Checks\n\n";
1201
+ output += "| Category | Issues |\n|----------|--------|\n";
1202
+ for (const [key, count] of Object.entries(summary.counts)) {
1203
+ const icon = count === 0 ? "āœ…" : "āš ļø";
1204
+ output += `| ${icon} ${key} | ${count} |\n`;
1773
1205
  }
1774
1206
  }
1207
+
1208
+ output += `\nšŸ“„ **Report:** .vibecheck/report.html\n`;
1775
1209
  output += "\n---\n_Context Enhanced by vibecheck AI_\n";
1776
1210
  return this.success(output);
1777
1211
  } catch (err) {
1778
- // Graceful degradation: check if scan completed but found issues (exit code 1)
1779
- const summary = await this.parseSummaryFromDisk(projectPath);
1780
- if (summary) {
1781
- output += this.formatScanOutput(summary, projectPath);
1782
-
1783
- // Submit results to dashboard if connected
1784
- if (apiConnected && apiScan) {
1785
- try {
1786
- // HARDENING: Add timeout to error case submission
1787
- const submitPromise = submitScanResults(apiScan.scanId, {
1788
- verdict: sanitizeString(summary.verdict, 50) || 'UNKNOWN',
1789
- score: sanitizeNumber(summary.score?.overall, 0, 100, 0),
1790
- findings: sanitizeArray(summary.findings, 1000) || [],
1791
- filesScanned: sanitizeNumber(summary.stats?.filesScanned, 0, 1000000, 0),
1792
- linesScanned: sanitizeNumber(summary.stats?.linesScanned, 0, 100000000, 0),
1793
- durationMs: sanitizeNumber(summary.timings?.total, 0, 3600000, 0),
1794
- metadata: {
1795
- profile,
1796
- source: 'mcp-server',
1797
- version: CONFIG.VERSION,
1798
- error: sanitizeString(err.message, 500),
1799
- },
1800
- });
1801
- const submitTimeout = new Promise((_, reject) =>
1802
- setTimeout(() => reject(new Error('Submit timeout')), 10000)
1803
- );
1804
-
1805
- await Promise.race([submitPromise, submitTimeout]);
1806
- console.error(`[MCP] Results sent to dashboard (with error)`);
1807
- } catch (apiErr) {
1808
- console.error(`[MCP] Failed to send results to dashboard: ${apiErr.message}`);
1809
- }
1810
- }
1811
- output += `\nāš ļø Scan completed with findings (exit code ${err.code || 1})\n`;
1812
- return this.success(output);
1813
- }
1814
-
1815
- // Report error to dashboard if connected
1816
- if (apiConnected && apiScan) {
1817
- try {
1818
- // HARDENING: Add timeout to error reporting
1819
- const reportPromise = reportScanError(apiScan.scanId, err);
1820
- const reportTimeout = new Promise((_, reject) =>
1821
- setTimeout(() => reject(new Error('Report timeout')), 10000)
1822
- );
1823
-
1824
- await Promise.race([reportPromise, reportTimeout]);
1825
- console.error(`[MCP] Error reported to dashboard`);
1826
- } catch (apiErr) {
1827
- console.error(`[MCP] Failed to report error to dashboard: ${apiErr.message}`);
1828
- }
1829
- }
1830
-
1831
1212
  return this.error(`Scan failed: ${err.message}`, {
1832
1213
  code: "SCAN_ERROR",
1833
1214
  suggestion: "Check that the project path is valid and contains scanable code",
@@ -1846,7 +1227,7 @@ class VibecheckMCP {
1846
1227
  // ============================================================================
1847
1228
  async handleGate(projectPath, args) {
1848
1229
  // Check tier access (STARTER tier required)
1849
- const access = await getFeatureAccessStatus("gate", args?.apiKey);
1230
+ const access = await checkFeatureAccess("gate", args?.apiKey);
1850
1231
  if (!access.hasAccess) {
1851
1232
  return {
1852
1233
  content: [{
@@ -1862,12 +1243,16 @@ class VibecheckMCP {
1862
1243
  let output = "# 🚦 vibecheck Gate\n\n";
1863
1244
  output += `**Policy:** ${policy}\n\n`;
1864
1245
 
1865
- // Build CLI arguments array (secure)
1866
- const cliArgs = [`--policy=${policy}`];
1867
- if (args?.sarif) cliArgs.push("--sarif");
1868
-
1869
1246
  try {
1870
- await this.runCLI("gate", cliArgs, projectPath);
1247
+ let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" gate`;
1248
+ cmd += ` --policy=${policy}`;
1249
+ if (args?.sarif) cmd += ` --sarif`;
1250
+
1251
+ execSync(cmd, {
1252
+ cwd: projectPath,
1253
+ encoding: "utf8",
1254
+ maxBuffer: 10 * 1024 * 1024,
1255
+ });
1871
1256
 
1872
1257
  output += "## āœ… GATE PASSED\n\n";
1873
1258
  output += "All checks passed. Clear to merge.\n";
@@ -1886,7 +1271,7 @@ class VibecheckMCP {
1886
1271
  async handleFix(projectPath, args) {
1887
1272
  // Check tier access for --apply and --autopilot (PRO tier required)
1888
1273
  if (args?.apply || args?.autopilot) {
1889
- const access = await getFeatureAccessStatus("fix.apply_patches", args?.apiKey);
1274
+ const access = await checkFeatureAccess("fix.apply_patches", args?.apiKey);
1890
1275
  if (!access.hasAccess) {
1891
1276
  return {
1892
1277
  content: [{
@@ -1900,35 +1285,35 @@ class VibecheckMCP {
1900
1285
 
1901
1286
  const mode = args?.autopilot ? "Autopilot" :
1902
1287
  args?.apply ? "Apply" :
1903
- args?.promptOnly ? "Prompt Only" :
1904
- args?.dryRun ? "Dry Run" : "Plan";
1288
+ args?.promptOnly ? "Prompt Only" : "Plan";
1905
1289
 
1906
1290
  let output = "# šŸ›  vibecheck Fix Missions v1\n\n";
1907
1291
  output += `**Mode:** ${mode}\n`;
1908
1292
  output += `**Max Missions:** ${args?.maxMissions || 8}\n`;
1909
1293
  if (args?.autopilot) output += `**Max Steps:** ${args?.maxSteps || 10}\n`;
1910
- if (args?.dryRun) output += `**Dry Run:** Yes (no changes will be made)\n`;
1911
1294
  output += "\n";
1912
1295
 
1913
- // Build CLI arguments array (secure)
1914
- const cliArgs = [];
1915
- if (args?.promptOnly) cliArgs.push("--prompt-only");
1916
- if (args?.apply) cliArgs.push("--apply");
1917
- if (args?.autopilot) cliArgs.push("--autopilot");
1918
- if (args?.share) cliArgs.push("--share");
1919
- if (args?.dryRun) cliArgs.push("--dry-run");
1920
- if (args?.maxMissions) cliArgs.push("--max-missions", String(args.maxMissions));
1921
- if (args?.maxSteps) cliArgs.push("--max-steps", String(args.maxSteps));
1922
-
1923
1296
  try {
1924
- const result = await this.runCLI("fix", cliArgs, projectPath, {
1925
- timeout: CONFIG.TIMEOUTS.AUTOPILOT
1297
+ let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" fix`;
1298
+ if (args?.promptOnly) cmd += ` --prompt-only`;
1299
+ if (args?.apply) cmd += ` --apply`;
1300
+ if (args?.autopilot) cmd += ` --autopilot`;
1301
+ if (args?.share) cmd += ` --share`;
1302
+ if (args?.maxMissions) cmd += ` --max-missions ${args.maxMissions}`;
1303
+ if (args?.maxSteps) cmd += ` --max-steps ${args.maxSteps}`;
1304
+
1305
+ const result = execSync(cmd, {
1306
+ cwd: projectPath,
1307
+ encoding: "utf8",
1308
+ maxBuffer: 10 * 1024 * 1024,
1309
+ timeout: 300000, // 5 min timeout for autopilot
1310
+ env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
1926
1311
  });
1927
1312
 
1928
- output += this.stripAnsi(result.stdout);
1313
+ output += result.replace(/\x1b\[[0-9;]*m/g, ""); // Strip ANSI codes
1929
1314
 
1930
1315
  // Read mission pack if available
1931
- const missionsDir = path.join(projectPath, CONFIG.OUTPUT_DIR, "missions");
1316
+ const missionsDir = path.join(projectPath, ".vibecheck", "missions");
1932
1317
  try {
1933
1318
  const dirs = await fs.readdir(missionsDir);
1934
1319
  const latest = dirs.sort().reverse()[0];
@@ -1942,12 +1327,12 @@ class VibecheckMCP {
1942
1327
  const m = missions.missions[i];
1943
1328
  output += `| ${i + 1} | ${m.title} | ${m.targetFindingIds.length} |\n`;
1944
1329
  }
1945
- output += `\nšŸ“ **Mission Pack:** ${CONFIG.OUTPUT_DIR}/missions/${latest}\n`;
1330
+ output += `\nšŸ“ **Mission Pack:** .vibecheck/missions/${latest}\n`;
1946
1331
  }
1947
1332
  } catch {}
1948
1333
  } catch (err) {
1949
1334
  output += `\nāš ļø Fix error: ${err.message}\n`;
1950
- if (err.partialOutput) output += `\n${this.stripAnsi(err.partialOutput)}\n`;
1335
+ if (err.stdout) output += `\n${err.stdout.replace(/\x1b\[[0-9;]*m/g, "")}\n`;
1951
1336
  }
1952
1337
 
1953
1338
  output += "\n---\n_Fix Missions v1 — Reality Firewall Protected_\n";
@@ -1960,18 +1345,22 @@ class VibecheckMCP {
1960
1345
  async handleShare(projectPath, args) {
1961
1346
  let output = "# šŸ“¦ vibecheck Share Bundle\n\n";
1962
1347
 
1963
- // Build CLI arguments array (secure)
1964
- const cliArgs = [];
1965
- if (args?.prComment) cliArgs.push("--pr-comment");
1966
- if (args?.out) cliArgs.push("--out", args.out);
1967
-
1968
1348
  try {
1969
- const result = await this.runCLI("share", cliArgs, projectPath);
1349
+ let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" share`;
1350
+ if (args?.prComment) cmd += ` --pr-comment`;
1351
+ if (args?.out) cmd += ` --out "${args.out}"`;
1352
+
1353
+ const result = execSync(cmd, {
1354
+ cwd: projectPath,
1355
+ encoding: "utf8",
1356
+ maxBuffer: 10 * 1024 * 1024,
1357
+ env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
1358
+ });
1970
1359
 
1971
- output += this.stripAnsi(result.stdout);
1360
+ output += result.replace(/\x1b\[[0-9;]*m/g, "");
1972
1361
 
1973
1362
  // Read share pack if available
1974
- const missionsDir = path.join(projectPath, CONFIG.OUTPUT_DIR, "missions");
1363
+ const missionsDir = path.join(projectPath, ".vibecheck", "missions");
1975
1364
  try {
1976
1365
  const dirs = await fs.readdir(missionsDir);
1977
1366
  const latest = dirs.sort().reverse()[0];
@@ -1994,14 +1383,14 @@ class VibecheckMCP {
1994
1383
  }
1995
1384
  }
1996
1385
 
1997
- output += `\nšŸ“ **Share Pack:** ${CONFIG.OUTPUT_DIR}/missions/${latest}/share/\n`;
1998
- output += `šŸ“„ **PR Comment:** ${CONFIG.OUTPUT_DIR}/missions/${latest}/share/pr_comment.md\n`;
1386
+ output += `\nšŸ“ **Share Pack:** .vibecheck/missions/${latest}/share/\n`;
1387
+ output += `šŸ“„ **PR Comment:** .vibecheck/missions/${latest}/share/pr_comment.md\n`;
1999
1388
  } catch {}
2000
1389
  }
2001
1390
  } catch {}
2002
1391
  } catch (err) {
2003
1392
  output += `\nāš ļø Share error: ${err.message}\n`;
2004
- if (err.partialOutput) output += `\n${this.stripAnsi(err.partialOutput)}\n`;
1393
+ if (err.stdout) output += `\n${err.stdout.replace(/\x1b\[[0-9;]*m/g, "")}\n`;
2005
1394
  }
2006
1395
 
2007
1396
  output += "\n---\n_Fix Missions Share Bundle_\n";
@@ -2015,23 +1404,28 @@ class VibecheckMCP {
2015
1404
  let output = "# šŸ“¦ vibecheck Truth Pack\n\n";
2016
1405
  output += `**Path:** ${projectPath}\n\n`;
2017
1406
 
2018
- // Build CLI arguments array (secure)
2019
- const cliArgs = [];
2020
- if (args?.snapshot) cliArgs.push("--snapshot");
2021
- if (args?.json) cliArgs.push("--json");
2022
-
2023
1407
  try {
2024
- const result = await this.runCLI("ctx", cliArgs, projectPath);
1408
+ let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" ctx`;
1409
+ if (args?.snapshot) cmd += ` --snapshot`;
1410
+ if (args?.json) cmd += ` --json`;
1411
+
1412
+ const result = execSync(cmd, {
1413
+ cwd: projectPath,
1414
+ encoding: "utf8",
1415
+ maxBuffer: 10 * 1024 * 1024,
1416
+ env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
1417
+ });
2025
1418
 
2026
1419
  if (args?.json) {
2027
1420
  // Return raw JSON
2028
- return this.success(result.stdout);
1421
+ output = result;
1422
+ return this.success(output);
2029
1423
  }
2030
1424
 
2031
- output += this.stripAnsi(result.stdout);
1425
+ output += result.replace(/\x1b\[[0-9;]*m/g, ""); // Strip ANSI codes
2032
1426
 
2033
1427
  // Read truthpack summary
2034
- const truthpackPath = path.join(projectPath, CONFIG.OUTPUT_DIR, "truth", "truthpack.json");
1428
+ const truthpackPath = path.join(projectPath, ".vibecheck", "truth", "truthpack.json");
2035
1429
  try {
2036
1430
  const truthpack = JSON.parse(await fs.readFile(truthpackPath, "utf-8"));
2037
1431
 
@@ -2046,7 +1440,7 @@ class VibecheckMCP {
2046
1440
  output += `| Stripe Detected | ${truthpack.billing?.hasStripe ? "āœ…" : "āŒ"} |\n`;
2047
1441
  output += `| Webhooks | ${truthpack.billing?.summary?.webhookHandlersFound || 0} |\n`;
2048
1442
 
2049
- output += `\nšŸ“ **Saved:** ${CONFIG.OUTPUT_DIR}/truth/truthpack.json\n`;
1443
+ output += `\nšŸ“ **Saved:** .vibecheck/truth/truthpack.json\n`;
2050
1444
  } catch {}
2051
1445
  } catch (err) {
2052
1446
  output += `\nāš ļø Truth pack error: ${err.message}\n`;
@@ -2061,7 +1455,7 @@ class VibecheckMCP {
2061
1455
  // ============================================================================
2062
1456
  async handleProve(projectPath, args) {
2063
1457
  // Check tier access (PRO tier required)
2064
- const access = await getFeatureAccessStatus("prove", args?.apiKey);
1458
+ const access = await checkFeatureAccess("prove", args?.apiKey);
2065
1459
  if (!access.hasAccess) {
2066
1460
  return {
2067
1461
  content: [{
@@ -2076,24 +1470,27 @@ class VibecheckMCP {
2076
1470
  output += `**URL:** ${args?.url || "(static only)"}\n`;
2077
1471
  output += `**Max Fix Rounds:** ${args?.maxFixRounds || 3}\n\n`;
2078
1472
 
2079
- // Build CLI arguments array (secure)
2080
- const cliArgs = [];
2081
- if (args?.url) cliArgs.push("--url", args.url);
2082
- if (args?.auth) cliArgs.push("--auth", args.auth);
2083
- if (args?.storageState) cliArgs.push("--storage-state", args.storageState);
2084
- if (args?.skipReality) cliArgs.push("--skip-reality");
2085
- if (args?.skipFix) cliArgs.push("--skip-fix");
2086
- if (args?.maxFixRounds) cliArgs.push("--max-fix-rounds", String(args.maxFixRounds));
2087
-
2088
1473
  try {
2089
- const result = await this.runCLI("prove", cliArgs, projectPath, {
2090
- timeout: CONFIG.TIMEOUTS.PROVE
1474
+ let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" prove`;
1475
+ if (args?.url) cmd += ` --url ${args.url}`;
1476
+ if (args?.auth) cmd += ` --auth ${args.auth}`;
1477
+ if (args?.storageState) cmd += ` --storage-state ${args.storageState}`;
1478
+ if (args?.skipReality) cmd += ` --skip-reality`;
1479
+ if (args?.skipFix) cmd += ` --skip-fix`;
1480
+ if (args?.maxFixRounds) cmd += ` --max-fix-rounds ${args.maxFixRounds}`;
1481
+
1482
+ const result = execSync(cmd, {
1483
+ cwd: projectPath,
1484
+ encoding: "utf8",
1485
+ maxBuffer: 10 * 1024 * 1024,
1486
+ timeout: 600000, // 10 min timeout for full prove loop
1487
+ env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
2091
1488
  });
2092
1489
 
2093
- output += this.stripAnsi(result.stdout);
1490
+ output += result.replace(/\x1b\[[0-9;]*m/g, "");
2094
1491
 
2095
1492
  // Read prove report
2096
- const provePath = path.join(projectPath, CONFIG.OUTPUT_DIR, "prove", "last_prove.json");
1493
+ const provePath = path.join(projectPath, ".vibecheck", "prove", "last_prove.json");
2097
1494
  try {
2098
1495
  const report = JSON.parse(await fs.readFile(provePath, "utf-8"));
2099
1496
 
@@ -2108,7 +1505,7 @@ class VibecheckMCP {
2108
1505
  } catch {}
2109
1506
  } catch (err) {
2110
1507
  output += `\nāš ļø Prove error: ${err.message}\n`;
2111
- if (err.partialOutput) output += `\n${this.stripAnsi(err.partialOutput)}\n`;
1508
+ if (err.stdout) output += `\n${err.stdout.replace(/\x1b\[[0-9;]*m/g, "")}\n`;
2112
1509
  }
2113
1510
 
2114
1511
  output += "\n---\n_One command to make it real_\n";
@@ -2127,18 +1524,19 @@ class VibecheckMCP {
2127
1524
 
2128
1525
  let output = `# šŸŽ¬ vibecheck Proof: ${mode.toUpperCase()}\n\n`;
2129
1526
 
2130
- // Build CLI arguments array (secure)
2131
- const cliArgs = [mode];
2132
- if (mode === "reality" && args?.url) cliArgs.push(`--url=${args.url}`);
2133
- if (mode === "reality" && args?.flow) cliArgs.push(`--flow=${args.flow}`);
2134
-
2135
1527
  try {
2136
- const result = await this.runCLI("proof", cliArgs, projectPath, {
2137
- timeout: CONFIG.TIMEOUTS.SCAN,
2138
- skipAuth: false
1528
+ let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" proof ${mode}`;
1529
+ if (mode === "reality" && args?.url) cmd += ` --url=${args.url}`;
1530
+ if (mode === "reality" && args?.flow) cmd += ` --flow=${args.flow}`;
1531
+
1532
+ const result = execSync(cmd, {
1533
+ cwd: projectPath,
1534
+ encoding: "utf8",
1535
+ maxBuffer: 10 * 1024 * 1024,
1536
+ timeout: 120000, // 2 min timeout for reality mode
2139
1537
  });
2140
1538
 
2141
- output += result.stdout;
1539
+ output += result;
2142
1540
  } catch (err) {
2143
1541
  if (mode === "mocks") {
2144
1542
  output += "## 🚫 MOCKPROOF: FAIL\n\n";
@@ -2147,7 +1545,7 @@ class VibecheckMCP {
2147
1545
  output += "## 🚫 REALITY MODE: FAIL\n\n";
2148
1546
  output += "Fake data or mock services detected at runtime.\n";
2149
1547
  }
2150
- output += `\n${err.partialOutput || err.message}\n`;
1548
+ output += `\n${err.stdout || err.message}\n`;
2151
1549
  }
2152
1550
 
2153
1551
  return this.success(output);
@@ -2264,30 +1662,24 @@ class VibecheckMCP {
2264
1662
  // SHIP - Quick health check
2265
1663
  // ============================================================================
2266
1664
  async handleShip(projectPath, args) {
2267
- // HARDENING: Validate project path
2268
- const validation = this.validateProjectPath(projectPath);
2269
- if (!validation.valid) {
2270
- return this.error(validation.error, {
2271
- code: validation.code || "INVALID_PATH",
2272
- suggestion: validation.suggestion,
2273
- nextSteps: validation.nextSteps || [],
2274
- });
2275
- }
2276
-
2277
1665
  let output = "# šŸš€ vibecheck Ship\n\n";
2278
1666
  output += `**Path:** ${projectPath}\n\n`;
2279
1667
 
2280
- // Build CLI arguments array (secure)
2281
- const cliArgs = [];
2282
- if (args?.fix) cliArgs.push("--fix");
2283
-
2284
1668
  try {
2285
- const result = await this.runCLI("ship", cliArgs, projectPath);
1669
+ let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" ship`;
1670
+ if (args?.fix) cmd += ` --fix`;
2286
1671
 
2287
- output += this.stripAnsi(result.stdout);
1672
+ const result = execSync(cmd, {
1673
+ cwd: projectPath,
1674
+ encoding: "utf8",
1675
+ maxBuffer: 10 * 1024 * 1024,
1676
+ env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
1677
+ });
1678
+
1679
+ // Parse the output for key information
1680
+ output += result.replace(/\x1b\[[0-9;]*m/g, ""); // Strip ANSI codes
2288
1681
  } catch (err) {
2289
1682
  output += `\nāš ļø Ship check failed: ${err.message}\n`;
2290
- if (err.partialOutput) output += `\n${this.stripAnsi(err.partialOutput)}\n`;
2291
1683
  }
2292
1684
 
2293
1685
  output += "\n---\n_Context Enhanced by vibecheck AI_\n";
@@ -2298,46 +1690,35 @@ class VibecheckMCP {
2298
1690
  // VERIFY - Runtime browser testing
2299
1691
  // ============================================================================
2300
1692
  async handleVerify(projectPath, args) {
2301
- // HARDENING: Validate URL
2302
- const urlValidation = validateUrl(args?.url);
2303
- if (!urlValidation.valid) {
2304
- return this.error(urlValidation.error, {
2305
- code: 'INVALID_URL',
2306
- suggestion: 'Provide a valid HTTP/HTTPS URL',
2307
- nextSteps: ['Check the URL format', 'Ensure the URL is accessible'],
2308
- });
2309
- }
2310
- const url = urlValidation.url;
1693
+ const url = args?.url;
1694
+ if (!url) return this.error("URL is required");
2311
1695
 
2312
1696
  let output = "# 🧪 vibecheck Verify\n\n";
2313
1697
  output += `**URL:** ${url}\n`;
2314
-
2315
- // HARDENING: Sanitize array inputs
2316
- const flows = sanitizeArray(args?.flows, 10);
2317
- if (flows.length) output += `**Flows:** ${flows.join(", ")}\n`;
1698
+ if (args?.flows?.length) output += `**Flows:** ${args.flows.join(", ")}\n`;
2318
1699
  if (args?.headed) output += `**Mode:** Headed (visible browser)\n`;
2319
1700
  if (args?.record) output += `**Recording:** Enabled\n`;
2320
1701
  output += "\n";
2321
1702
 
2322
- // Build CLI arguments array (secure)
2323
- const cliArgs = ["--url", url];
2324
- // HARDENING: Sanitize auth - don't log full credentials
2325
- if (args?.auth && typeof args.auth === 'string') {
2326
- cliArgs.push("--auth", sanitizeString(args.auth, 200));
2327
- }
2328
- if (flows.length) cliArgs.push("--flows", flows.join(","));
2329
- if (args?.headed) cliArgs.push("--headed");
2330
- if (args?.record) cliArgs.push("--record");
2331
-
2332
1703
  try {
2333
- const result = await this.runCLI("verify", cliArgs, projectPath, {
2334
- timeout: CONFIG.TIMEOUTS.VERIFY
1704
+ let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" verify --url "${url}"`;
1705
+ if (args?.auth) cmd += ` --auth "${args.auth}"`;
1706
+ if (args?.flows?.length) cmd += ` --flows ${args.flows.join(",")}`;
1707
+ if (args?.headed) cmd += ` --headed`;
1708
+ if (args?.record) cmd += ` --record`;
1709
+
1710
+ const result = execSync(cmd, {
1711
+ cwd: projectPath,
1712
+ encoding: "utf8",
1713
+ maxBuffer: 10 * 1024 * 1024,
1714
+ timeout: 180000, // 3 min timeout
1715
+ env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
2335
1716
  });
2336
1717
 
2337
- output += this.stripAnsi(result.stdout);
1718
+ output += result.replace(/\x1b\[[0-9;]*m/g, "");
2338
1719
 
2339
1720
  // Try to read reality results
2340
- const realityPath = path.join(projectPath, CONFIG.OUTPUT_DIR, "reality", "last_reality.json");
1721
+ const realityPath = path.join(projectPath, ".vibecheck", "reality", "last_reality.json");
2341
1722
  try {
2342
1723
  const reality = JSON.parse(await fs.readFile(realityPath, "utf-8"));
2343
1724
 
@@ -2360,11 +1741,11 @@ class VibecheckMCP {
2360
1741
  }
2361
1742
  }
2362
1743
 
2363
- output += `\nšŸ“ **Full Report:** ${CONFIG.OUTPUT_DIR}/reality/last_reality.json\n`;
1744
+ output += `\nšŸ“ **Full Report:** .vibecheck/reality/last_reality.json\n`;
2364
1745
  } catch {}
2365
1746
  } catch (err) {
2366
1747
  output += `\nāš ļø Verify failed: ${err.message}\n`;
2367
- if (err.partialOutput) output += `\n${this.stripAnsi(err.partialOutput)}\n`;
1748
+ if (err.stdout) output += `\n${err.stdout.replace(/\x1b\[[0-9;]*m/g, "")}\n`;
2368
1749
  }
2369
1750
 
2370
1751
  output += "\n---\n_Runtime Verification by vibecheck_\n";
@@ -2375,78 +1756,42 @@ class VibecheckMCP {
2375
1756
  // REALITY v2 - Two-Pass Auth Verification + Dead UI Crawler
2376
1757
  // ============================================================================
2377
1758
  async handleReality(projectPath, args) {
2378
- // HARDENING: Validate URL
2379
- const urlValidation = validateUrl(args?.url);
2380
- if (!urlValidation.valid) {
2381
- return this.error(urlValidation.error, {
2382
- code: 'INVALID_URL',
2383
- suggestion: 'Provide a valid HTTP/HTTPS URL',
2384
- nextSteps: ['Check the URL format', 'Ensure the URL is accessible'],
2385
- });
2386
- }
2387
- const url = urlValidation.url;
1759
+ const url = args?.url;
1760
+ if (!url) return this.error("URL is required");
2388
1761
 
2389
1762
  let output = "# 🧪 vibecheck Reality v2\n\n";
2390
1763
  output += `**URL:** ${url}\n`;
2391
1764
  output += `**Two-Pass Auth:** ${args?.verifyAuth ? "Yes" : "No"}\n`;
2392
-
2393
- // HARDENING: Safely display auth info (mask password)
2394
- if (args?.auth && typeof args.auth === 'string') {
2395
- const authParts = args.auth.split(":");
2396
- const maskedAuth = authParts[0] ? `${authParts[0].slice(0, 20)}:***` : '***';
2397
- output += `**Auth:** ${maskedAuth}\n`;
2398
- }
2399
- if (args?.storageState) output += `**Storage State:** ${sanitizeString(args.storageState, 100)}\n`;
1765
+ if (args?.auth) output += `**Auth:** ${args.auth.split(":")[0]}:***\n`;
1766
+ if (args?.storageState) output += `**Storage State:** ${args.storageState}\n`;
2400
1767
  if (args?.headed) output += `**Mode:** Headed (visible browser)\n`;
2401
1768
  if (args?.danger) output += `**Danger Mode:** Enabled (risky clicks allowed)\n`;
2402
1769
  output += "\n";
2403
1770
 
2404
- // Build CLI arguments array (secure)
2405
- const cliArgs = ["--url", url];
2406
- if (args?.auth && typeof args.auth === 'string') {
2407
- cliArgs.push("--auth", sanitizeString(args.auth, 200));
2408
- }
2409
- if (args?.verifyAuth) cliArgs.push("--verify-auth");
2410
-
2411
- // HARDENING: Validate path arguments
2412
- if (args?.storageState) {
2413
- const pathCheck = sanitizePath(args.storageState, projectPath);
2414
- if (pathCheck.valid) {
2415
- cliArgs.push("--storage-state", pathCheck.path);
2416
- }
2417
- }
2418
- if (args?.saveStorageState) {
2419
- const pathCheck = sanitizePath(args.saveStorageState, projectPath);
2420
- if (pathCheck.valid) {
2421
- cliArgs.push("--save-storage-state", pathCheck.path);
2422
- }
2423
- }
2424
- if (args?.truthpack) {
2425
- const pathCheck = sanitizePath(args.truthpack, projectPath);
2426
- if (pathCheck.valid) {
2427
- cliArgs.push("--truthpack", pathCheck.path);
2428
- }
2429
- }
2430
- if (args?.headed) cliArgs.push("--headed");
2431
-
2432
- // HARDENING: Bound numeric arguments
2433
- if (args?.maxPages) {
2434
- cliArgs.push("--max-pages", String(sanitizeNumber(args.maxPages, 1, 100, 18)));
2435
- }
2436
- if (args?.maxDepth) {
2437
- cliArgs.push("--max-depth", String(sanitizeNumber(args.maxDepth, 1, 10, 2)));
2438
- }
2439
- if (args?.danger) cliArgs.push("--danger");
2440
-
2441
1771
  try {
2442
- const result = await this.runCLI("reality", cliArgs, projectPath, {
2443
- timeout: CONFIG.TIMEOUTS.REALITY
1772
+ let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" reality --url "${url}"`;
1773
+ if (args?.auth) cmd += ` --auth "${args.auth}"`;
1774
+ if (args?.verifyAuth) cmd += ` --verify-auth`;
1775
+ if (args?.storageState) cmd += ` --storage-state "${args.storageState}"`;
1776
+ if (args?.saveStorageState) cmd += ` --save-storage-state "${args.saveStorageState}"`;
1777
+ if (args?.truthpack) cmd += ` --truthpack "${args.truthpack}"`;
1778
+ if (args?.headed) cmd += ` --headed`;
1779
+ if (args?.maxPages) cmd += ` --max-pages ${args.maxPages}`;
1780
+ if (args?.maxDepth) cmd += ` --max-depth ${args.maxDepth}`;
1781
+ if (args?.danger) cmd += ` --danger`;
1782
+
1783
+ const result = execSync(cmd, {
1784
+ cwd: projectPath,
1785
+ encoding: "utf8",
1786
+ maxBuffer: 10 * 1024 * 1024,
1787
+ timeout: 300000, // 5 min timeout for two-pass
1788
+ env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
2444
1789
  });
2445
1790
 
2446
- output += this.stripAnsi(result.stdout);
1791
+ output += result.replace(/\x1b\[[0-9;]*m/g, "");
2447
1792
 
2448
1793
  // Read reality results
2449
- const realityPath = path.join(projectPath, CONFIG.OUTPUT_DIR, "reality", "last_reality.json");
1794
+ const realityPath = path.join(projectPath, ".vibecheck", "reality", "last_reality.json");
2450
1795
  try {
2451
1796
  const reality = JSON.parse(await fs.readFile(realityPath, "utf-8"));
2452
1797
 
@@ -2497,7 +1842,7 @@ class VibecheckMCP {
2497
1842
  const verdict = blocks.length > 0 ? "šŸ›‘ BLOCK" : warns.length > 0 ? "āš ļø WARN" : "āœ… CLEAN";
2498
1843
  output += `\n## Verdict: ${verdict}\n`;
2499
1844
 
2500
- output += `\nšŸ“ **Full Report:** ${CONFIG.OUTPUT_DIR}/reality/last_reality.json\n`;
1845
+ output += `\nšŸ“ **Full Report:** .vibecheck/reality/last_reality.json\n`;
2501
1846
 
2502
1847
  if (reality.meta?.savedStorageState) {
2503
1848
  output += `šŸ“ **Saved Auth State:** ${reality.meta.savedStorageState}\n`;
@@ -2505,7 +1850,7 @@ class VibecheckMCP {
2505
1850
  } catch {}
2506
1851
  } catch (err) {
2507
1852
  output += `\nāš ļø Reality failed: ${err.message}\n`;
2508
- if (err.partialOutput) output += `\n${this.stripAnsi(err.partialOutput)}\n`;
1853
+ if (err.stdout) output += `\n${err.stdout.replace(/\x1b\[[0-9;]*m/g, "")}\n`;
2509
1854
  }
2510
1855
 
2511
1856
  output += "\n---\n_Reality Mode v2 — Two-Pass Auth Verification_\n";
@@ -2516,38 +1861,35 @@ class VibecheckMCP {
2516
1861
  // AI-TEST - AI Agent testing
2517
1862
  // ============================================================================
2518
1863
  async handleAITest(projectPath, args) {
2519
- // HARDENING: Validate URL
2520
- const urlValidation = validateUrl(args?.url);
2521
- if (!urlValidation.valid) {
2522
- return this.error(urlValidation.error, {
2523
- code: 'INVALID_URL',
2524
- suggestion: 'Provide a valid HTTP/HTTPS URL',
2525
- nextSteps: ['Check the URL format', 'Ensure the URL is accessible'],
2526
- });
2527
- }
2528
- const url = urlValidation.url;
2529
-
2530
- // HARDENING: Sanitize goal string
2531
- const goal = sanitizeString(args?.goal, 500) || "Test all features";
1864
+ const url = args?.url;
1865
+ if (!url) return this.error("URL is required");
2532
1866
 
2533
1867
  let output = "# šŸ¤– vibecheck AI Agent\n\n";
2534
1868
  output += `**URL:** ${url}\n`;
2535
- output += `**Goal:** ${goal}\n\n`;
2536
-
2537
- // Build CLI arguments array (secure)
2538
- const cliArgs = ["--url", url];
2539
- if (goal) cliArgs.push("--goal", goal);
2540
- if (args?.headed) cliArgs.push("--headed");
1869
+ output += `**Goal:** ${args?.goal || "Test all features"}\n\n`;
2541
1870
 
2542
1871
  try {
2543
- const result = await this.runCLI("ai-test", cliArgs, projectPath, {
2544
- timeout: CONFIG.TIMEOUTS.VERIFY
1872
+ let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" ai-test --url "${url}"`;
1873
+ if (args?.goal) cmd += ` --goal "${args.goal}"`;
1874
+ if (args?.headed) cmd += ` --headed`;
1875
+
1876
+ const result = execSync(cmd, {
1877
+ cwd: projectPath,
1878
+ encoding: "utf8",
1879
+ maxBuffer: 10 * 1024 * 1024,
1880
+ timeout: 180000,
1881
+ env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
2545
1882
  });
2546
1883
 
2547
- output += this.stripAnsi(result.stdout);
1884
+ output += result.replace(/\x1b\[[0-9;]*m/g, "");
2548
1885
 
2549
1886
  // Try to read fix prompts
2550
- const promptPath = path.join(projectPath, CONFIG.OUTPUT_DIR, "ai-agent", "fix-prompt.md");
1887
+ const promptPath = path.join(
1888
+ projectPath,
1889
+ ".vibecheck",
1890
+ "ai-agent",
1891
+ "fix-prompt.md",
1892
+ );
2551
1893
  try {
2552
1894
  const prompts = await fs.readFile(promptPath, "utf-8");
2553
1895
  output += "\n## Fix Prompts Generated\n\n";
@@ -2571,15 +1913,19 @@ class VibecheckMCP {
2571
1913
  let output = "# šŸ¤– vibecheck Autopilot\n\n";
2572
1914
  output += `**Action:** ${action}\n\n`;
2573
1915
 
2574
- // Build CLI arguments array (secure - no injection possible)
2575
- const cliArgs = [action];
2576
- if (args?.slack) cliArgs.push("--slack", args.slack);
2577
- if (args?.email) cliArgs.push("--email", args.email);
2578
-
2579
1916
  try {
2580
- const result = await this.runCLI("autopilot", cliArgs, projectPath);
1917
+ let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" autopilot ${action}`;
1918
+ if (args?.slack) cmd += ` --slack="${args.slack}"`;
1919
+ if (args?.email) cmd += ` --email="${args.email}"`;
1920
+
1921
+ const result = execSync(cmd, {
1922
+ cwd: projectPath,
1923
+ encoding: "utf8",
1924
+ maxBuffer: 10 * 1024 * 1024,
1925
+ env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
1926
+ });
2581
1927
 
2582
- output += this.stripAnsi(result.stdout);
1928
+ output += result.replace(/\x1b\[[0-9;]*m/g, "");
2583
1929
  } catch (err) {
2584
1930
  output += `\nāš ļø Autopilot failed: ${err.message}\n`;
2585
1931
  }
@@ -2592,7 +1938,7 @@ class VibecheckMCP {
2592
1938
  // ============================================================================
2593
1939
  async handleAutopilotPlan(projectPath, args) {
2594
1940
  // Check tier access (PRO tier required)
2595
- const access = await getFeatureAccessStatus("fix.apply_patches", args?.apiKey);
1941
+ const access = await checkFeatureAccess("fix.apply_patches", args?.apiKey);
2596
1942
  if (!access.hasAccess) {
2597
1943
  return {
2598
1944
  content: [{
@@ -2616,17 +1962,20 @@ class VibecheckMCP {
2616
1962
  const core = await import(corePath);
2617
1963
  runAutopilot = core.runAutopilot;
2618
1964
  } catch {
2619
- // Fallback to CLI - build secure args array
2620
- const cliArgs = [
2621
- "plan",
2622
- "--profile", args?.profile || "ship",
2623
- "--max-fixes", String(args?.maxFixes || 10),
2624
- "--json"
2625
- ];
2626
-
2627
- const result = await this.runCLI("autopilot", cliArgs, projectPath);
1965
+ // Fallback to CLI
1966
+ let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" autopilot plan`;
1967
+ cmd += ` --profile ${args?.profile || "ship"}`;
1968
+ cmd += ` --max-fixes ${args?.maxFixes || 10}`;
1969
+ cmd += ` --json`;
1970
+
1971
+ const result = execSync(cmd, {
1972
+ cwd: projectPath,
1973
+ encoding: "utf8",
1974
+ maxBuffer: 10 * 1024 * 1024,
1975
+ env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
1976
+ });
2628
1977
 
2629
- const jsonResult = JSON.parse(result.stdout);
1978
+ const jsonResult = JSON.parse(result);
2630
1979
  output += `## Scan Results\n\n`;
2631
1980
  output += `- **Total findings:** ${jsonResult.totalFindings}\n`;
2632
1981
  output += `- **Fixable:** ${jsonResult.fixableFindings}\n`;
@@ -2679,7 +2028,7 @@ class VibecheckMCP {
2679
2028
  // ============================================================================
2680
2029
  async handleAutopilotApply(projectPath, args) {
2681
2030
  // Check tier access (PRO tier required)
2682
- const access = await getFeatureAccessStatus("fix.apply_patches", args?.apiKey);
2031
+ const access = await checkFeatureAccess("fix.apply_patches", args?.apiKey);
2683
2032
  if (!access.hasAccess) {
2684
2033
  return {
2685
2034
  content: [{
@@ -2695,22 +2044,24 @@ class VibecheckMCP {
2695
2044
  output += `**Profile:** ${args?.profile || "ship"}\n`;
2696
2045
  output += `**Dry Run:** ${args?.dryRun ? "Yes" : "No"}\n\n`;
2697
2046
 
2698
- // Build CLI arguments array (secure)
2699
- const cliArgs = [
2700
- "apply",
2701
- "--profile", args?.profile || "ship",
2702
- "--max-fixes", String(args?.maxFixes || 10),
2703
- "--json"
2704
- ];
2705
- if (args?.verify === false) cliArgs.push("--no-verify");
2706
- if (args?.dryRun) cliArgs.push("--dry-run");
2707
-
2708
2047
  try {
2709
- const result = await this.runCLI("autopilot", cliArgs, projectPath, {
2710
- timeout: CONFIG.TIMEOUTS.AUTOPILOT,
2048
+ // Fallback to CLI
2049
+ let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" autopilot apply`;
2050
+ cmd += ` --profile ${args?.profile || "ship"}`;
2051
+ cmd += ` --max-fixes ${args?.maxFixes || 10}`;
2052
+ if (args?.verify === false) cmd += ` --no-verify`;
2053
+ if (args?.dryRun) cmd += ` --dry-run`;
2054
+ cmd += ` --json`;
2055
+
2056
+ const result = execSync(cmd, {
2057
+ cwd: projectPath,
2058
+ encoding: "utf8",
2059
+ maxBuffer: 10 * 1024 * 1024,
2060
+ timeout: 300000, // 5 min timeout
2061
+ env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
2711
2062
  });
2712
2063
 
2713
- const jsonResult = JSON.parse(result.stdout);
2064
+ const jsonResult = JSON.parse(result);
2714
2065
 
2715
2066
  output += `## Results\n\n`;
2716
2067
  output += `- **Packs attempted:** ${jsonResult.packsAttempted}\n`;
@@ -2740,7 +2091,7 @@ class VibecheckMCP {
2740
2091
  // ============================================================================
2741
2092
  async handleBadge(projectPath, args) {
2742
2093
  // Check tier access (STARTER tier required)
2743
- const access = await getFeatureAccessStatus("badge", args?.apiKey);
2094
+ const access = await checkFeatureAccess("badge", args?.apiKey);
2744
2095
  if (!access.hasAccess) {
2745
2096
  return {
2746
2097
  content: [{
@@ -2755,17 +2106,26 @@ class VibecheckMCP {
2755
2106
 
2756
2107
  let output = "# šŸ… vibecheck Badge\n\n";
2757
2108
 
2758
- // Build CLI arguments array (secure)
2759
- const cliArgs = ["--format", format];
2760
- if (args?.style) cliArgs.push("--style", args.style);
2761
-
2762
2109
  try {
2763
- const result = await this.runCLI("badge", cliArgs, projectPath);
2110
+ let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" badge --format ${format}`;
2111
+ if (args?.style) cmd += ` --style ${args.style}`;
2112
+
2113
+ const result = execSync(cmd, {
2114
+ cwd: projectPath,
2115
+ encoding: "utf8",
2116
+ maxBuffer: 10 * 1024 * 1024,
2117
+ env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
2118
+ });
2764
2119
 
2765
- output += this.stripAnsi(result.stdout);
2120
+ output += result.replace(/\x1b\[[0-9;]*m/g, "");
2766
2121
 
2767
2122
  // Read the badge file
2768
- const badgePath = path.join(projectPath, CONFIG.OUTPUT_DIR, "badges", `badge.${format}`);
2123
+ const badgePath = path.join(
2124
+ projectPath,
2125
+ ".vibecheck",
2126
+ "badges",
2127
+ `badge.${format}`,
2128
+ );
2769
2129
  try {
2770
2130
  const badge = await fs.readFile(badgePath, "utf-8");
2771
2131
  if (format === "md") {
@@ -2793,15 +2153,19 @@ class VibecheckMCP {
2793
2153
  output += `**Project:** ${path.basename(projectPath)}\n`;
2794
2154
  output += `**Platform:** ${platform}\n\n`;
2795
2155
 
2796
- // Build CLI arguments array (secure)
2797
- const cliArgs = [];
2798
- if (platform !== "all") cliArgs.push(`--platform=${platform}`);
2799
-
2800
2156
  try {
2801
- await this.runCLI("context", cliArgs, projectPath, { skipAuth: false });
2157
+ let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" context`;
2158
+ if (platform !== "all") cmd += ` --platform=${platform}`;
2159
+
2160
+ execSync(cmd, {
2161
+ cwd: projectPath,
2162
+ encoding: "utf8",
2163
+ maxBuffer: 10 * 1024 * 1024,
2164
+ });
2802
2165
 
2803
2166
  output += "## āœ… Context Generated\n\n";
2804
- output += "Your AI coding assistants now have full project awareness.\n\n";
2167
+ output +=
2168
+ "Your AI coding assistants now have full project awareness.\n\n";
2805
2169
 
2806
2170
  output += "### Generated Files\n\n";
2807
2171
 
@@ -2822,8 +2186,8 @@ class VibecheckMCP {
2822
2186
  }
2823
2187
 
2824
2188
  output += "**Universal (MCP):**\n";
2825
- output += `- \`${CONFIG.OUTPUT_DIR}/context.json\` - Full context\n`;
2826
- output += `- \`${CONFIG.OUTPUT_DIR}/project-map.json\` - Project analysis\n\n`;
2189
+ output += "- `.vibecheck/context.json` - Full context\n";
2190
+ output += "- `.vibecheck/project-map.json` - Project analysis\n\n";
2827
2191
 
2828
2192
  output += "### What Your AI Now Knows\n\n";
2829
2193
  output += "- Project architecture and structure\n";
@@ -2832,7 +2196,8 @@ class VibecheckMCP {
2832
2196
  output += "- Coding conventions and patterns\n";
2833
2197
  output += "- Dependencies and tech stack\n\n";
2834
2198
 
2835
- output += "> **Tip:** Regenerate after major codebase changes with `vibecheck context`\n";
2199
+ output +=
2200
+ "> **Tip:** Regenerate after major codebase changes with `vibecheck context`\n";
2836
2201
  } catch (err) {
2837
2202
  output += `\nāš ļø Context generation failed: ${err.message}\n`;
2838
2203
  }
@@ -2896,57 +2261,15 @@ class VibecheckMCP {
2896
2261
  }
2897
2262
 
2898
2263
  // ============================================================================
2899
- // RUN - with graceful shutdown handling
2264
+ // RUN
2900
2265
  // ============================================================================
2901
2266
  async run() {
2902
2267
  const transport = new StdioServerTransport();
2903
-
2904
- // ========================================================================
2905
- // HARDENING: Graceful shutdown handling
2906
- // ========================================================================
2907
- const shutdown = async (signal) => {
2908
- console.error(`\n[MCP] Received ${signal}, shutting down gracefully...`);
2909
- try {
2910
- // Clear rate limit state to prevent memory leaks
2911
- rateLimitState.calls = [];
2912
-
2913
- // Close server connection
2914
- await this.server.close();
2915
- console.error('[MCP] Server closed successfully');
2916
- } catch (err) {
2917
- console.error(`[MCP] Error during shutdown: ${err.message}`);
2918
- }
2919
- process.exit(0);
2920
- };
2921
-
2922
- // Handle termination signals
2923
- process.on('SIGINT', () => shutdown('SIGINT'));
2924
- process.on('SIGTERM', () => shutdown('SIGTERM'));
2925
-
2926
- // Handle uncaught errors gracefully
2927
- process.on('uncaughtException', (err) => {
2928
- console.error(`[MCP] Uncaught exception: ${err.message}`);
2929
- console.error(err.stack);
2930
- // Don't exit - try to keep running
2931
- });
2932
-
2933
- process.on('unhandledRejection', (reason, promise) => {
2934
- console.error(`[MCP] Unhandled rejection at:`, promise);
2935
- console.error(`[MCP] Reason:`, reason);
2936
- // Don't exit - try to keep running
2937
- });
2938
-
2939
2268
  await this.server.connect(transport);
2940
- console.error(`vibecheck MCP Server v${VERSION} running on stdio (hardened)`);
2269
+ console.error("vibecheck MCP Server v2.0 running on stdio");
2941
2270
  }
2942
2271
  }
2943
2272
 
2944
- // ============================================================================
2945
- // MAIN - with error handling
2946
- // ============================================================================
2273
+ // Main
2947
2274
  const server = new VibecheckMCP();
2948
- server.run().catch((err) => {
2949
- console.error(`[MCP] Fatal error starting server: ${err.message}`);
2950
- console.error(err.stack);
2951
- process.exit(1);
2952
- });
2275
+ server.run().catch(console.error);