vibecheck-ai 2.0.2 → 5.0.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 (456) hide show
  1. package/bin/.generated +25 -0
  2. package/bin/_deprecations.js +463 -0
  3. package/bin/_router.js +46 -0
  4. package/bin/cli-hygiene.js +241 -0
  5. package/bin/dev/run-v2-torture.js +30 -0
  6. package/bin/registry.js +656 -0
  7. package/bin/runners/CLI_REFACTOR_SUMMARY.md +229 -0
  8. package/bin/runners/ENHANCEMENT_GUIDE.md +121 -0
  9. package/bin/runners/REPORT_AUDIT.md +64 -0
  10. package/bin/runners/cli-utils.js +1070 -0
  11. package/bin/runners/context/ai-task-decomposer.js +337 -0
  12. package/bin/runners/context/analyzer.js +513 -0
  13. package/bin/runners/context/api-contracts.js +427 -0
  14. package/bin/runners/context/context-diff.js +342 -0
  15. package/bin/runners/context/context-pruner.js +291 -0
  16. package/bin/runners/context/dependency-graph.js +414 -0
  17. package/bin/runners/context/generators/claude.js +107 -0
  18. package/bin/runners/context/generators/codex.js +108 -0
  19. package/bin/runners/context/generators/copilot.js +119 -0
  20. package/bin/runners/context/generators/cursor-enhanced.js +2525 -0
  21. package/bin/runners/context/generators/cursor.js +514 -0
  22. package/bin/runners/context/generators/mcp.js +169 -0
  23. package/bin/runners/context/generators/windsurf.js +180 -0
  24. package/bin/runners/context/git-context.js +304 -0
  25. package/bin/runners/context/index.js +1110 -0
  26. package/bin/runners/context/insights.js +173 -0
  27. package/bin/runners/context/mcp-server/generate-rules.js +337 -0
  28. package/bin/runners/context/mcp-server/index.js +1176 -0
  29. package/bin/runners/context/mcp-server/package.json +24 -0
  30. package/bin/runners/context/memory.js +200 -0
  31. package/bin/runners/context/monorepo.js +215 -0
  32. package/bin/runners/context/multi-repo-federation.js +404 -0
  33. package/bin/runners/context/patterns.js +253 -0
  34. package/bin/runners/context/proof-context.js +1264 -0
  35. package/bin/runners/context/security-scanner.js +541 -0
  36. package/bin/runners/context/semantic-search.js +350 -0
  37. package/bin/runners/context/shared.js +264 -0
  38. package/bin/runners/context/team-conventions.js +336 -0
  39. package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -0
  40. package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +474 -0
  41. package/bin/runners/lib/agent-firewall/change-packet/builder.js +488 -0
  42. package/bin/runners/lib/agent-firewall/change-packet/schema.json +228 -0
  43. package/bin/runners/lib/agent-firewall/change-packet/store.js +200 -0
  44. package/bin/runners/lib/agent-firewall/claims/claim-types.js +21 -0
  45. package/bin/runners/lib/agent-firewall/claims/extractor.js +303 -0
  46. package/bin/runners/lib/agent-firewall/claims/patterns.js +24 -0
  47. package/bin/runners/lib/agent-firewall/critic/index.js +151 -0
  48. package/bin/runners/lib/agent-firewall/critic/judge.js +432 -0
  49. package/bin/runners/lib/agent-firewall/critic/prompts.js +305 -0
  50. package/bin/runners/lib/agent-firewall/enforcement/gateway.js +1059 -0
  51. package/bin/runners/lib/agent-firewall/enforcement/index.js +98 -0
  52. package/bin/runners/lib/agent-firewall/enforcement/mode.js +318 -0
  53. package/bin/runners/lib/agent-firewall/enforcement/orchestrator.js +484 -0
  54. package/bin/runners/lib/agent-firewall/enforcement/proof-artifact.js +418 -0
  55. package/bin/runners/lib/agent-firewall/enforcement/schemas/change-event.schema.json +173 -0
  56. package/bin/runners/lib/agent-firewall/enforcement/schemas/intent.schema.json +181 -0
  57. package/bin/runners/lib/agent-firewall/enforcement/schemas/verdict.schema.json +222 -0
  58. package/bin/runners/lib/agent-firewall/enforcement/verdict-v2.js +333 -0
  59. package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +88 -0
  60. package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +75 -0
  61. package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +127 -0
  62. package/bin/runners/lib/agent-firewall/evidence/resolver.js +102 -0
  63. package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +213 -0
  64. package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +145 -0
  65. package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +19 -0
  66. package/bin/runners/lib/agent-firewall/fs-hook/installer.js +87 -0
  67. package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +184 -0
  68. package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +163 -0
  69. package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +107 -0
  70. package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +68 -0
  71. package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +66 -0
  72. package/bin/runners/lib/agent-firewall/index.js +200 -0
  73. package/bin/runners/lib/agent-firewall/integration/index.js +20 -0
  74. package/bin/runners/lib/agent-firewall/integration/ship-gate.js +437 -0
  75. package/bin/runners/lib/agent-firewall/intent/alignment-engine.js +634 -0
  76. package/bin/runners/lib/agent-firewall/intent/auto-detect.js +426 -0
  77. package/bin/runners/lib/agent-firewall/intent/index.js +102 -0
  78. package/bin/runners/lib/agent-firewall/intent/schema.js +352 -0
  79. package/bin/runners/lib/agent-firewall/intent/store.js +283 -0
  80. package/bin/runners/lib/agent-firewall/interception/fs-interceptor.js +502 -0
  81. package/bin/runners/lib/agent-firewall/interception/index.js +23 -0
  82. package/bin/runners/lib/agent-firewall/interceptor/base.js +308 -0
  83. package/bin/runners/lib/agent-firewall/interceptor/cursor.js +35 -0
  84. package/bin/runners/lib/agent-firewall/interceptor/vscode.js +35 -0
  85. package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +34 -0
  86. package/bin/runners/lib/agent-firewall/lawbook/distributor.js +465 -0
  87. package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +604 -0
  88. package/bin/runners/lib/agent-firewall/lawbook/index.js +304 -0
  89. package/bin/runners/lib/agent-firewall/lawbook/registry.js +514 -0
  90. package/bin/runners/lib/agent-firewall/lawbook/schema.js +420 -0
  91. package/bin/runners/lib/agent-firewall/logger.js +141 -0
  92. package/bin/runners/lib/agent-firewall/policy/default-policy.json +90 -0
  93. package/bin/runners/lib/agent-firewall/policy/engine.js +103 -0
  94. package/bin/runners/lib/agent-firewall/policy/loader.js +451 -0
  95. package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +50 -0
  96. package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +50 -0
  97. package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +79 -0
  98. package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +227 -0
  99. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +191 -0
  100. package/bin/runners/lib/agent-firewall/policy/rules/scope.js +93 -0
  101. package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +57 -0
  102. package/bin/runners/lib/agent-firewall/policy/schema.json +183 -0
  103. package/bin/runners/lib/agent-firewall/policy/verdict.js +54 -0
  104. package/bin/runners/lib/agent-firewall/proposal/extractor.js +394 -0
  105. package/bin/runners/lib/agent-firewall/proposal/index.js +212 -0
  106. package/bin/runners/lib/agent-firewall/proposal/schema.js +251 -0
  107. package/bin/runners/lib/agent-firewall/proposal/validator.js +386 -0
  108. package/bin/runners/lib/agent-firewall/reality/index.js +332 -0
  109. package/bin/runners/lib/agent-firewall/reality/state.js +625 -0
  110. package/bin/runners/lib/agent-firewall/reality/watcher.js +322 -0
  111. package/bin/runners/lib/agent-firewall/risk/index.js +173 -0
  112. package/bin/runners/lib/agent-firewall/risk/scorer.js +328 -0
  113. package/bin/runners/lib/agent-firewall/risk/thresholds.js +322 -0
  114. package/bin/runners/lib/agent-firewall/risk/vectors.js +421 -0
  115. package/bin/runners/lib/agent-firewall/session/collector.js +451 -0
  116. package/bin/runners/lib/agent-firewall/session/index.js +26 -0
  117. package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +472 -0
  118. package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +346 -0
  119. package/bin/runners/lib/agent-firewall/simulator/index.js +181 -0
  120. package/bin/runners/lib/agent-firewall/simulator/route-validator.js +380 -0
  121. package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +661 -0
  122. package/bin/runners/lib/agent-firewall/time-machine/index.js +267 -0
  123. package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +436 -0
  124. package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +490 -0
  125. package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +530 -0
  126. package/bin/runners/lib/agent-firewall/truthpack/index.js +67 -0
  127. package/bin/runners/lib/agent-firewall/truthpack/loader.js +137 -0
  128. package/bin/runners/lib/agent-firewall/unblock/planner.js +337 -0
  129. package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +118 -0
  130. package/bin/runners/lib/ai-bridge.js +416 -0
  131. package/bin/runners/lib/analysis-core.js +309 -0
  132. package/bin/runners/lib/analyzers.js +2500 -0
  133. package/bin/runners/lib/api-client.js +269 -0
  134. package/bin/runners/lib/approve-output.js +235 -0
  135. package/bin/runners/lib/artifact-envelope.js +540 -0
  136. package/bin/runners/lib/assets/vibecheck-logo.png +0 -0
  137. package/bin/runners/lib/audit-bridge.js +391 -0
  138. package/bin/runners/lib/auth-shared.js +977 -0
  139. package/bin/runners/lib/auth-truth.js +193 -0
  140. package/bin/runners/lib/auth.js +215 -0
  141. package/bin/runners/lib/authority-badge.js +425 -0
  142. package/bin/runners/lib/backup.js +62 -0
  143. package/bin/runners/lib/billing.js +107 -0
  144. package/bin/runners/lib/checkpoint.js +941 -0
  145. package/bin/runners/lib/claims.js +118 -0
  146. package/bin/runners/lib/classify-output.js +204 -0
  147. package/bin/runners/lib/cleanup/engine.js +571 -0
  148. package/bin/runners/lib/cleanup/index.js +53 -0
  149. package/bin/runners/lib/cleanup/output.js +375 -0
  150. package/bin/runners/lib/cleanup/rules.js +1060 -0
  151. package/bin/runners/lib/cli-output.js +400 -0
  152. package/bin/runners/lib/cli-ui.js +540 -0
  153. package/bin/runners/lib/compliance-bridge-new.js +0 -0
  154. package/bin/runners/lib/compliance-bridge.js +165 -0
  155. package/bin/runners/lib/contracts/auth-contract.js +202 -0
  156. package/bin/runners/lib/contracts/env-contract.js +181 -0
  157. package/bin/runners/lib/contracts/external-contract.js +206 -0
  158. package/bin/runners/lib/contracts/guard.js +168 -0
  159. package/bin/runners/lib/contracts/index.js +89 -0
  160. package/bin/runners/lib/contracts/plan-validator.js +311 -0
  161. package/bin/runners/lib/contracts/route-contract.js +199 -0
  162. package/bin/runners/lib/contracts.js +804 -0
  163. package/bin/runners/lib/default-config.js +127 -0
  164. package/bin/runners/lib/detect.js +89 -0
  165. package/bin/runners/lib/detectors-v2.js +622 -0
  166. package/bin/runners/lib/doctor/autofix.js +254 -0
  167. package/bin/runners/lib/doctor/diagnosis-receipt.js +454 -0
  168. package/bin/runners/lib/doctor/failure-signatures.js +526 -0
  169. package/bin/runners/lib/doctor/fix-script.js +336 -0
  170. package/bin/runners/lib/doctor/index.js +37 -0
  171. package/bin/runners/lib/doctor/modules/build-tools.js +453 -0
  172. package/bin/runners/lib/doctor/modules/dependencies.js +325 -0
  173. package/bin/runners/lib/doctor/modules/index.js +105 -0
  174. package/bin/runners/lib/doctor/modules/network.js +250 -0
  175. package/bin/runners/lib/doctor/modules/os-quirks.js +706 -0
  176. package/bin/runners/lib/doctor/modules/project.js +312 -0
  177. package/bin/runners/lib/doctor/modules/repo-integrity.js +485 -0
  178. package/bin/runners/lib/doctor/modules/runtime.js +224 -0
  179. package/bin/runners/lib/doctor/modules/security.js +350 -0
  180. package/bin/runners/lib/doctor/modules/system.js +213 -0
  181. package/bin/runners/lib/doctor/modules/vibecheck.js +394 -0
  182. package/bin/runners/lib/doctor/reporter.js +262 -0
  183. package/bin/runners/lib/doctor/safe-repair.js +384 -0
  184. package/bin/runners/lib/doctor/service.js +262 -0
  185. package/bin/runners/lib/doctor/types.js +113 -0
  186. package/bin/runners/lib/doctor/ui.js +263 -0
  187. package/bin/runners/lib/doctor-enhanced.js +233 -0
  188. package/bin/runners/lib/doctor-output.js +226 -0
  189. package/bin/runners/lib/doctor-v2.js +608 -0
  190. package/bin/runners/lib/drift.js +425 -0
  191. package/bin/runners/lib/enforcement.js +72 -0
  192. package/bin/runners/lib/engine/ast-cache.js +210 -0
  193. package/bin/runners/lib/engine/auth-extractor.js +211 -0
  194. package/bin/runners/lib/engine/billing-extractor.js +112 -0
  195. package/bin/runners/lib/engine/enforcement-extractor.js +100 -0
  196. package/bin/runners/lib/engine/env-extractor.js +207 -0
  197. package/bin/runners/lib/engine/express-extractor.js +208 -0
  198. package/bin/runners/lib/engine/extractors.js +849 -0
  199. package/bin/runners/lib/engine/index.js +207 -0
  200. package/bin/runners/lib/engine/repo-index.js +514 -0
  201. package/bin/runners/lib/engine/types.js +124 -0
  202. package/bin/runners/lib/engines/accessibility-engine.js +190 -0
  203. package/bin/runners/lib/engines/api-consistency-engine.js +162 -0
  204. package/bin/runners/lib/engines/ast-cache.js +99 -0
  205. package/bin/runners/lib/engines/attack-detector.js +1192 -0
  206. package/bin/runners/lib/engines/code-quality-engine.js +255 -0
  207. package/bin/runners/lib/engines/console-logs-engine.js +115 -0
  208. package/bin/runners/lib/engines/cross-file-analysis-engine.js +268 -0
  209. package/bin/runners/lib/engines/dead-code-engine.js +198 -0
  210. package/bin/runners/lib/engines/deprecated-api-engine.js +226 -0
  211. package/bin/runners/lib/engines/empty-catch-engine.js +150 -0
  212. package/bin/runners/lib/engines/file-filter.js +131 -0
  213. package/bin/runners/lib/engines/hardcoded-secrets-engine.js +251 -0
  214. package/bin/runners/lib/engines/mock-data-engine.js +272 -0
  215. package/bin/runners/lib/engines/parallel-processor.js +71 -0
  216. package/bin/runners/lib/engines/performance-issues-engine.js +265 -0
  217. package/bin/runners/lib/engines/security-vulnerabilities-engine.js +243 -0
  218. package/bin/runners/lib/engines/todo-fixme-engine.js +115 -0
  219. package/bin/runners/lib/engines/type-aware-engine.js +152 -0
  220. package/bin/runners/lib/engines/unsafe-regex-engine.js +225 -0
  221. package/bin/runners/lib/engines/vibecheck-engines/README.md +53 -0
  222. package/bin/runners/lib/engines/vibecheck-engines/index.js +15 -0
  223. package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
  224. package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
  225. package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
  226. package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
  227. package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
  228. package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
  229. package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
  230. package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +139 -0
  231. package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
  232. package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
  233. package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
  234. package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
  235. package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
  236. package/bin/runners/lib/engines/vibecheck-engines/package.json +13 -0
  237. package/bin/runners/lib/enterprise-detect.js +603 -0
  238. package/bin/runners/lib/enterprise-init.js +942 -0
  239. package/bin/runners/lib/entitlements-v2.js +265 -0
  240. package/bin/runners/lib/entitlements.generated.js +0 -0
  241. package/bin/runners/lib/entitlements.js +340 -0
  242. package/bin/runners/lib/env-resolver.js +417 -0
  243. package/bin/runners/lib/env-template.js +66 -0
  244. package/bin/runners/lib/env.js +189 -0
  245. package/bin/runners/lib/error-handler.js +368 -0
  246. package/bin/runners/lib/error-messages.js +289 -0
  247. package/bin/runners/lib/evidence-pack.js +684 -0
  248. package/bin/runners/lib/exit-codes.js +275 -0
  249. package/bin/runners/lib/extractors/client-calls.js +990 -0
  250. package/bin/runners/lib/extractors/fastify-route-dump.js +573 -0
  251. package/bin/runners/lib/extractors/fastify-routes.js +426 -0
  252. package/bin/runners/lib/extractors/index.js +363 -0
  253. package/bin/runners/lib/extractors/next-routes.js +524 -0
  254. package/bin/runners/lib/extractors/proof-graph.js +431 -0
  255. package/bin/runners/lib/extractors/route-matcher.js +451 -0
  256. package/bin/runners/lib/extractors/truthpack-v2.js +377 -0
  257. package/bin/runners/lib/extractors/ui-bindings.js +547 -0
  258. package/bin/runners/lib/finding-id.js +69 -0
  259. package/bin/runners/lib/finding-sorter.js +89 -0
  260. package/bin/runners/lib/findings-schema.js +281 -0
  261. package/bin/runners/lib/fingerprint.js +377 -0
  262. package/bin/runners/lib/firewall-prompt.js +50 -0
  263. package/bin/runners/lib/fix-output.js +228 -0
  264. package/bin/runners/lib/global-flags.js +250 -0
  265. package/bin/runners/lib/graph/graph-builder.js +265 -0
  266. package/bin/runners/lib/graph/html-renderer.js +413 -0
  267. package/bin/runners/lib/graph/index.js +32 -0
  268. package/bin/runners/lib/graph/runtime-collector.js +215 -0
  269. package/bin/runners/lib/graph/static-extractor.js +518 -0
  270. package/bin/runners/lib/help-formatter.js +413 -0
  271. package/bin/runners/lib/html-proof-report.js +913 -0
  272. package/bin/runners/lib/html-report.js +650 -0
  273. package/bin/runners/lib/init-wizard.js +601 -0
  274. package/bin/runners/lib/interactive-menu.js +1496 -0
  275. package/bin/runners/lib/json-output.js +76 -0
  276. package/bin/runners/lib/llm.js +75 -0
  277. package/bin/runners/lib/logger.js +38 -0
  278. package/bin/runners/lib/meter.js +61 -0
  279. package/bin/runners/lib/missions/briefing.js +427 -0
  280. package/bin/runners/lib/missions/checkpoint.js +753 -0
  281. package/bin/runners/lib/missions/evidence.js +126 -0
  282. package/bin/runners/lib/missions/hardening.js +851 -0
  283. package/bin/runners/lib/missions/plan.js +648 -0
  284. package/bin/runners/lib/missions/safety-gates.js +645 -0
  285. package/bin/runners/lib/missions/schema.js +478 -0
  286. package/bin/runners/lib/missions/templates.js +317 -0
  287. package/bin/runners/lib/next-action.js +560 -0
  288. package/bin/runners/lib/packs/bundle.js +675 -0
  289. package/bin/runners/lib/packs/evidence-pack.js +671 -0
  290. package/bin/runners/lib/packs/pack-factory.js +837 -0
  291. package/bin/runners/lib/packs/permissions-pack.js +686 -0
  292. package/bin/runners/lib/packs/proof-graph-pack.js +779 -0
  293. package/bin/runners/lib/patch.js +40 -0
  294. package/bin/runners/lib/permissions/auth-model.js +213 -0
  295. package/bin/runners/lib/permissions/idor-prover.js +205 -0
  296. package/bin/runners/lib/permissions/index.js +45 -0
  297. package/bin/runners/lib/permissions/matrix-builder.js +198 -0
  298. package/bin/runners/lib/pkgjson.js +28 -0
  299. package/bin/runners/lib/policy.js +295 -0
  300. package/bin/runners/lib/polish/accessibility.js +62 -0
  301. package/bin/runners/lib/polish/analyzer.js +93 -0
  302. package/bin/runners/lib/polish/backend.js +87 -0
  303. package/bin/runners/lib/polish/configuration.js +83 -0
  304. package/bin/runners/lib/polish/documentation.js +83 -0
  305. package/bin/runners/lib/polish/frontend.js +817 -0
  306. package/bin/runners/lib/polish/index.js +27 -0
  307. package/bin/runners/lib/polish/infrastructure.js +80 -0
  308. package/bin/runners/lib/polish/internationalization.js +85 -0
  309. package/bin/runners/lib/polish/libraries.js +180 -0
  310. package/bin/runners/lib/polish/observability.js +75 -0
  311. package/bin/runners/lib/polish/performance.js +64 -0
  312. package/bin/runners/lib/polish/privacy.js +110 -0
  313. package/bin/runners/lib/polish/resilience.js +92 -0
  314. package/bin/runners/lib/polish/security.js +78 -0
  315. package/bin/runners/lib/polish/seo.js +71 -0
  316. package/bin/runners/lib/polish/styles.js +62 -0
  317. package/bin/runners/lib/polish/utils.js +104 -0
  318. package/bin/runners/lib/preflight.js +142 -0
  319. package/bin/runners/lib/prerequisites.js +149 -0
  320. package/bin/runners/lib/prove-output.js +220 -0
  321. package/bin/runners/lib/reality/correlation-detectors.js +359 -0
  322. package/bin/runners/lib/reality/index.js +318 -0
  323. package/bin/runners/lib/reality/request-hashing.js +416 -0
  324. package/bin/runners/lib/reality/request-mapper.js +453 -0
  325. package/bin/runners/lib/reality/safety-rails.js +463 -0
  326. package/bin/runners/lib/reality/semantic-snapshot.js +408 -0
  327. package/bin/runners/lib/reality/toast-detector.js +393 -0
  328. package/bin/runners/lib/reality-findings.js +84 -0
  329. package/bin/runners/lib/reality-output.js +231 -0
  330. package/bin/runners/lib/receipts.js +179 -0
  331. package/bin/runners/lib/redact.js +29 -0
  332. package/bin/runners/lib/replay/capsule-manager.js +154 -0
  333. package/bin/runners/lib/replay/index.js +263 -0
  334. package/bin/runners/lib/replay/player.js +348 -0
  335. package/bin/runners/lib/replay/recorder.js +331 -0
  336. package/bin/runners/lib/report-engine.js +626 -0
  337. package/bin/runners/lib/report-html.js +1233 -0
  338. package/bin/runners/lib/report-output.js +366 -0
  339. package/bin/runners/lib/report-templates.js +967 -0
  340. package/bin/runners/lib/report.js +135 -0
  341. package/bin/runners/lib/route-detection.js +1209 -0
  342. package/bin/runners/lib/route-truth.js +1322 -0
  343. package/bin/runners/lib/safelist/index.js +96 -0
  344. package/bin/runners/lib/safelist/integration.js +334 -0
  345. package/bin/runners/lib/safelist/matcher.js +696 -0
  346. package/bin/runners/lib/safelist/schema.js +948 -0
  347. package/bin/runners/lib/safelist/store.js +438 -0
  348. package/bin/runners/lib/sandbox/index.js +59 -0
  349. package/bin/runners/lib/sandbox/proof-chain.js +399 -0
  350. package/bin/runners/lib/sandbox/sandbox-runner.js +205 -0
  351. package/bin/runners/lib/sandbox/worktree.js +174 -0
  352. package/bin/runners/lib/scan-cache.js +330 -0
  353. package/bin/runners/lib/scan-output-schema.js +344 -0
  354. package/bin/runners/lib/scan-output.js +631 -0
  355. package/bin/runners/lib/scan-runner.js +135 -0
  356. package/bin/runners/lib/schema-validator.js +350 -0
  357. package/bin/runners/lib/schemas/ajv-validator.js +464 -0
  358. package/bin/runners/lib/schemas/contracts.schema.json +160 -0
  359. package/bin/runners/lib/schemas/error-envelope.schema.json +105 -0
  360. package/bin/runners/lib/schemas/finding-v3.schema.json +151 -0
  361. package/bin/runners/lib/schemas/finding.schema.json +100 -0
  362. package/bin/runners/lib/schemas/mission-pack.schema.json +206 -0
  363. package/bin/runners/lib/schemas/proof-graph.schema.json +176 -0
  364. package/bin/runners/lib/schemas/reality-report.schema.json +162 -0
  365. package/bin/runners/lib/schemas/report-artifact.schema.json +120 -0
  366. package/bin/runners/lib/schemas/run-request.schema.json +108 -0
  367. package/bin/runners/lib/schemas/share-pack.schema.json +180 -0
  368. package/bin/runners/lib/schemas/ship-manifest.schema.json +251 -0
  369. package/bin/runners/lib/schemas/ship-report.schema.json +117 -0
  370. package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -0
  371. package/bin/runners/lib/schemas/validator.js +465 -0
  372. package/bin/runners/lib/schemas/verdict.schema.json +140 -0
  373. package/bin/runners/lib/score-history.js +282 -0
  374. package/bin/runners/lib/security-bridge.js +249 -0
  375. package/bin/runners/lib/server-usage.js +513 -0
  376. package/bin/runners/lib/share-pack.js +239 -0
  377. package/bin/runners/lib/ship-gate.js +832 -0
  378. package/bin/runners/lib/ship-manifest.js +1153 -0
  379. package/bin/runners/lib/ship-output-enterprise.js +239 -0
  380. package/bin/runners/lib/ship-output.js +1128 -0
  381. package/bin/runners/lib/snippets.js +67 -0
  382. package/bin/runners/lib/status-output.js +340 -0
  383. package/bin/runners/lib/terminal-ui.js +356 -0
  384. package/bin/runners/lib/truth.js +1691 -0
  385. package/bin/runners/lib/ui.js +562 -0
  386. package/bin/runners/lib/unified-cli-output.js +947 -0
  387. package/bin/runners/lib/unified-output.js +197 -0
  388. package/bin/runners/lib/upsell.js +410 -0
  389. package/bin/runners/lib/usage.js +153 -0
  390. package/bin/runners/lib/validate-patch.js +156 -0
  391. package/bin/runners/lib/verdict-engine.js +628 -0
  392. package/bin/runners/lib/verification.js +345 -0
  393. package/bin/runners/lib/why-tree.js +650 -0
  394. package/bin/runners/reality/engine.js +917 -0
  395. package/bin/runners/reality/flows.js +122 -0
  396. package/bin/runners/reality/report.js +378 -0
  397. package/bin/runners/reality/session.js +193 -0
  398. package/bin/runners/runAIAgent.js +229 -0
  399. package/bin/runners/runAgent.d.ts +5 -0
  400. package/bin/runners/runAgent.js +161 -0
  401. package/bin/runners/runAllowlist.js +418 -0
  402. package/bin/runners/runApprove.js +320 -0
  403. package/bin/runners/runAudit.js +692 -0
  404. package/bin/runners/runAuth.js +731 -0
  405. package/bin/runners/runCI.js +353 -0
  406. package/bin/runners/runCheckpoint.js +530 -0
  407. package/bin/runners/runClassify.js +928 -0
  408. package/bin/runners/runCleanup.js +343 -0
  409. package/bin/runners/runContext.d.ts +4 -0
  410. package/bin/runners/runContext.js +175 -0
  411. package/bin/runners/runDoctor.js +877 -0
  412. package/bin/runners/runEvidencePack.js +362 -0
  413. package/bin/runners/runFirewall.d.ts +5 -0
  414. package/bin/runners/runFirewall.js +134 -0
  415. package/bin/runners/runFirewallHook.d.ts +5 -0
  416. package/bin/runners/runFirewallHook.js +56 -0
  417. package/bin/runners/runFix.js +1355 -0
  418. package/bin/runners/runForge.js +380 -0
  419. package/bin/runners/runGuard.js +262 -0
  420. package/bin/runners/runInit.js +1927 -0
  421. package/bin/runners/runIntent.js +906 -0
  422. package/bin/runners/runKickoff.js +878 -0
  423. package/bin/runners/runLabs.js +424 -0
  424. package/bin/runners/runLaunch.js +2000 -0
  425. package/bin/runners/runLink.js +785 -0
  426. package/bin/runners/runMcp.js +1875 -0
  427. package/bin/runners/runPacks.js +2089 -0
  428. package/bin/runners/runPolish.d.ts +4 -0
  429. package/bin/runners/runPolish.js +390 -0
  430. package/bin/runners/runPromptFirewall.js +211 -0
  431. package/bin/runners/runProve.js +1411 -0
  432. package/bin/runners/runQuickstart.js +531 -0
  433. package/bin/runners/runReality.js +2260 -0
  434. package/bin/runners/runReport.js +726 -0
  435. package/bin/runners/runRuntime.js +110 -0
  436. package/bin/runners/runSafelist.js +1190 -0
  437. package/bin/runners/runScan.js +688 -0
  438. package/bin/runners/runShield.js +1282 -0
  439. package/bin/runners/runShip.js +1660 -0
  440. package/bin/runners/runTruth.d.ts +5 -0
  441. package/bin/runners/runTruth.js +101 -0
  442. package/bin/runners/runValidate.js +179 -0
  443. package/bin/runners/runWatch.js +478 -0
  444. package/bin/runners/utils.js +360 -0
  445. package/bin/scan.js +617 -0
  446. package/bin/vibecheck.js +1617 -0
  447. package/dist/guardrail/index.d.ts +2405 -0
  448. package/dist/guardrail/index.js +9747 -0
  449. package/dist/guardrail/index.js.map +1 -0
  450. package/dist/scanner/index.d.ts +282 -0
  451. package/dist/scanner/index.js +3395 -0
  452. package/dist/scanner/index.js.map +1 -0
  453. package/package.json +123 -104
  454. package/README.md +0 -491
  455. package/dist/index.js +0 -99711
  456. package/dist/index.js.map +0 -1
@@ -0,0 +1,1875 @@
1
+ /**
2
+ * Vibecheck MCP Server CLI - HARDENED v4.1.0
3
+ *
4
+ * "My IDE is now VibeCheck-powered"
5
+ *
6
+ * HARDENING FEATURES:
7
+ * - Comprehensive error codes and structured error handling
8
+ * - Retry logic with exponential backoff
9
+ * - Input validation and sanitization
10
+ * - Lock file to prevent multiple instances
11
+ * - Startup verification with health probe
12
+ * - Structured logging with log levels
13
+ * - Connection testing and verification
14
+ * - Graceful degradation and recovery
15
+ * - Signal handling and cleanup
16
+ * - Timeout management
17
+ * - Secret redaction
18
+ *
19
+ * Subcommands:
20
+ * serve - Start the MCP server (default)
21
+ * doctor - Health check with self-healing
22
+ * config - Print IDE-specific connection configs
23
+ * manifest - Machine-readable tool manifest for IDEs
24
+ * status - Check server health
25
+ * test - Test MCP server connection
26
+ *
27
+ * @version 4.1.0
28
+ */
29
+
30
+ "use strict";
31
+
32
+ const path = require("path");
33
+ const fs = require("fs");
34
+ const os = require("os");
35
+ const net = require("net");
36
+ const crypto = require("crypto");
37
+ const { spawn, execSync } = require("child_process");
38
+ const { EXIT } = require("./lib/exit-codes");
39
+ const { parseGlobalFlags, shouldSuppressOutput, isJsonMode } = require("./lib/global-flags");
40
+
41
+ // ============================================================================
42
+ // CONSTANTS & CONFIGURATION
43
+ // ============================================================================
44
+ const VERSION = "4.1.0";
45
+ const DEFAULT_PORT = 3847;
46
+ const PORT_RANGE = { min: 3847, max: 3867 };
47
+ const FALLBACK_PORTS = [8000, 8080, 9000, 9999, 3000];
48
+ const SERVER_NAME = "vibecheck-mcp";
49
+ const LOCK_FILE_NAME = ".vibecheck-mcp.lock";
50
+ const LOG_FILE_NAME = "mcp-server.log";
51
+ const MAX_LOG_SIZE = 10 * 1024 * 1024; // 10MB
52
+
53
+ // Timeouts (ms)
54
+ const TIMEOUTS = {
55
+ STARTUP: 10000, // Max time to wait for server startup
56
+ HEALTH_PROBE: 3000, // Health check timeout
57
+ SHUTDOWN: 5000, // Graceful shutdown timeout
58
+ RETRY_BASE: 1000, // Base retry delay
59
+ RETRY_MAX: 30000, // Max retry delay
60
+ PORT_CHECK: 1000, // Port availability check
61
+ };
62
+
63
+ // Retry configuration
64
+ const RETRY_CONFIG = {
65
+ maxAttempts: 3,
66
+ backoffMultiplier: 2,
67
+ jitter: 0.1,
68
+ };
69
+
70
+ // Error codes
71
+ const ERROR_CODES = {
72
+ SUCCESS: 0,
73
+ VALIDATION_ERROR: 1,
74
+ SERVER_NOT_FOUND: 2,
75
+ PORT_UNAVAILABLE: 3,
76
+ STARTUP_FAILED: 4,
77
+ ALREADY_RUNNING: 5,
78
+ CONFIG_ERROR: 6,
79
+ PERMISSION_DENIED: 7,
80
+ NETWORK_ERROR: 8,
81
+ TIMEOUT: 9,
82
+ INTERNAL_ERROR: 10,
83
+ NOT_CONFIGURED: 11,
84
+ HEALTH_CHECK_FAILED: 12,
85
+ };
86
+
87
+ // ============================================================================
88
+ // ANSI STYLING (Safe for non-TTY)
89
+ // ============================================================================
90
+ const SUPPORTS_COLOR = process.stdout.isTTY && !process.env.NO_COLOR;
91
+ const c = SUPPORTS_COLOR ? {
92
+ reset: "\x1b[0m",
93
+ bold: "\x1b[1m",
94
+ dim: "\x1b[2m",
95
+ italic: "\x1b[3m",
96
+ cyan: "\x1b[36m",
97
+ green: "\x1b[32m",
98
+ yellow: "\x1b[33m",
99
+ red: "\x1b[31m",
100
+ magenta: "\x1b[35m",
101
+ blue: "\x1b[34m",
102
+ gray: "\x1b[90m",
103
+ bgGreen: "\x1b[42m",
104
+ bgRed: "\x1b[41m",
105
+ bgYellow: "\x1b[43m",
106
+ bgBlue: "\x1b[44m",
107
+ } : Object.fromEntries([
108
+ "reset", "bold", "dim", "italic", "cyan", "green", "yellow", "red",
109
+ "magenta", "blue", "gray", "bgGreen", "bgRed", "bgYellow", "bgBlue"
110
+ ].map(k => [k, ""]));
111
+
112
+ const sym = {
113
+ check: SUPPORTS_COLOR ? "✓" : "[OK]",
114
+ cross: SUPPORTS_COLOR ? "✗" : "[FAIL]",
115
+ warn: SUPPORTS_COLOR ? "⚠" : "[WARN]",
116
+ info: SUPPORTS_COLOR ? "ℹ" : "[INFO]",
117
+ arrow: SUPPORTS_COLOR ? "→" : "->",
118
+ box: SUPPORTS_COLOR ? "█" : "#",
119
+ dot: SUPPORTS_COLOR ? "•" : "-",
120
+ rocket: SUPPORTS_COLOR ? "🚀" : "[*]",
121
+ plug: SUPPORTS_COLOR ? "🔌" : "[PLUG]",
122
+ wrench: SUPPORTS_COLOR ? "🔧" : "[FIX]",
123
+ heart: SUPPORTS_COLOR ? "💚" : "[OK]",
124
+ shield: SUPPORTS_COLOR ? "🛡️" : "[SHIELD]",
125
+ brain: SUPPORTS_COLOR ? "🧠" : "[AI]",
126
+ clipboard: SUPPORTS_COLOR ? "📋" : "[COPY]",
127
+ folder: SUPPORTS_COLOR ? "📁" : "[DIR]",
128
+ lock: SUPPORTS_COLOR ? "🔒" : "[LOCK]",
129
+ key: SUPPORTS_COLOR ? "🔑" : "[KEY]",
130
+ timer: SUPPORTS_COLOR ? "⏱️" : "[TIME]",
131
+ retry: SUPPORTS_COLOR ? "🔄" : "[RETRY]",
132
+ };
133
+
134
+ // ============================================================================
135
+ // STRUCTURED LOGGING
136
+ // ============================================================================
137
+ const LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3, silent: 4 };
138
+ let currentLogLevel = LOG_LEVELS.info;
139
+ let logFile = null;
140
+
141
+ function setLogLevel(level) {
142
+ currentLogLevel = LOG_LEVELS[level] ?? LOG_LEVELS.info;
143
+ }
144
+
145
+ function getLogFilePath(projectPath) {
146
+ const logDir = path.join(projectPath, ".vibecheck", "logs");
147
+ return path.join(logDir, LOG_FILE_NAME);
148
+ }
149
+
150
+ function initLogFile(projectPath) {
151
+ try {
152
+ const logPath = getLogFilePath(projectPath);
153
+ const logDir = path.dirname(logPath);
154
+
155
+ if (!fs.existsSync(logDir)) {
156
+ fs.mkdirSync(logDir, { recursive: true });
157
+ }
158
+
159
+ // Rotate log if too large
160
+ if (fs.existsSync(logPath)) {
161
+ const stats = fs.statSync(logPath);
162
+ if (stats.size > MAX_LOG_SIZE) {
163
+ const backupPath = `${logPath}.1`;
164
+ if (fs.existsSync(backupPath)) {
165
+ fs.unlinkSync(backupPath);
166
+ }
167
+ fs.renameSync(logPath, backupPath);
168
+ }
169
+ }
170
+
171
+ logFile = fs.createWriteStream(logPath, { flags: "a" });
172
+ } catch {
173
+ // Logging is best-effort
174
+ }
175
+ }
176
+
177
+ function log(level, message, meta = {}) {
178
+ if (LOG_LEVELS[level] < currentLogLevel) return;
179
+
180
+ const timestamp = new Date().toISOString();
181
+ const entry = {
182
+ timestamp,
183
+ level,
184
+ message,
185
+ ...meta,
186
+ };
187
+
188
+ // Write to log file
189
+ if (logFile) {
190
+ try {
191
+ logFile.write(JSON.stringify(entry) + "\n");
192
+ } catch {
193
+ // Best effort
194
+ }
195
+ }
196
+
197
+ // Console output (only for warn/error or if debug mode)
198
+ if (level === "error") {
199
+ console.error(`${c.red}[ERROR]${c.reset} ${message}`);
200
+ } else if (level === "warn" && currentLogLevel <= LOG_LEVELS.warn) {
201
+ console.error(`${c.yellow}[WARN]${c.reset} ${message}`);
202
+ } else if (level === "debug" && currentLogLevel === LOG_LEVELS.debug) {
203
+ console.error(`${c.gray}[DEBUG]${c.reset} ${message}`);
204
+ }
205
+ }
206
+
207
+ // ============================================================================
208
+ // INPUT VALIDATION & SANITIZATION
209
+ // ============================================================================
210
+
211
+ /**
212
+ * Validate and sanitize a file path
213
+ */
214
+ function validatePath(inputPath, basePath = process.cwd()) {
215
+ if (!inputPath || typeof inputPath !== "string") {
216
+ return { valid: false, error: "Path must be a non-empty string", code: ERROR_CODES.VALIDATION_ERROR };
217
+ }
218
+
219
+ // Length check
220
+ if (inputPath.length > 4096) {
221
+ return { valid: false, error: "Path too long (max 4096 chars)", code: ERROR_CODES.VALIDATION_ERROR };
222
+ }
223
+
224
+ // Null byte check
225
+ if (inputPath.includes("\0")) {
226
+ return { valid: false, error: "Path contains invalid characters", code: ERROR_CODES.VALIDATION_ERROR };
227
+ }
228
+
229
+ try {
230
+ const resolved = path.resolve(basePath, inputPath);
231
+ const normalizedBase = path.resolve(basePath).replace(/\\/g, "/").toLowerCase();
232
+ const normalizedPath = resolved.replace(/\\/g, "/").toLowerCase();
233
+
234
+ // Path traversal check
235
+ const baseWithSep = normalizedBase.endsWith("/") ? normalizedBase : normalizedBase + "/";
236
+ if (!normalizedPath.startsWith(baseWithSep) && normalizedPath !== normalizedBase) {
237
+ // Allow if it's the base path itself or a child
238
+ if (!normalizedPath.startsWith(normalizedBase)) {
239
+ return { valid: false, error: "Path traversal detected", code: ERROR_CODES.VALIDATION_ERROR };
240
+ }
241
+ }
242
+
243
+ return { valid: true, path: resolved };
244
+ } catch (err) {
245
+ return { valid: false, error: `Invalid path: ${err.message}`, code: ERROR_CODES.VALIDATION_ERROR };
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Validate port number
251
+ */
252
+ function validatePort(port) {
253
+ const num = parseInt(port, 10);
254
+ if (isNaN(num) || num < 1 || num > 65535) {
255
+ return { valid: false, error: `Invalid port: ${port} (must be 1-65535)`, code: ERROR_CODES.VALIDATION_ERROR };
256
+ }
257
+ return { valid: true, port: num };
258
+ }
259
+
260
+ /**
261
+ * Validate IDE name
262
+ */
263
+ function validateIde(ide) {
264
+ const validIdes = ["cursor", "windsurf", "claude", "vscode", "all"];
265
+ const normalized = (ide || "all").toLowerCase();
266
+ if (!validIdes.includes(normalized)) {
267
+ return { valid: false, error: `Invalid IDE: ${ide}. Valid options: ${validIdes.join(", ")}`, code: ERROR_CODES.VALIDATION_ERROR };
268
+ }
269
+ return { valid: true, ide: normalized };
270
+ }
271
+
272
+ /**
273
+ * Redact sensitive values from output
274
+ */
275
+ function redactSecrets(text) {
276
+ if (typeof text !== "string") return text;
277
+
278
+ const patterns = [
279
+ [/(?:sk_live_|sk_test_)[a-zA-Z0-9]{24,}/g, "[STRIPE_KEY_REDACTED]"],
280
+ [/(?:AKIA|ASIA)[A-Z0-9]{16}/g, "[AWS_KEY_REDACTED]"],
281
+ [/ghp_[a-zA-Z0-9]{36}/g, "[GITHUB_TOKEN_REDACTED]"],
282
+ [/xox[baprs]-[0-9A-Za-z\-]{10,}/g, "[SLACK_TOKEN_REDACTED]"],
283
+ [/eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*/g, "[JWT_REDACTED]"],
284
+ [/(?:password|secret|token|apikey|api_key)["']?\s*[:=]\s*["'][^"']{8,}["']/gi, "[SECRET_REDACTED]"],
285
+ [/VIBECHECK_API_KEY=\S+/g, "VIBECHECK_API_KEY=[REDACTED]"],
286
+ ];
287
+
288
+ let result = text;
289
+ for (const [pattern, replacement] of patterns) {
290
+ result = result.replace(pattern, replacement);
291
+ }
292
+ return result;
293
+ }
294
+
295
+ // ============================================================================
296
+ // LOCK FILE MANAGEMENT
297
+ // ============================================================================
298
+
299
+ function getLockFilePath(projectPath) {
300
+ return path.join(projectPath, ".vibecheck", LOCK_FILE_NAME);
301
+ }
302
+
303
+ function createLockFile(projectPath, pid, port) {
304
+ const lockPath = getLockFilePath(projectPath);
305
+ const lockDir = path.dirname(lockPath);
306
+
307
+ try {
308
+ if (!fs.existsSync(lockDir)) {
309
+ fs.mkdirSync(lockDir, { recursive: true });
310
+ }
311
+
312
+ const lockData = {
313
+ pid,
314
+ port,
315
+ startedAt: new Date().toISOString(),
316
+ hostname: os.hostname(),
317
+ nodeVersion: process.version,
318
+ };
319
+
320
+ fs.writeFileSync(lockPath, JSON.stringify(lockData, null, 2));
321
+ log("debug", "Lock file created", { lockPath, pid, port });
322
+ return { success: true };
323
+ } catch (err) {
324
+ log("error", "Failed to create lock file", { error: err.message });
325
+ return { success: false, error: err.message };
326
+ }
327
+ }
328
+
329
+ function readLockFile(projectPath) {
330
+ const lockPath = getLockFilePath(projectPath);
331
+
332
+ try {
333
+ if (!fs.existsSync(lockPath)) {
334
+ return { exists: false };
335
+ }
336
+
337
+ const data = JSON.parse(fs.readFileSync(lockPath, "utf8"));
338
+ return { exists: true, data };
339
+ } catch {
340
+ return { exists: false };
341
+ }
342
+ }
343
+
344
+ function removeLockFile(projectPath) {
345
+ const lockPath = getLockFilePath(projectPath);
346
+
347
+ try {
348
+ if (fs.existsSync(lockPath)) {
349
+ fs.unlinkSync(lockPath);
350
+ log("debug", "Lock file removed", { lockPath });
351
+ }
352
+ return { success: true };
353
+ } catch (err) {
354
+ log("warn", "Failed to remove lock file", { error: err.message });
355
+ return { success: false, error: err.message };
356
+ }
357
+ }
358
+
359
+ function isProcessRunning(pid) {
360
+ try {
361
+ process.kill(pid, 0);
362
+ return true;
363
+ } catch {
364
+ return false;
365
+ }
366
+ }
367
+
368
+ function checkExistingInstance(projectPath) {
369
+ const lock = readLockFile(projectPath);
370
+
371
+ if (!lock.exists) {
372
+ return { running: false };
373
+ }
374
+
375
+ const { pid, port, startedAt } = lock.data;
376
+
377
+ if (isProcessRunning(pid)) {
378
+ return {
379
+ running: true,
380
+ pid,
381
+ port,
382
+ startedAt,
383
+ message: `MCP server already running (PID: ${pid}, Port: ${port})`,
384
+ };
385
+ }
386
+
387
+ // Stale lock file - remove it
388
+ log("info", "Removing stale lock file", { pid });
389
+ removeLockFile(projectPath);
390
+ return { running: false, wasStale: true };
391
+ }
392
+
393
+ // ============================================================================
394
+ // RETRY LOGIC WITH EXPONENTIAL BACKOFF
395
+ // ============================================================================
396
+
397
+ async function sleep(ms) {
398
+ return new Promise(resolve => setTimeout(resolve, ms));
399
+ }
400
+
401
+ function calculateBackoff(attempt, config = RETRY_CONFIG) {
402
+ const baseDelay = TIMEOUTS.RETRY_BASE * Math.pow(config.backoffMultiplier, attempt);
403
+ const jitter = baseDelay * config.jitter * (Math.random() * 2 - 1);
404
+ return Math.min(baseDelay + jitter, TIMEOUTS.RETRY_MAX);
405
+ }
406
+
407
+ async function withRetry(fn, options = {}) {
408
+ const { maxAttempts = RETRY_CONFIG.maxAttempts, onRetry, shouldRetry } = options;
409
+
410
+ let lastError;
411
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
412
+ try {
413
+ return await fn(attempt);
414
+ } catch (err) {
415
+ lastError = err;
416
+
417
+ if (shouldRetry && !shouldRetry(err, attempt)) {
418
+ throw err;
419
+ }
420
+
421
+ if (attempt < maxAttempts - 1) {
422
+ const delay = calculateBackoff(attempt);
423
+ log("debug", `Retry ${attempt + 1}/${maxAttempts} after ${delay}ms`, { error: err.message });
424
+
425
+ if (onRetry) {
426
+ onRetry(err, attempt, delay);
427
+ }
428
+
429
+ await sleep(delay);
430
+ }
431
+ }
432
+ }
433
+
434
+ throw lastError;
435
+ }
436
+
437
+ // ============================================================================
438
+ // PORT MANAGEMENT
439
+ // ============================================================================
440
+
441
+ async function isPortAvailable(port, timeout = TIMEOUTS.PORT_CHECK) {
442
+ return new Promise((resolve) => {
443
+ const server = net.createServer();
444
+ const timeoutId = setTimeout(() => {
445
+ server.close();
446
+ resolve(false);
447
+ }, timeout);
448
+
449
+ server.once("error", () => {
450
+ clearTimeout(timeoutId);
451
+ resolve(false);
452
+ });
453
+
454
+ server.once("listening", () => {
455
+ clearTimeout(timeoutId);
456
+ server.close();
457
+ resolve(true);
458
+ });
459
+
460
+ server.listen(port, "127.0.0.1");
461
+ });
462
+ }
463
+
464
+ async function findAvailablePort(startPort = DEFAULT_PORT) {
465
+ // Try primary range
466
+ for (let port = startPort; port <= PORT_RANGE.max; port++) {
467
+ if (await isPortAvailable(port)) {
468
+ return { port, source: "primary" };
469
+ }
470
+ }
471
+
472
+ // Try fallback ports
473
+ for (const port of FALLBACK_PORTS) {
474
+ if (await isPortAvailable(port)) {
475
+ return { port, source: "fallback" };
476
+ }
477
+ }
478
+
479
+ return { port: null, error: "No available ports found" };
480
+ }
481
+
482
+ function findPortProcess(port) {
483
+ try {
484
+ if (process.platform === "win32") {
485
+ const result = execSync(`netstat -ano | findstr :${port}`, {
486
+ encoding: "utf8",
487
+ timeout: 5000,
488
+ stdio: ["pipe", "pipe", "pipe"],
489
+ });
490
+ const lines = result.trim().split("\n").filter(l => l.includes("LISTENING"));
491
+ if (lines.length > 0) {
492
+ const pid = lines[0].trim().split(/\s+/).pop();
493
+ try {
494
+ const taskResult = execSync(`tasklist /FI "PID eq ${pid}" /FO CSV /NH`, { encoding: "utf8" });
495
+ const name = taskResult.split(",")[0]?.replace(/"/g, "");
496
+ return { pid: parseInt(pid), name };
497
+ } catch {
498
+ return { pid: parseInt(pid), name: "unknown" };
499
+ }
500
+ }
501
+ } else {
502
+ const result = execSync(`lsof -i :${port} -t 2>/dev/null | head -1`, {
503
+ encoding: "utf8",
504
+ timeout: 5000,
505
+ });
506
+ const pid = result.trim();
507
+ if (pid) {
508
+ try {
509
+ const name = execSync(`ps -p ${pid} -o comm= 2>/dev/null`, { encoding: "utf8" }).trim();
510
+ return { pid: parseInt(pid), name };
511
+ } catch {
512
+ return { pid: parseInt(pid), name: "unknown" };
513
+ }
514
+ }
515
+ }
516
+ } catch {
517
+ return null;
518
+ }
519
+ return null;
520
+ }
521
+
522
+ // ============================================================================
523
+ // PATH UTILITIES
524
+ // ============================================================================
525
+
526
+ function getProjectPath(inputPath) {
527
+ const result = validatePath(inputPath || process.cwd());
528
+ if (!result.valid) {
529
+ return process.cwd();
530
+ }
531
+ return result.path;
532
+ }
533
+
534
+ function getMcpServerPath() {
535
+ return path.join(__dirname, "..", "..", "mcp-server", "index.js");
536
+ }
537
+
538
+ function mcpServerExists() {
539
+ const serverPath = getMcpServerPath();
540
+ return fs.existsSync(serverPath);
541
+ }
542
+
543
+ function getCliBinPath() {
544
+ return path.join(__dirname, "..", "vibecheck.js");
545
+ }
546
+
547
+ function getManifestPath() {
548
+ return path.join(__dirname, "..", "..", "mcp-server", "manifest.json");
549
+ }
550
+
551
+ // ============================================================================
552
+ // TOOL REGISTRY
553
+ // ============================================================================
554
+ const TOOL_CATEGORIES = {
555
+ setup: { name: "Setup", icon: "🔧", description: "Project initialization and health" },
556
+ analysis: { name: "Analysis", icon: "🔍", description: "Code analysis and auditing" },
557
+ truth: { name: "Truth", icon: "📜", description: "Ground truth and context generation" },
558
+ enforcement: { name: "Enforcement", icon: "🛡️", description: "Agent firewall and intent tracking" },
559
+ proof: { name: "Proof", icon: "✅", description: "Verification and evidence" },
560
+ automation: { name: "Automation", icon: "🤖", description: "CI/CD and deployment" },
561
+ output: { name: "Output", icon: "📦", description: "Reports and artifacts" },
562
+ config: { name: "Config", icon: "⚙️", description: "Configuration management" },
563
+ account: { name: "Account", icon: "👤", description: "Authentication and billing" },
564
+ };
565
+
566
+ const CANONICAL_TOOLS = [
567
+ { name: "vibecheck.link", cli: "link", category: "setup", tier: "free", description: "One-time project setup: config, contracts, scripts" },
568
+ { name: "vibecheck.kickoff", cli: "kickoff", category: "setup", tier: "free", description: "Interactive guided onboarding experience" },
569
+ { name: "vibecheck.doctor", cli: "doctor", category: "setup", tier: "free", description: "Environment, dependency, and configuration health check" },
570
+ { name: "vibecheck.audit", cli: "audit", category: "analysis", tier: "free", description: "Analyze codebase for issues (routes, env, auth, security, dead code)" },
571
+ { name: "vibecheck.forge", cli: "forge", category: "truth", tier: "free", description: "Generate IDE rules and AI context (.cursorrules, MDC, Copilot)" },
572
+ { name: "vibecheck.shield", cli: "shield", category: "enforcement", tier: "free", description: "Agent Firewall - intercept, validate, enforce AI actions", proOptions: ["enforce", "lock", "install", "check"] },
573
+ { name: "vibecheck.intent", cli: "intent", category: "enforcement", tier: "free", description: "Declare and manage AI session intent for enforcement" },
574
+ { name: "vibecheck.ship", cli: "ship", category: "proof", tier: "pro", description: "Verdict engine - SHIP/WARN/BLOCK with evidence-backed decision" },
575
+ { name: "vibecheck.fix", cli: "fix", category: "proof", tier: "pro", description: "AI-powered auto-fix for detected findings" },
576
+ { name: "vibecheck.prove", cli: "prove", category: "proof", tier: "pro", description: "Full proof loop with runtime verification" },
577
+ { name: "vibecheck.reality", cli: "reality", category: "proof", tier: "pro", description: "Browser-based runtime verification with auth boundary testing" },
578
+ { name: "vibecheck.checkpoint", cli: "checkpoint", category: "proof", tier: "pro", description: "Compare baseline vs current state" },
579
+ { name: "vibecheck.launch", cli: "launch", category: "automation", tier: "pro", description: "CI/CD enforcement - preflight checks and deploy gates" },
580
+ { name: "vibecheck.packs", cli: "packs", category: "output", tier: "free", description: "Generate shareable artifact packs: reports, evidence, proof graphs" },
581
+ { name: "vibecheck.seal", cli: "seal", category: "output", tier: "pro", description: "Generate verification seal/badge with cryptographic attestation" },
582
+ { name: "vibecheck.safelist", cli: "safelist", category: "config", tier: "free", description: "Manage finding safelist for false positives" },
583
+ { name: "vibecheck.auth", cli: "auth", category: "account", tier: "free", description: "Authentication management: login, logout, whoami" },
584
+ { name: "vibecheck.approve", cli: "approve", category: "proof", tier: "pro", description: "Authority verdicts - PROCEED/STOP/DEFER with proofs" },
585
+ { name: "vibecheck.polish", cli: "polish", category: "proof", tier: "pro", description: "Code polish and cleanup" },
586
+ { name: "vibecheck.labs", cli: "labs", category: "setup", tier: "free", description: "Access experimental features" },
587
+ { name: "vibecheck.health", cli: "health", category: "setup", tier: "free", description: "MCP server health check" },
588
+ { name: "vibecheck.manifest", cli: "manifest", category: "setup", tier: "free", description: "Get tool manifest" },
589
+ ];
590
+
591
+ // ============================================================================
592
+ // IDE CONFIGURATIONS
593
+ // ============================================================================
594
+ const IDE_CONFIGS = {
595
+ cursor: {
596
+ name: "Cursor",
597
+ icon: "⌘",
598
+ configPath: {
599
+ win32: path.join(os.homedir(), ".cursor", "mcp.json"),
600
+ darwin: path.join(os.homedir(), ".cursor", "mcp.json"),
601
+ linux: path.join(os.homedir(), ".cursor", "mcp.json"),
602
+ },
603
+ format: "json",
604
+ instructions: "Add to Cursor Settings → MCP, or create ~/.cursor/mcp.json",
605
+ },
606
+ windsurf: {
607
+ name: "Windsurf",
608
+ icon: "🏄",
609
+ configPath: { win32: null, darwin: null, linux: null },
610
+ format: "json",
611
+ instructions: "Open Command Palette → 'Windsurf: Open MCP Config'",
612
+ },
613
+ claude: {
614
+ name: "Claude Desktop",
615
+ icon: "🤖",
616
+ configPath: {
617
+ win32: path.join(process.env.APPDATA || "", "Claude", "claude_desktop_config.json"),
618
+ darwin: path.join(os.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json"),
619
+ linux: path.join(os.homedir(), ".config", "claude", "claude_desktop_config.json"),
620
+ },
621
+ format: "json",
622
+ instructions: "Edit claude_desktop_config.json in your Claude config directory",
623
+ },
624
+ vscode: {
625
+ name: "VS Code (Continue)",
626
+ icon: "💻",
627
+ configPath: {
628
+ win32: path.join(os.homedir(), ".continue", "config.json"),
629
+ darwin: path.join(os.homedir(), ".continue", "config.json"),
630
+ linux: path.join(os.homedir(), ".continue", "config.json"),
631
+ },
632
+ format: "continue",
633
+ instructions: "Add to ~/.continue/config.json if using Continue extension",
634
+ },
635
+ };
636
+
637
+ // ============================================================================
638
+ // STRUCTURED ERROR RESPONSES
639
+ // ============================================================================
640
+
641
+ function createError(code, message, details = {}) {
642
+ return {
643
+ success: false,
644
+ error: {
645
+ code,
646
+ message,
647
+ ...details,
648
+ },
649
+ };
650
+ }
651
+
652
+ function createSuccess(data, meta = {}) {
653
+ return {
654
+ success: true,
655
+ data,
656
+ meta: {
657
+ version: VERSION,
658
+ timestamp: new Date().toISOString(),
659
+ ...meta,
660
+ },
661
+ };
662
+ }
663
+
664
+ // ============================================================================
665
+ // DOCTOR - Health Check with Self-Healing
666
+ // ============================================================================
667
+
668
+ async function runDoctor(opts, globalFlags) {
669
+ const json = isJsonMode(globalFlags);
670
+ const quiet = shouldSuppressOutput(globalFlags);
671
+ const checks = [];
672
+ const fixes = [];
673
+ const projectPath = getProjectPath(opts.path);
674
+
675
+ if (!quiet && !json) {
676
+ console.log(`\n${c.bold}${sym.wrench} VibeCheck MCP Doctor v${VERSION}${c.reset}\n`);
677
+ console.log(`${c.dim}Running comprehensive health checks...${c.reset}\n`);
678
+ }
679
+
680
+ // Check 1: Node.js version
681
+ const nodeVersion = process.version;
682
+ const nodeMajor = parseInt(nodeVersion.slice(1).split(".")[0]);
683
+ const nodeOk = nodeMajor >= 18;
684
+ checks.push({
685
+ id: "node-version",
686
+ name: "Node.js Version",
687
+ status: nodeOk ? "pass" : "fail",
688
+ current: nodeVersion,
689
+ required: ">=18.0.0",
690
+ message: nodeOk ? `Node.js ${nodeVersion}` : `Node.js ${nodeVersion} too old`,
691
+ fix: nodeOk ? null : "Install Node.js 18+ from https://nodejs.org",
692
+ critical: true,
693
+ });
694
+
695
+ // Check 2: MCP Server exists
696
+ const serverPath = getMcpServerPath();
697
+ const serverExists = mcpServerExists();
698
+ checks.push({
699
+ id: "mcp-server",
700
+ name: "MCP Server",
701
+ status: serverExists ? "pass" : "fail",
702
+ path: serverPath,
703
+ message: serverExists ? "Server module found" : "Server module not found",
704
+ fix: serverExists ? null : "Run 'npm install' in vibecheck directory",
705
+ critical: true,
706
+ });
707
+
708
+ // Check 3: Port availability
709
+ const portAvailable = await isPortAvailable(DEFAULT_PORT);
710
+ let portMessage = portAvailable ? `Port ${DEFAULT_PORT} available` : `Port ${DEFAULT_PORT} in use`;
711
+ let portFix = null;
712
+ let portProcess = null;
713
+
714
+ if (!portAvailable) {
715
+ portProcess = findPortProcess(DEFAULT_PORT);
716
+ if (portProcess) {
717
+ portMessage = `Port ${DEFAULT_PORT} in use by ${portProcess.name} (PID: ${portProcess.pid})`;
718
+ portFix = `Kill process ${portProcess.pid} or use --port flag`;
719
+ }
720
+ }
721
+
722
+ checks.push({
723
+ id: "port-available",
724
+ name: "Default Port",
725
+ status: portAvailable ? "pass" : "warn",
726
+ port: DEFAULT_PORT,
727
+ process: portProcess,
728
+ message: portMessage,
729
+ fix: portFix,
730
+ critical: false,
731
+ });
732
+
733
+ // Check 4: Existing instance
734
+ const existingInstance = checkExistingInstance(projectPath);
735
+ checks.push({
736
+ id: "no-existing-instance",
737
+ name: "No Existing Instance",
738
+ status: existingInstance.running ? "warn" : "pass",
739
+ message: existingInstance.running
740
+ ? `Server running (PID: ${existingInstance.pid})`
741
+ : existingInstance.wasStale ? "Cleaned stale lock" : "No instance running",
742
+ pid: existingInstance.pid,
743
+ fix: existingInstance.running ? `Stop existing server or use different port` : null,
744
+ critical: false,
745
+ });
746
+
747
+ // Check 5: Project config
748
+ const configPaths = [
749
+ path.join(projectPath, ".vibecheckrc"),
750
+ path.join(projectPath, "vibecheck.config.json"),
751
+ path.join(projectPath, ".vibecheck", "config.json"),
752
+ ];
753
+ const configExists = configPaths.some(p => fs.existsSync(p));
754
+ const configPath = configPaths.find(p => fs.existsSync(p));
755
+ checks.push({
756
+ id: "project-config",
757
+ name: "Project Config",
758
+ status: configExists ? "pass" : "warn",
759
+ path: configPath || projectPath,
760
+ message: configExists ? `Config found: ${path.basename(configPath)}` : "No config (run 'vibecheck link')",
761
+ fix: configExists ? null : "Run 'vibecheck link' to initialize",
762
+ critical: false,
763
+ });
764
+
765
+ // Check 6: Environment variables
766
+ const apiKey = process.env.VIBECHECK_API_KEY;
767
+ checks.push({
768
+ id: "api-key",
769
+ name: "API Key",
770
+ status: apiKey ? "pass" : "info",
771
+ message: apiKey ? "VIBECHECK_API_KEY configured" : "No API key (free tier)",
772
+ fix: apiKey ? null : "Set VIBECHECK_API_KEY for PRO features",
773
+ critical: false,
774
+ });
775
+
776
+ // Check 7: Write permissions
777
+ let canWrite = false;
778
+ try {
779
+ const testFile = path.join(projectPath, ".vibecheck", ".write-test");
780
+ const testDir = path.dirname(testFile);
781
+ if (!fs.existsSync(testDir)) {
782
+ fs.mkdirSync(testDir, { recursive: true });
783
+ }
784
+ fs.writeFileSync(testFile, "test");
785
+ fs.unlinkSync(testFile);
786
+ canWrite = true;
787
+ } catch {
788
+ canWrite = false;
789
+ }
790
+
791
+ checks.push({
792
+ id: "write-permissions",
793
+ name: "Write Permissions",
794
+ status: canWrite ? "pass" : "fail",
795
+ path: path.join(projectPath, ".vibecheck"),
796
+ message: canWrite ? "Can write to .vibecheck/" : "Cannot write to project directory",
797
+ fix: canWrite ? null : "Check directory permissions",
798
+ critical: true,
799
+ });
800
+
801
+ // Check 8: IDE integrations
802
+ const platform = process.platform;
803
+ const ideChecks = [];
804
+
805
+ for (const [ide, config] of Object.entries(IDE_CONFIGS)) {
806
+ const configPath = config.configPath[platform];
807
+ if (configPath) {
808
+ const exists = fs.existsSync(configPath);
809
+ let hasVibecheck = false;
810
+ let parseError = null;
811
+
812
+ if (exists) {
813
+ try {
814
+ const content = JSON.parse(fs.readFileSync(configPath, "utf8"));
815
+ hasVibecheck = !!(content.mcpServers?.vibecheck || content.mcpServers?.["vibecheck-local"]);
816
+ } catch (err) {
817
+ parseError = err.message;
818
+ }
819
+ }
820
+
821
+ ideChecks.push({
822
+ ide: config.name,
823
+ key: ide,
824
+ configExists: exists,
825
+ vibecheckConfigured: hasVibecheck,
826
+ parseError,
827
+ path: configPath,
828
+ });
829
+ }
830
+ }
831
+
832
+ const configuredIdes = ideChecks.filter(c => c.vibecheckConfigured);
833
+ checks.push({
834
+ id: "ide-integration",
835
+ name: "IDE Integration",
836
+ status: configuredIdes.length > 0 ? "pass" : "warn",
837
+ message: configuredIdes.length > 0
838
+ ? `Configured: ${configuredIdes.map(c => c.ide).join(", ")}`
839
+ : "No IDE configured",
840
+ fix: configuredIdes.length > 0 ? null : "Run 'vibecheck mcp config --ide cursor --write'",
841
+ details: ideChecks,
842
+ critical: false,
843
+ });
844
+
845
+ // Check 9: Manifest file
846
+ const manifestPath = getManifestPath();
847
+ const manifestExists = fs.existsSync(manifestPath);
848
+ checks.push({
849
+ id: "manifest",
850
+ name: "Tool Manifest",
851
+ status: manifestExists ? "pass" : "warn",
852
+ path: manifestPath,
853
+ message: manifestExists ? "Manifest available" : "Manifest not found",
854
+ fix: manifestExists ? null : "Manifest will be generated on server start",
855
+ critical: false,
856
+ });
857
+
858
+ // Self-healing
859
+ if (opts.fix) {
860
+ for (const check of checks) {
861
+ if (check.status !== "pass" && check.fix && !check.critical) {
862
+ // Auto-fix: Create project config
863
+ if (check.id === "project-config" && !configExists) {
864
+ try {
865
+ const configDir = path.join(projectPath, ".vibecheck");
866
+ if (!fs.existsSync(configDir)) {
867
+ fs.mkdirSync(configDir, { recursive: true });
868
+ }
869
+ const rcPath = path.join(projectPath, ".vibecheckrc");
870
+ fs.writeFileSync(rcPath, JSON.stringify({
871
+ version: "1.0.0",
872
+ created: new Date().toISOString(),
873
+ mcp: { autoStart: false },
874
+ }, null, 2));
875
+ fixes.push({ id: check.id, action: "Created .vibecheckrc" });
876
+ check.status = "fixed";
877
+ } catch (err) {
878
+ fixes.push({ id: check.id, action: `Failed: ${err.message}`, success: false });
879
+ }
880
+ }
881
+
882
+ // Auto-fix: Remove stale lock
883
+ if (check.id === "no-existing-instance" && existingInstance.wasStale) {
884
+ fixes.push({ id: check.id, action: "Removed stale lock file" });
885
+ check.status = "fixed";
886
+ }
887
+ }
888
+ }
889
+ }
890
+
891
+ // Calculate summary
892
+ const summary = {
893
+ pass: checks.filter(c => c.status === "pass").length,
894
+ warn: checks.filter(c => c.status === "warn").length,
895
+ fail: checks.filter(c => c.status === "fail").length,
896
+ info: checks.filter(c => c.status === "info").length,
897
+ fixed: checks.filter(c => c.status === "fixed").length,
898
+ };
899
+
900
+ const healthy = summary.fail === 0;
901
+ const hasWarnings = summary.warn > 0;
902
+ const criticalFail = checks.some(c => c.status === "fail" && c.critical);
903
+
904
+ // Output
905
+ if (json) {
906
+ console.log(JSON.stringify(createSuccess({
907
+ healthy,
908
+ criticalFail,
909
+ checks,
910
+ fixes: fixes.length > 0 ? fixes : undefined,
911
+ summary,
912
+ }), null, 2));
913
+ return healthy ? EXIT.SUCCESS : EXIT.INTERNAL_ERROR;
914
+ }
915
+
916
+ // Pretty output
917
+ for (const check of checks) {
918
+ const icon = check.status === "pass" ? `${c.green}${sym.check}${c.reset}` :
919
+ check.status === "fail" ? `${c.red}${sym.cross}${c.reset}` :
920
+ check.status === "warn" ? `${c.yellow}${sym.warn}${c.reset}` :
921
+ check.status === "fixed" ? `${c.cyan}${sym.wrench}${c.reset}` :
922
+ `${c.blue}${sym.info}${c.reset}`;
923
+
924
+ const critical = check.critical && check.status === "fail" ? ` ${c.red}(CRITICAL)${c.reset}` : "";
925
+
926
+ console.log(` ${icon} ${c.bold}${check.name}${c.reset}${critical}`);
927
+ console.log(` ${c.dim}${check.message}${c.reset}`);
928
+
929
+ if (check.fix && check.status !== "fixed" && check.status !== "pass") {
930
+ console.log(` ${c.yellow}${sym.arrow} ${check.fix}${c.reset}`);
931
+ }
932
+ console.log();
933
+ }
934
+
935
+ // Summary
936
+ console.log(`${c.dim}${"─".repeat(55)}${c.reset}\n`);
937
+
938
+ if (healthy && !hasWarnings) {
939
+ console.log(`${c.green}${sym.heart} All checks passed!${c.reset}\n`);
940
+ console.log(`${c.dim}Your MCP setup is bulletproof. Start with:${c.reset}`);
941
+ console.log(` ${c.cyan}vibecheck mcp serve${c.reset}\n`);
942
+ } else if (criticalFail) {
943
+ console.log(`${c.red}${sym.cross} Critical issues found${c.reset}\n`);
944
+ console.log(`${c.dim}Fix critical issues before starting the server.${c.reset}\n`);
945
+ } else if (summary.fail > 0) {
946
+ console.log(`${c.red}${sym.cross} ${summary.fail} check(s) failed${c.reset}\n`);
947
+ console.log(`${c.dim}Fix issues above, then run:${c.reset}`);
948
+ console.log(` ${c.cyan}vibecheck mcp doctor --fix${c.reset}\n`);
949
+ } else {
950
+ console.log(`${c.yellow}${sym.warn} ${summary.warn} warning(s)${c.reset}\n`);
951
+ console.log(`${c.dim}Server will work, but consider fixing warnings.${c.reset}`);
952
+ console.log(` ${c.cyan}vibecheck mcp serve${c.reset}\n`);
953
+ }
954
+
955
+ if (fixes.length > 0) {
956
+ console.log(`${c.cyan}${sym.wrench} Auto-fixed ${fixes.length} issue(s)${c.reset}\n`);
957
+ }
958
+
959
+ return healthy ? EXIT.SUCCESS : (criticalFail ? ERROR_CODES.HEALTH_CHECK_FAILED : EXIT.SUCCESS);
960
+ }
961
+
962
+ // ============================================================================
963
+ // CONFIG - Generate IDE-specific Configurations
964
+ // ============================================================================
965
+
966
+ function generateConfig(ide, opts) {
967
+ const projectPath = getProjectPath(opts.path);
968
+ const mcpServerPath = getMcpServerPath();
969
+ const cliBinPath = getCliBinPath();
970
+
971
+ const useNpx = opts.npx || !fs.existsSync(mcpServerPath);
972
+ const useCli = opts.cli;
973
+
974
+ let command, args, cwd;
975
+
976
+ if (useNpx) {
977
+ command = "npx";
978
+ args = ["-y", "@vibecheck/mcp-server"];
979
+ } else if (useCli) {
980
+ command = "node";
981
+ args = [cliBinPath, "mcp", "serve"];
982
+ cwd = projectPath;
983
+ } else {
984
+ command = "node";
985
+ args = [mcpServerPath];
986
+ cwd = projectPath;
987
+ }
988
+
989
+ const env = {};
990
+ if (opts.apiKey || process.env.VIBECHECK_API_KEY) {
991
+ env.VIBECHECK_API_KEY = opts.apiKey || "${VIBECHECK_API_KEY}";
992
+ }
993
+ env.VIBECHECK_PROJECT_PATH = projectPath;
994
+
995
+ if (ide === "vscode") {
996
+ return {
997
+ mcpServers: [{
998
+ name: "vibecheck",
999
+ command,
1000
+ args,
1001
+ ...(cwd ? { cwd } : {}),
1002
+ env: Object.keys(env).length > 0 ? env : undefined,
1003
+ }],
1004
+ };
1005
+ }
1006
+
1007
+ return {
1008
+ mcpServers: {
1009
+ vibecheck: {
1010
+ command,
1011
+ args,
1012
+ ...(cwd ? { cwd } : {}),
1013
+ env: Object.keys(env).length > 0 ? env : undefined,
1014
+ },
1015
+ },
1016
+ };
1017
+ }
1018
+
1019
+ async function runConfig(opts, globalFlags) {
1020
+ const json = isJsonMode(globalFlags);
1021
+ const quiet = shouldSuppressOutput(globalFlags);
1022
+
1023
+ // Validate IDE
1024
+ const ideValidation = validateIde(opts.ide);
1025
+ if (!ideValidation.valid) {
1026
+ if (json) {
1027
+ console.log(JSON.stringify(createError(ideValidation.code, ideValidation.error)));
1028
+ } else {
1029
+ console.error(`${c.red}${sym.cross} ${ideValidation.error}${c.reset}`);
1030
+ }
1031
+ return ERROR_CODES.VALIDATION_ERROR;
1032
+ }
1033
+
1034
+ const ide = ideValidation.ide;
1035
+
1036
+ if (!quiet && !json) {
1037
+ console.log(`\n${c.bold}${sym.plug} VibeCheck MCP Configuration${c.reset}\n`);
1038
+ }
1039
+
1040
+ const configs = {};
1041
+ const ides = ide === "all" ? Object.keys(IDE_CONFIGS) : [ide];
1042
+
1043
+ for (const ideKey of ides) {
1044
+ const ideConfig = IDE_CONFIGS[ideKey];
1045
+ if (!ideConfig) continue;
1046
+
1047
+ const config = generateConfig(ideKey, opts);
1048
+ configs[ideKey] = config;
1049
+
1050
+ if (!json && !quiet) {
1051
+ const configPath = ideConfig.configPath[process.platform];
1052
+
1053
+ console.log(`${c.cyan}${ideConfig.icon} ${ideConfig.name}${c.reset}`);
1054
+ console.log(`${c.dim}${ideConfig.instructions}${c.reset}`);
1055
+ if (configPath) {
1056
+ console.log(`${c.dim}Path: ${configPath}${c.reset}`);
1057
+ }
1058
+ console.log();
1059
+ console.log(`${c.green}┌${"─".repeat(53)}┐${c.reset}`);
1060
+ const configLines = JSON.stringify(config, null, 2).split("\n");
1061
+ for (const line of configLines) {
1062
+ console.log(`${c.green}│${c.reset} ${line}`);
1063
+ }
1064
+ console.log(`${c.green}└${"─".repeat(53)}┘${c.reset}`);
1065
+ console.log();
1066
+ }
1067
+ }
1068
+
1069
+ if (json) {
1070
+ console.log(JSON.stringify(createSuccess(ide === "all" ? configs : configs[ide])));
1071
+ return EXIT.SUCCESS;
1072
+ }
1073
+
1074
+ if (!quiet) {
1075
+ console.log(`${c.dim}${"─".repeat(55)}${c.reset}\n`);
1076
+ console.log(`${c.bold}Quick Setup:${c.reset}`);
1077
+ console.log(` 1. Copy the config above to your IDE's MCP settings`);
1078
+ console.log(` 2. Restart your IDE`);
1079
+ console.log(` 3. Ask: "${c.cyan}What vibecheck tools are available?${c.reset}"\n`);
1080
+
1081
+ // Auto-write
1082
+ if (opts.write && ide !== "all") {
1083
+ const ideConfig = IDE_CONFIGS[ide];
1084
+ const configPath = ideConfig?.configPath[process.platform];
1085
+
1086
+ if (configPath) {
1087
+ try {
1088
+ const dir = path.dirname(configPath);
1089
+ if (!fs.existsSync(dir)) {
1090
+ fs.mkdirSync(dir, { recursive: true });
1091
+ }
1092
+
1093
+ let existingConfig = {};
1094
+ if (fs.existsSync(configPath)) {
1095
+ try {
1096
+ existingConfig = JSON.parse(fs.readFileSync(configPath, "utf8"));
1097
+ } catch {
1098
+ // Start fresh
1099
+ }
1100
+ }
1101
+
1102
+ const newConfig = generateConfig(ide, opts);
1103
+ const merged = {
1104
+ ...existingConfig,
1105
+ mcpServers: {
1106
+ ...(existingConfig.mcpServers || {}),
1107
+ ...(newConfig.mcpServers || {}),
1108
+ },
1109
+ };
1110
+
1111
+ fs.writeFileSync(configPath, JSON.stringify(merged, null, 2));
1112
+ console.log(`${c.green}${sym.check} Config written to: ${configPath}${c.reset}\n`);
1113
+ } catch (err) {
1114
+ console.log(`${c.red}${sym.cross} Write failed: ${err.message}${c.reset}\n`);
1115
+ return ERROR_CODES.PERMISSION_DENIED;
1116
+ }
1117
+ } else {
1118
+ console.log(`${c.yellow}${sym.warn} No config path for ${ide} on ${process.platform}${c.reset}\n`);
1119
+ }
1120
+ }
1121
+ }
1122
+
1123
+ return EXIT.SUCCESS;
1124
+ }
1125
+
1126
+ // ============================================================================
1127
+ // MANIFEST - Machine-readable Tool Manifest
1128
+ // ============================================================================
1129
+
1130
+ async function runManifest(opts, globalFlags) {
1131
+ const json = isJsonMode(globalFlags) || opts.format === "json";
1132
+ const format = opts.format || "json";
1133
+
1134
+ // Try to read from file first
1135
+ const manifestPath = getManifestPath();
1136
+ let manifest;
1137
+
1138
+ if (fs.existsSync(manifestPath)) {
1139
+ try {
1140
+ manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
1141
+ } catch {
1142
+ // Fall back to generated
1143
+ }
1144
+ }
1145
+
1146
+ if (!manifest) {
1147
+ manifest = {
1148
+ $schema: "https://json-schema.org/draft/2020-12/schema",
1149
+ name: "vibecheck-mcp",
1150
+ version: VERSION,
1151
+ description: "VibeCheck MCP Server - AI-powered code verification",
1152
+ tools: CANONICAL_TOOLS,
1153
+ categories: TOOL_CATEGORIES,
1154
+ generatedAt: new Date().toISOString(),
1155
+ };
1156
+ }
1157
+
1158
+ if (format === "summary") {
1159
+ const output = {
1160
+ name: manifest.name,
1161
+ version: manifest.version,
1162
+ toolCount: manifest.tools?.length || CANONICAL_TOOLS.length,
1163
+ categories: Object.keys(manifest.categories || TOOL_CATEGORIES),
1164
+ tiers: {
1165
+ free: (manifest.tools || CANONICAL_TOOLS).filter(t => t.tier === "free").length,
1166
+ pro: (manifest.tools || CANONICAL_TOOLS).filter(t => t.tier === "pro").length,
1167
+ },
1168
+ };
1169
+ console.log(JSON.stringify(createSuccess(output), null, 2));
1170
+ } else {
1171
+ console.log(JSON.stringify(json ? createSuccess(manifest) : manifest, null, 2));
1172
+ }
1173
+
1174
+ return EXIT.SUCCESS;
1175
+ }
1176
+
1177
+ // ============================================================================
1178
+ // SERVE - Start the MCP Server (HARDENED)
1179
+ // ============================================================================
1180
+
1181
+ async function runServe(opts, globalFlags) {
1182
+ const json = isJsonMode(globalFlags);
1183
+ const quiet = shouldSuppressOutput(globalFlags);
1184
+ const projectPath = getProjectPath(opts.path);
1185
+
1186
+ // Initialize logging
1187
+ if (opts.debug) {
1188
+ setLogLevel("debug");
1189
+ }
1190
+ initLogFile(projectPath);
1191
+
1192
+ log("info", "Starting MCP server", { projectPath, version: VERSION });
1193
+
1194
+ // Validate server exists
1195
+ const serverPath = getMcpServerPath();
1196
+ if (!fs.existsSync(serverPath)) {
1197
+ const error = createError(ERROR_CODES.SERVER_NOT_FOUND, "MCP server not found", { path: serverPath });
1198
+ if (json) {
1199
+ console.log(JSON.stringify(error));
1200
+ } else {
1201
+ console.error(`${c.red}${sym.cross} MCP server not found: ${serverPath}${c.reset}`);
1202
+ console.error(`${c.dim}Run 'npm install' in vibecheck directory${c.reset}`);
1203
+ }
1204
+ log("error", "Server not found", { serverPath });
1205
+ return ERROR_CODES.SERVER_NOT_FOUND;
1206
+ }
1207
+
1208
+ // Check for existing instance
1209
+ const existingInstance = checkExistingInstance(projectPath);
1210
+ if (existingInstance.running) {
1211
+ if (opts.force) {
1212
+ log("warn", "Force-starting despite existing instance", { pid: existingInstance.pid });
1213
+ try {
1214
+ process.kill(existingInstance.pid, "SIGTERM");
1215
+ await sleep(1000);
1216
+ } catch {
1217
+ // Process may already be dead
1218
+ }
1219
+ removeLockFile(projectPath);
1220
+ } else {
1221
+ const error = createError(ERROR_CODES.ALREADY_RUNNING, existingInstance.message, {
1222
+ pid: existingInstance.pid,
1223
+ port: existingInstance.port,
1224
+ });
1225
+ if (json) {
1226
+ console.log(JSON.stringify(error));
1227
+ } else {
1228
+ console.error(`${c.red}${sym.lock} ${existingInstance.message}${c.reset}`);
1229
+ console.error(`${c.dim}Use --force to override${c.reset}`);
1230
+ }
1231
+ return ERROR_CODES.ALREADY_RUNNING;
1232
+ }
1233
+ }
1234
+
1235
+ // Port handling with retry
1236
+ let port = opts.port || DEFAULT_PORT;
1237
+ let portInfo = null;
1238
+
1239
+ const portResult = await withRetry(
1240
+ async (attempt) => {
1241
+ if (await isPortAvailable(port)) {
1242
+ return { port, available: true };
1243
+ }
1244
+
1245
+ if (opts.autoPort !== false) {
1246
+ const available = await findAvailablePort(port + 1);
1247
+ if (available.port) {
1248
+ return { port: available.port, available: true, source: available.source };
1249
+ }
1250
+ }
1251
+
1252
+ throw new Error(`Port ${port} unavailable`);
1253
+ },
1254
+ {
1255
+ maxAttempts: 2,
1256
+ onRetry: (err, attempt) => {
1257
+ log("debug", `Port retry ${attempt + 1}`, { error: err.message });
1258
+ },
1259
+ }
1260
+ ).catch(err => ({ available: false, error: err.message }));
1261
+
1262
+ if (!portResult.available) {
1263
+ portInfo = findPortProcess(port);
1264
+ const error = createError(ERROR_CODES.PORT_UNAVAILABLE, `No available ports`, {
1265
+ requestedPort: port,
1266
+ blockedBy: portInfo,
1267
+ });
1268
+ if (json) {
1269
+ console.log(JSON.stringify(error));
1270
+ } else {
1271
+ console.error(`${c.red}${sym.cross} Port ${port} unavailable${portInfo ? ` (${portInfo.name})` : ""}${c.reset}`);
1272
+ }
1273
+ log("error", "No available ports", { port, portInfo });
1274
+ return ERROR_CODES.PORT_UNAVAILABLE;
1275
+ }
1276
+
1277
+ if (portResult.port !== port) {
1278
+ if (!quiet && !json) {
1279
+ console.log(`${c.yellow}${sym.warn} Port ${port} in use, using ${portResult.port}${c.reset}\n`);
1280
+ }
1281
+ log("info", "Using alternate port", { requested: port, actual: portResult.port });
1282
+ port = portResult.port;
1283
+ }
1284
+
1285
+ // Banner
1286
+ if (!quiet && !json) {
1287
+ console.log(`
1288
+ ${c.cyan}${c.bold}╔════════════════════════════════════════════════════════════════╗
1289
+ ║ ║
1290
+ ║ ${sym.brain} VibeCheck MCP Server v${VERSION} ║
1291
+ ║ Your IDE is now VibeCheck-powered ║
1292
+ ║ ║
1293
+ ║ ${c.dim}Hardened Build - Production Ready${c.reset}${c.cyan}${c.bold} ║
1294
+ ║ ║
1295
+ ╚════════════════════════════════════════════════════════════════╝${c.reset}
1296
+ `);
1297
+ }
1298
+
1299
+ // Environment setup
1300
+ const env = {
1301
+ ...process.env,
1302
+ VIBECHECK_PROJECT_PATH: projectPath,
1303
+ VIBECHECK_MCP_PORT: String(port),
1304
+ VIBECHECK_MCP_VERSION: VERSION,
1305
+ NODE_ENV: process.env.NODE_ENV || "production",
1306
+ };
1307
+
1308
+ if (opts.debug) {
1309
+ env.VIBECHECK_DEBUG = "true";
1310
+ env.DEBUG = "vibecheck:*";
1311
+ }
1312
+
1313
+ // Spawn server process
1314
+ const server = spawn("node", [serverPath], {
1315
+ cwd: projectPath,
1316
+ env,
1317
+ stdio: opts.stdio ? "inherit" : ["pipe", "pipe", "pipe"],
1318
+ detached: false,
1319
+ });
1320
+
1321
+ if (!server.pid) {
1322
+ const error = createError(ERROR_CODES.STARTUP_FAILED, "Failed to spawn server process");
1323
+ if (json) {
1324
+ console.log(JSON.stringify(error));
1325
+ } else {
1326
+ console.error(`${c.red}${sym.cross} Failed to start server${c.reset}`);
1327
+ }
1328
+ log("error", "Spawn failed");
1329
+ return ERROR_CODES.STARTUP_FAILED;
1330
+ }
1331
+
1332
+ // Create lock file
1333
+ createLockFile(projectPath, server.pid, port);
1334
+
1335
+ // Track server state
1336
+ let serverReady = false;
1337
+ let serverError = null;
1338
+ let outputBuffer = "";
1339
+
1340
+ // Handle server output
1341
+ if (!opts.stdio) {
1342
+ server.stdout.on("data", (data) => {
1343
+ const text = data.toString();
1344
+ outputBuffer += text;
1345
+
1346
+ // Check for ready signal
1347
+ if (text.includes("Server running") || text.includes("MCP server started")) {
1348
+ serverReady = true;
1349
+ }
1350
+
1351
+ if (!quiet) {
1352
+ process.stdout.write(redactSecrets(text));
1353
+ }
1354
+
1355
+ log("debug", "stdout", { data: redactSecrets(text.trim()) });
1356
+ });
1357
+
1358
+ server.stderr.on("data", (data) => {
1359
+ const text = data.toString();
1360
+
1361
+ // Check for errors
1362
+ if (text.includes("Error") || text.includes("EADDRINUSE")) {
1363
+ serverError = text.trim();
1364
+ }
1365
+
1366
+ if (!quiet) {
1367
+ process.stderr.write(redactSecrets(text));
1368
+ }
1369
+
1370
+ log("warn", "stderr", { data: redactSecrets(text.trim()) });
1371
+ });
1372
+ }
1373
+
1374
+ // Server error handler
1375
+ server.on("error", (err) => {
1376
+ serverError = err.message;
1377
+ log("error", "Server error", { error: err.message });
1378
+
1379
+ if (json) {
1380
+ console.log(JSON.stringify(createError(ERROR_CODES.STARTUP_FAILED, err.message)));
1381
+ } else {
1382
+ console.error(`${c.red}${sym.cross} Server error: ${err.message}${c.reset}`);
1383
+ }
1384
+ });
1385
+
1386
+ // Wait for server startup with timeout
1387
+ const startupResult = await Promise.race([
1388
+ new Promise((resolve) => {
1389
+ const checkInterval = setInterval(() => {
1390
+ if (serverReady) {
1391
+ clearInterval(checkInterval);
1392
+ resolve({ ready: true });
1393
+ }
1394
+ if (serverError) {
1395
+ clearInterval(checkInterval);
1396
+ resolve({ ready: false, error: serverError });
1397
+ }
1398
+ }, 100);
1399
+ }),
1400
+ sleep(TIMEOUTS.STARTUP).then(() => ({ ready: false, error: "Startup timeout" })),
1401
+ ]);
1402
+
1403
+ if (!startupResult.ready && !opts.stdio) {
1404
+ // Server didn't signal ready, but may still be running
1405
+ // Check if process is alive
1406
+ if (!isProcessRunning(server.pid)) {
1407
+ removeLockFile(projectPath);
1408
+ const error = createError(ERROR_CODES.STARTUP_FAILED, startupResult.error || "Server exited unexpectedly");
1409
+ if (json) {
1410
+ console.log(JSON.stringify(error));
1411
+ }
1412
+ return ERROR_CODES.STARTUP_FAILED;
1413
+ }
1414
+ // Process is running, assume it's okay
1415
+ serverReady = true;
1416
+ }
1417
+
1418
+ // Success output
1419
+ if (!quiet && !json) {
1420
+ console.log(`${c.green}${sym.rocket} Server started successfully${c.reset}`);
1421
+ console.log(`${c.dim}├─ PID: ${server.pid}${c.reset}`);
1422
+ console.log(`${c.dim}├─ Port: ${port}${c.reset}`);
1423
+ console.log(`${c.dim}├─ Project: ${projectPath}${c.reset}`);
1424
+ console.log(`${c.dim}└─ Lock: ${getLockFilePath(projectPath)}${c.reset}`);
1425
+ console.log();
1426
+
1427
+ // Tool summary
1428
+ const freeTools = CANONICAL_TOOLS.filter(t => t.tier === "free");
1429
+ const proTools = CANONICAL_TOOLS.filter(t => t.tier === "pro");
1430
+
1431
+ console.log(`${c.bold}Available Tools (${CANONICAL_TOOLS.length}):${c.reset}`);
1432
+ console.log(` ${c.green}FREE${c.reset} (${freeTools.length}): ${freeTools.slice(0, 6).map(t => t.cli).join(", ")}...`);
1433
+ console.log(` ${c.magenta}PRO${c.reset} (${proTools.length}): ${proTools.slice(0, 6).map(t => t.cli).join(", ")}...`);
1434
+ console.log();
1435
+
1436
+ console.log(`${c.dim}${"─".repeat(60)}${c.reset}`);
1437
+ console.log(`${c.dim}Press Ctrl+C for graceful shutdown${c.reset}`);
1438
+ console.log(`${c.dim}${"─".repeat(60)}${c.reset}\n`);
1439
+ }
1440
+
1441
+ log("info", "Server started", { pid: server.pid, port });
1442
+
1443
+ // Graceful shutdown handler
1444
+ let shuttingDown = false;
1445
+
1446
+ const cleanup = async (signal) => {
1447
+ if (shuttingDown) return;
1448
+ shuttingDown = true;
1449
+
1450
+ log("info", "Shutdown initiated", { signal });
1451
+
1452
+ if (!quiet && !json) {
1453
+ console.log(`\n${c.dim}${sym.timer} Shutting down gracefully...${c.reset}`);
1454
+ }
1455
+
1456
+ // Send SIGTERM first
1457
+ server.kill("SIGTERM");
1458
+
1459
+ // Wait for graceful shutdown
1460
+ const shutdownResult = await Promise.race([
1461
+ new Promise((resolve) => {
1462
+ server.once("close", () => resolve({ clean: true }));
1463
+ }),
1464
+ sleep(TIMEOUTS.SHUTDOWN).then(() => ({ clean: false })),
1465
+ ]);
1466
+
1467
+ if (!shutdownResult.clean) {
1468
+ log("warn", "Force killing server");
1469
+ server.kill("SIGKILL");
1470
+ }
1471
+
1472
+ // Cleanup
1473
+ removeLockFile(projectPath);
1474
+
1475
+ if (logFile) {
1476
+ logFile.end();
1477
+ }
1478
+
1479
+ if (!quiet && !json) {
1480
+ console.log(`${c.green}${sym.check} Server stopped${c.reset}\n`);
1481
+ }
1482
+
1483
+ log("info", "Server stopped", { clean: shutdownResult.clean });
1484
+ };
1485
+
1486
+ // Register signal handlers
1487
+ process.on("SIGINT", () => cleanup("SIGINT").then(() => process.exit(0)));
1488
+ process.on("SIGTERM", () => cleanup("SIGTERM").then(() => process.exit(0)));
1489
+
1490
+ // Handle uncaught errors
1491
+ process.on("uncaughtException", (err) => {
1492
+ log("error", "Uncaught exception", { error: err.message, stack: err.stack });
1493
+ cleanup("uncaughtException").then(() => process.exit(1));
1494
+ });
1495
+
1496
+ // Server close handler
1497
+ server.on("close", (code, signal) => {
1498
+ if (!shuttingDown) {
1499
+ log("warn", "Server exited unexpectedly", { code, signal });
1500
+ removeLockFile(projectPath);
1501
+
1502
+ if (!quiet && !json) {
1503
+ if (code === 0) {
1504
+ console.log(`\n${c.green}${sym.check} Server exited cleanly${c.reset}`);
1505
+ } else {
1506
+ console.log(`\n${c.yellow}${sym.warn} Server exited (code: ${code}, signal: ${signal})${c.reset}`);
1507
+ }
1508
+ }
1509
+ }
1510
+ });
1511
+
1512
+ // Keep process alive
1513
+ return new Promise((resolve) => {
1514
+ server.on("close", (code) => {
1515
+ resolve(code === 0 ? EXIT.SUCCESS : ERROR_CODES.INTERNAL_ERROR);
1516
+ });
1517
+ });
1518
+ }
1519
+
1520
+ // ============================================================================
1521
+ // STATUS - Quick Status Check
1522
+ // ============================================================================
1523
+
1524
+ async function runStatus(opts, globalFlags) {
1525
+ const json = isJsonMode(globalFlags);
1526
+ const quiet = shouldSuppressOutput(globalFlags);
1527
+ const projectPath = getProjectPath(opts.path);
1528
+
1529
+ const status = {
1530
+ version: VERSION,
1531
+ node: process.version,
1532
+ platform: process.platform,
1533
+ arch: process.arch,
1534
+ projectPath,
1535
+ serverPath: getMcpServerPath(),
1536
+ serverExists: mcpServerExists(),
1537
+ manifestPath: getManifestPath(),
1538
+ manifestExists: fs.existsSync(getManifestPath()),
1539
+ lockFile: getLockFilePath(projectPath),
1540
+ defaultPort: DEFAULT_PORT,
1541
+ };
1542
+
1543
+ // Check running instance
1544
+ const instance = checkExistingInstance(projectPath);
1545
+ status.running = instance.running;
1546
+ if (instance.running) {
1547
+ status.pid = instance.pid;
1548
+ status.runningPort = instance.port;
1549
+ status.startedAt = instance.startedAt;
1550
+ }
1551
+
1552
+ // Check port
1553
+ status.portAvailable = await isPortAvailable(DEFAULT_PORT);
1554
+ if (!status.portAvailable) {
1555
+ const proc = findPortProcess(DEFAULT_PORT);
1556
+ if (proc) {
1557
+ status.portBlockedBy = proc;
1558
+ }
1559
+ }
1560
+
1561
+ // Check config
1562
+ const configPaths = [
1563
+ path.join(projectPath, ".vibecheckrc"),
1564
+ path.join(projectPath, "vibecheck.config.json"),
1565
+ ];
1566
+ status.projectConfigured = configPaths.some(p => fs.existsSync(p));
1567
+ status.apiKeySet = !!process.env.VIBECHECK_API_KEY;
1568
+
1569
+ if (json) {
1570
+ console.log(JSON.stringify(createSuccess(status), null, 2));
1571
+ return EXIT.SUCCESS;
1572
+ }
1573
+
1574
+ if (!quiet) {
1575
+ console.log(`\n${c.bold}${sym.info} VibeCheck MCP Status${c.reset}\n`);
1576
+
1577
+ const check = (v) => v ? `${c.green}${sym.check}${c.reset}` : `${c.red}${sym.cross}${c.reset}`;
1578
+ const warn = (v) => v ? `${c.green}${sym.check}${c.reset}` : `${c.yellow}${sym.warn}${c.reset}`;
1579
+
1580
+ console.log(` ${c.dim}Version:${c.reset} ${status.version}`);
1581
+ console.log(` ${c.dim}Node.js:${c.reset} ${status.node}`);
1582
+ console.log(` ${c.dim}Platform:${c.reset} ${status.platform}/${status.arch}`);
1583
+ console.log();
1584
+ console.log(` ${c.dim}Server:${c.reset} ${check(status.serverExists)} ${status.serverExists ? "Found" : "Not found"}`);
1585
+ console.log(` ${c.dim}Running:${c.reset} ${status.running ? `${c.green}Yes${c.reset} (PID: ${status.pid})` : `${c.dim}No${c.reset}`}`);
1586
+ console.log(` ${c.dim}Port ${DEFAULT_PORT}:${c.reset} ${warn(status.portAvailable)} ${status.portAvailable ? "Available" : `In use${status.portBlockedBy ? ` (${status.portBlockedBy.name})` : ""}`}`);
1587
+ console.log();
1588
+ console.log(` ${c.dim}Project:${c.reset} ${warn(status.projectConfigured)} ${status.projectConfigured ? "Configured" : "Not configured"}`);
1589
+ console.log(` ${c.dim}API Key:${c.reset} ${status.apiKeySet ? `${c.green}${sym.check}${c.reset} Set` : `${c.dim}Not set${c.reset}`}`);
1590
+ console.log();
1591
+ }
1592
+
1593
+ return EXIT.SUCCESS;
1594
+ }
1595
+
1596
+ // ============================================================================
1597
+ // TEST - Test MCP Connection
1598
+ // ============================================================================
1599
+
1600
+ async function runTest(opts, globalFlags) {
1601
+ const json = isJsonMode(globalFlags);
1602
+ const quiet = shouldSuppressOutput(globalFlags);
1603
+ const projectPath = getProjectPath(opts.path);
1604
+
1605
+ if (!quiet && !json) {
1606
+ console.log(`\n${c.bold}${sym.plug} Testing MCP Connection${c.reset}\n`);
1607
+ }
1608
+
1609
+ const results = {
1610
+ serverExists: mcpServerExists(),
1611
+ manifestExists: fs.existsSync(getManifestPath()),
1612
+ nodeVersion: process.version,
1613
+ nodeMajor: parseInt(process.version.slice(1).split(".")[0]),
1614
+ };
1615
+
1616
+ results.nodeOk = results.nodeMajor >= 18;
1617
+
1618
+ // Check running instance
1619
+ const instance = checkExistingInstance(projectPath);
1620
+ results.instanceRunning = instance.running;
1621
+
1622
+ // Calculate overall status
1623
+ results.ready = results.serverExists && results.nodeOk;
1624
+ results.canStart = results.ready && !instance.running;
1625
+
1626
+ if (json) {
1627
+ console.log(JSON.stringify(createSuccess(results), null, 2));
1628
+ return results.ready ? EXIT.SUCCESS : ERROR_CODES.HEALTH_CHECK_FAILED;
1629
+ }
1630
+
1631
+ if (!quiet) {
1632
+ const check = (v) => v ? `${c.green}${sym.check}${c.reset}` : `${c.red}${sym.cross}${c.reset}`;
1633
+
1634
+ console.log(` ${check(results.nodeOk)} Node.js ${results.nodeVersion} ${results.nodeOk ? "" : "(need >=18)"}`);
1635
+ console.log(` ${check(results.serverExists)} MCP server module`);
1636
+ console.log(` ${check(results.manifestExists)} Tool manifest`);
1637
+ console.log(` ${check(!results.instanceRunning)} No existing instance`);
1638
+ console.log();
1639
+
1640
+ if (results.ready) {
1641
+ console.log(`${c.green}${sym.heart} Ready to start!${c.reset}`);
1642
+ console.log(`${c.dim}Run: vibecheck mcp serve${c.reset}\n`);
1643
+ } else {
1644
+ console.log(`${c.red}${sym.cross} Not ready - fix issues above${c.reset}\n`);
1645
+ }
1646
+ }
1647
+
1648
+ return results.ready ? EXIT.SUCCESS : ERROR_CODES.HEALTH_CHECK_FAILED;
1649
+ }
1650
+
1651
+ // ============================================================================
1652
+ // HELP
1653
+ // ============================================================================
1654
+
1655
+ function printHelp() {
1656
+ console.log(`
1657
+ ${c.cyan}${c.bold}vibecheck mcp${c.reset} - Hardened MCP Server for AI-Powered IDEs
1658
+
1659
+ ${c.bold}VERSION${c.reset}
1660
+ ${VERSION} (Hardened Build)
1661
+
1662
+ ${c.bold}USAGE${c.reset}
1663
+ vibecheck mcp [command] [options]
1664
+
1665
+ ${c.bold}COMMANDS${c.reset}
1666
+ ${c.cyan}serve${c.reset} Start the MCP server (default)
1667
+ ${c.cyan}doctor${c.reset} Comprehensive health check with auto-healing
1668
+ ${c.cyan}config${c.reset} Generate IDE-specific connection configs
1669
+ ${c.cyan}manifest${c.reset} Export machine-readable tool manifest
1670
+ ${c.cyan}status${c.reset} Quick status check
1671
+ ${c.cyan}test${c.reset} Test MCP readiness
1672
+
1673
+ ${c.bold}SERVE OPTIONS${c.reset}
1674
+ --port <port> Port number (default: ${DEFAULT_PORT})
1675
+ --path <path> Project path (default: cwd)
1676
+ --no-auto-port Don't auto-select port if blocked
1677
+ --force Force start (kill existing instance)
1678
+ --debug Enable debug logging
1679
+ --stdio Use stdio mode (inherit streams)
1680
+
1681
+ ${c.bold}DOCTOR OPTIONS${c.reset}
1682
+ --fix Auto-fix issues where possible
1683
+ --path <path> Project to check
1684
+
1685
+ ${c.bold}CONFIG OPTIONS${c.reset}
1686
+ --ide <ide> Target IDE (cursor, windsurf, claude, vscode, all)
1687
+ --write Write config to IDE config file
1688
+ --npx Use npx command
1689
+ --cli Use CLI wrapper
1690
+ --api-key <key> Include API key
1691
+
1692
+ ${c.bold}MANIFEST OPTIONS${c.reset}
1693
+ --format <fmt> Output format (json, summary)
1694
+
1695
+ ${c.bold}GLOBAL OPTIONS${c.reset}
1696
+ --json JSON output mode
1697
+ --quiet Suppress non-essential output
1698
+
1699
+ ${c.bold}HARDENING FEATURES${c.reset}
1700
+ ${sym.lock} Lock file prevents multiple instances
1701
+ ${sym.retry} Retry logic with exponential backoff
1702
+ ${sym.shield} Input validation and sanitization
1703
+ ${sym.key} Secret redaction in logs
1704
+ ${sym.timer} Timeout management
1705
+ ${sym.heart} Health probes and self-healing
1706
+
1707
+ ${c.bold}EXAMPLES${c.reset}
1708
+ ${c.dim}# Full health check with auto-fix${c.reset}
1709
+ vibecheck mcp doctor --fix
1710
+
1711
+ ${c.dim}# Start server with debug logging${c.reset}
1712
+ vibecheck mcp serve --debug
1713
+
1714
+ ${c.dim}# Generate and write Cursor config${c.reset}
1715
+ vibecheck mcp config --ide cursor --write
1716
+
1717
+ ${c.dim}# Quick status check${c.reset}
1718
+ vibecheck mcp status --json
1719
+
1720
+ ${c.bold}ERROR CODES${c.reset}
1721
+ 0 Success
1722
+ 1 Validation error
1723
+ 2 Server not found
1724
+ 3 Port unavailable
1725
+ 4 Startup failed
1726
+ 5 Already running
1727
+ 12 Health check failed
1728
+
1729
+ ${c.bold}DOCUMENTATION${c.reset}
1730
+ https://vibecheckai.dev/docs/mcp
1731
+ `);
1732
+ }
1733
+
1734
+ // ============================================================================
1735
+ // ARGUMENT PARSING
1736
+ // ============================================================================
1737
+
1738
+ function parseArgs(args) {
1739
+ const opts = {
1740
+ command: null,
1741
+ port: null,
1742
+ path: null,
1743
+ autoPort: true,
1744
+ force: false,
1745
+ debug: false,
1746
+ stdio: false,
1747
+ fix: false,
1748
+ ide: "all",
1749
+ write: false,
1750
+ npx: false,
1751
+ cli: false,
1752
+ apiKey: null,
1753
+ format: "json",
1754
+ help: false,
1755
+ };
1756
+
1757
+ const commands = ["serve", "doctor", "config", "manifest", "status", "test"];
1758
+
1759
+ for (let i = 0; i < args.length; i++) {
1760
+ const a = args[i];
1761
+
1762
+ if (commands.includes(a) && !opts.command) {
1763
+ opts.command = a;
1764
+ continue;
1765
+ }
1766
+
1767
+ switch (a) {
1768
+ case "--port":
1769
+ opts.port = parseInt(args[++i]);
1770
+ break;
1771
+ case "--path":
1772
+ opts.path = args[++i];
1773
+ break;
1774
+ case "--no-auto-port":
1775
+ opts.autoPort = false;
1776
+ break;
1777
+ case "--force":
1778
+ opts.force = true;
1779
+ break;
1780
+ case "--debug":
1781
+ opts.debug = true;
1782
+ break;
1783
+ case "--stdio":
1784
+ opts.stdio = true;
1785
+ break;
1786
+ case "--fix":
1787
+ opts.fix = true;
1788
+ break;
1789
+ case "--ide":
1790
+ opts.ide = args[++i];
1791
+ break;
1792
+ case "--write":
1793
+ opts.write = true;
1794
+ break;
1795
+ case "--npx":
1796
+ opts.npx = true;
1797
+ break;
1798
+ case "--cli":
1799
+ opts.cli = true;
1800
+ break;
1801
+ case "--api-key":
1802
+ opts.apiKey = args[++i];
1803
+ break;
1804
+ case "--format":
1805
+ opts.format = args[++i];
1806
+ break;
1807
+ case "--help":
1808
+ case "-h":
1809
+ opts.help = true;
1810
+ break;
1811
+ default:
1812
+ if (a.startsWith("--port=")) opts.port = parseInt(a.split("=")[1]);
1813
+ else if (a.startsWith("--path=")) opts.path = a.split("=")[1];
1814
+ else if (a.startsWith("--ide=")) opts.ide = a.split("=")[1];
1815
+ else if (a.startsWith("--api-key=")) opts.apiKey = a.split("=")[1];
1816
+ else if (a.startsWith("--format=")) opts.format = a.split("=")[1];
1817
+ }
1818
+ }
1819
+
1820
+ // Default command
1821
+ if (!opts.command) {
1822
+ opts.command = "serve";
1823
+ }
1824
+
1825
+ return opts;
1826
+ }
1827
+
1828
+ // ============================================================================
1829
+ // MAIN ENTRY POINT
1830
+ // ============================================================================
1831
+
1832
+ async function runMcp(args) {
1833
+ const opts = parseArgs(args);
1834
+ const { flags: globalFlags } = parseGlobalFlags(args);
1835
+
1836
+ if (opts.help) {
1837
+ printHelp();
1838
+ return EXIT.SUCCESS;
1839
+ }
1840
+
1841
+ try {
1842
+ switch (opts.command) {
1843
+ case "serve":
1844
+ return await runServe(opts, globalFlags);
1845
+ case "doctor":
1846
+ return await runDoctor(opts, globalFlags);
1847
+ case "config":
1848
+ return await runConfig(opts, globalFlags);
1849
+ case "manifest":
1850
+ return await runManifest(opts, globalFlags);
1851
+ case "status":
1852
+ return await runStatus(opts, globalFlags);
1853
+ case "test":
1854
+ return await runTest(opts, globalFlags);
1855
+ default:
1856
+ printHelp();
1857
+ return ERROR_CODES.VALIDATION_ERROR;
1858
+ }
1859
+ } catch (err) {
1860
+ log("error", "Unhandled error", { error: err.message, stack: err.stack });
1861
+
1862
+ if (isJsonMode(globalFlags)) {
1863
+ console.log(JSON.stringify(createError(ERROR_CODES.INTERNAL_ERROR, err.message)));
1864
+ } else {
1865
+ console.error(`${c.red}${sym.cross} Error: ${err.message}${c.reset}`);
1866
+ if (opts.debug) {
1867
+ console.error(err.stack);
1868
+ }
1869
+ }
1870
+
1871
+ return ERROR_CODES.INTERNAL_ERROR;
1872
+ }
1873
+ }
1874
+
1875
+ module.exports = { runMcp, ERROR_CODES };