@quantracode/vibecheck 0.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 (209) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +839 -0
  3. package/dist/__tests__/cli.test.d.ts +2 -0
  4. package/dist/__tests__/cli.test.d.ts.map +1 -0
  5. package/dist/__tests__/cli.test.js +243 -0
  6. package/dist/__tests__/fixtures/safe-app/app/api/users/route.js +36 -0
  7. package/dist/__tests__/fixtures/vulnerable-app/app/api/users/route.js +28 -0
  8. package/dist/__tests__/fixtures/vulnerable-app/lib/config.d.ts +4 -0
  9. package/dist/__tests__/fixtures/vulnerable-app/lib/config.d.ts.map +1 -0
  10. package/dist/__tests__/fixtures/vulnerable-app/lib/config.js +6 -0
  11. package/dist/__tests__/scanners/env-config.test.d.ts +2 -0
  12. package/dist/__tests__/scanners/env-config.test.d.ts.map +1 -0
  13. package/dist/__tests__/scanners/env-config.test.js +142 -0
  14. package/dist/__tests__/scanners/nextjs-middleware.test.d.ts +2 -0
  15. package/dist/__tests__/scanners/nextjs-middleware.test.d.ts.map +1 -0
  16. package/dist/__tests__/scanners/nextjs-middleware.test.js +193 -0
  17. package/dist/__tests__/scanners/scanner-packs.test.d.ts +2 -0
  18. package/dist/__tests__/scanners/scanner-packs.test.d.ts.map +1 -0
  19. package/dist/__tests__/scanners/scanner-packs.test.js +126 -0
  20. package/dist/__tests__/scanners/unused-security-imports.test.d.ts +2 -0
  21. package/dist/__tests__/scanners/unused-security-imports.test.d.ts.map +1 -0
  22. package/dist/__tests__/scanners/unused-security-imports.test.js +145 -0
  23. package/dist/commands/demo-artifact.d.ts +7 -0
  24. package/dist/commands/demo-artifact.d.ts.map +1 -0
  25. package/dist/commands/demo-artifact.js +322 -0
  26. package/dist/commands/evaluate.d.ts +30 -0
  27. package/dist/commands/evaluate.d.ts.map +1 -0
  28. package/dist/commands/evaluate.js +258 -0
  29. package/dist/commands/explain.d.ts +12 -0
  30. package/dist/commands/explain.d.ts.map +1 -0
  31. package/dist/commands/explain.js +214 -0
  32. package/dist/commands/index.d.ts +7 -0
  33. package/dist/commands/index.d.ts.map +1 -0
  34. package/dist/commands/index.js +6 -0
  35. package/dist/commands/intent.d.ts +21 -0
  36. package/dist/commands/intent.d.ts.map +1 -0
  37. package/dist/commands/intent.js +192 -0
  38. package/dist/commands/scan.d.ts +44 -0
  39. package/dist/commands/scan.d.ts.map +1 -0
  40. package/dist/commands/scan.js +497 -0
  41. package/dist/commands/waivers.d.ts +30 -0
  42. package/dist/commands/waivers.d.ts.map +1 -0
  43. package/dist/commands/waivers.js +249 -0
  44. package/dist/index.d.ts +3 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +17 -0
  47. package/dist/phase3/index.d.ts +11 -0
  48. package/dist/phase3/index.d.ts.map +1 -0
  49. package/dist/phase3/index.js +12 -0
  50. package/dist/phase3/intent-miner.d.ts +32 -0
  51. package/dist/phase3/intent-miner.d.ts.map +1 -0
  52. package/dist/phase3/intent-miner.js +323 -0
  53. package/dist/phase3/proof-trace-builder.d.ts +42 -0
  54. package/dist/phase3/proof-trace-builder.d.ts.map +1 -0
  55. package/dist/phase3/proof-trace-builder.js +441 -0
  56. package/dist/phase3/scanners/auth-by-ui-server-gap.d.ts +15 -0
  57. package/dist/phase3/scanners/auth-by-ui-server-gap.d.ts.map +1 -0
  58. package/dist/phase3/scanners/auth-by-ui-server-gap.js +237 -0
  59. package/dist/phase3/scanners/comment-claim-unproven.d.ts +14 -0
  60. package/dist/phase3/scanners/comment-claim-unproven.d.ts.map +1 -0
  61. package/dist/phase3/scanners/comment-claim-unproven.js +161 -0
  62. package/dist/phase3/scanners/index.d.ts +31 -0
  63. package/dist/phase3/scanners/index.d.ts.map +1 -0
  64. package/dist/phase3/scanners/index.js +40 -0
  65. package/dist/phase3/scanners/middleware-assumed-not-matching.d.ts +14 -0
  66. package/dist/phase3/scanners/middleware-assumed-not-matching.d.ts.map +1 -0
  67. package/dist/phase3/scanners/middleware-assumed-not-matching.js +172 -0
  68. package/dist/phase3/scanners/validation-claimed-missing.d.ts +15 -0
  69. package/dist/phase3/scanners/validation-claimed-missing.d.ts.map +1 -0
  70. package/dist/phase3/scanners/validation-claimed-missing.js +204 -0
  71. package/dist/scanners/abuse/compute-abuse.d.ts +20 -0
  72. package/dist/scanners/abuse/compute-abuse.d.ts.map +1 -0
  73. package/dist/scanners/abuse/compute-abuse.js +509 -0
  74. package/dist/scanners/abuse/index.d.ts +12 -0
  75. package/dist/scanners/abuse/index.d.ts.map +1 -0
  76. package/dist/scanners/abuse/index.js +15 -0
  77. package/dist/scanners/auth/index.d.ts +5 -0
  78. package/dist/scanners/auth/index.d.ts.map +1 -0
  79. package/dist/scanners/auth/index.js +10 -0
  80. package/dist/scanners/auth/middleware-gap.d.ts +22 -0
  81. package/dist/scanners/auth/middleware-gap.d.ts.map +1 -0
  82. package/dist/scanners/auth/middleware-gap.js +203 -0
  83. package/dist/scanners/auth/unprotected-api-route.d.ts +12 -0
  84. package/dist/scanners/auth/unprotected-api-route.d.ts.map +1 -0
  85. package/dist/scanners/auth/unprotected-api-route.js +126 -0
  86. package/dist/scanners/config/index.d.ts +5 -0
  87. package/dist/scanners/config/index.d.ts.map +1 -0
  88. package/dist/scanners/config/index.js +10 -0
  89. package/dist/scanners/config/insecure-defaults.d.ts +12 -0
  90. package/dist/scanners/config/insecure-defaults.d.ts.map +1 -0
  91. package/dist/scanners/config/insecure-defaults.js +77 -0
  92. package/dist/scanners/config/undocumented-env.d.ts +24 -0
  93. package/dist/scanners/config/undocumented-env.d.ts.map +1 -0
  94. package/dist/scanners/config/undocumented-env.js +159 -0
  95. package/dist/scanners/crypto/index.d.ts +6 -0
  96. package/dist/scanners/crypto/index.d.ts.map +1 -0
  97. package/dist/scanners/crypto/index.js +11 -0
  98. package/dist/scanners/crypto/jwt-decode-unverified.d.ts +14 -0
  99. package/dist/scanners/crypto/jwt-decode-unverified.d.ts.map +1 -0
  100. package/dist/scanners/crypto/jwt-decode-unverified.js +87 -0
  101. package/dist/scanners/crypto/math-random-tokens.d.ts +13 -0
  102. package/dist/scanners/crypto/math-random-tokens.d.ts.map +1 -0
  103. package/dist/scanners/crypto/math-random-tokens.js +80 -0
  104. package/dist/scanners/crypto/weak-hashing.d.ts +11 -0
  105. package/dist/scanners/crypto/weak-hashing.d.ts.map +1 -0
  106. package/dist/scanners/crypto/weak-hashing.js +95 -0
  107. package/dist/scanners/env-config.d.ts +24 -0
  108. package/dist/scanners/env-config.d.ts.map +1 -0
  109. package/dist/scanners/env-config.js +164 -0
  110. package/dist/scanners/hallucinations/index.d.ts +4 -0
  111. package/dist/scanners/hallucinations/index.d.ts.map +1 -0
  112. package/dist/scanners/hallucinations/index.js +8 -0
  113. package/dist/scanners/hallucinations/unused-security-imports.d.ts +36 -0
  114. package/dist/scanners/hallucinations/unused-security-imports.d.ts.map +1 -0
  115. package/dist/scanners/hallucinations/unused-security-imports.js +309 -0
  116. package/dist/scanners/helpers/ast-helpers.d.ts +6 -0
  117. package/dist/scanners/helpers/ast-helpers.d.ts.map +1 -0
  118. package/dist/scanners/helpers/ast-helpers.js +945 -0
  119. package/dist/scanners/helpers/context-builder.d.ts +17 -0
  120. package/dist/scanners/helpers/context-builder.d.ts.map +1 -0
  121. package/dist/scanners/helpers/context-builder.js +148 -0
  122. package/dist/scanners/helpers/index.d.ts +3 -0
  123. package/dist/scanners/helpers/index.d.ts.map +1 -0
  124. package/dist/scanners/helpers/index.js +2 -0
  125. package/dist/scanners/index.d.ts +30 -0
  126. package/dist/scanners/index.d.ts.map +1 -0
  127. package/dist/scanners/index.js +102 -0
  128. package/dist/scanners/middleware/index.d.ts +4 -0
  129. package/dist/scanners/middleware/index.d.ts.map +1 -0
  130. package/dist/scanners/middleware/index.js +7 -0
  131. package/dist/scanners/middleware/missing-rate-limit.d.ts +13 -0
  132. package/dist/scanners/middleware/missing-rate-limit.d.ts.map +1 -0
  133. package/dist/scanners/middleware/missing-rate-limit.js +140 -0
  134. package/dist/scanners/network/cors-misconfiguration.d.ts +14 -0
  135. package/dist/scanners/network/cors-misconfiguration.d.ts.map +1 -0
  136. package/dist/scanners/network/cors-misconfiguration.js +89 -0
  137. package/dist/scanners/network/index.d.ts +7 -0
  138. package/dist/scanners/network/index.d.ts.map +1 -0
  139. package/dist/scanners/network/index.js +18 -0
  140. package/dist/scanners/network/missing-timeout.d.ts +15 -0
  141. package/dist/scanners/network/missing-timeout.d.ts.map +1 -0
  142. package/dist/scanners/network/missing-timeout.js +93 -0
  143. package/dist/scanners/network/open-redirect.d.ts +15 -0
  144. package/dist/scanners/network/open-redirect.d.ts.map +1 -0
  145. package/dist/scanners/network/open-redirect.js +88 -0
  146. package/dist/scanners/network/ssrf-prone-fetch.d.ts +12 -0
  147. package/dist/scanners/network/ssrf-prone-fetch.d.ts.map +1 -0
  148. package/dist/scanners/network/ssrf-prone-fetch.js +90 -0
  149. package/dist/scanners/nextjs-middleware.d.ts +26 -0
  150. package/dist/scanners/nextjs-middleware.d.ts.map +1 -0
  151. package/dist/scanners/nextjs-middleware.js +246 -0
  152. package/dist/scanners/privacy/debug-flags.d.ts +13 -0
  153. package/dist/scanners/privacy/debug-flags.d.ts.map +1 -0
  154. package/dist/scanners/privacy/debug-flags.js +124 -0
  155. package/dist/scanners/privacy/index.d.ts +6 -0
  156. package/dist/scanners/privacy/index.d.ts.map +1 -0
  157. package/dist/scanners/privacy/index.js +11 -0
  158. package/dist/scanners/privacy/over-broad-response.d.ts +15 -0
  159. package/dist/scanners/privacy/over-broad-response.d.ts.map +1 -0
  160. package/dist/scanners/privacy/over-broad-response.js +109 -0
  161. package/dist/scanners/privacy/sensitive-logging.d.ts +11 -0
  162. package/dist/scanners/privacy/sensitive-logging.d.ts.map +1 -0
  163. package/dist/scanners/privacy/sensitive-logging.js +78 -0
  164. package/dist/scanners/types.d.ts +456 -0
  165. package/dist/scanners/types.d.ts.map +1 -0
  166. package/dist/scanners/types.js +16 -0
  167. package/dist/scanners/unused-security-imports.d.ts +34 -0
  168. package/dist/scanners/unused-security-imports.d.ts.map +1 -0
  169. package/dist/scanners/unused-security-imports.js +206 -0
  170. package/dist/scanners/uploads/index.d.ts +5 -0
  171. package/dist/scanners/uploads/index.d.ts.map +1 -0
  172. package/dist/scanners/uploads/index.js +9 -0
  173. package/dist/scanners/uploads/missing-constraints.d.ts +15 -0
  174. package/dist/scanners/uploads/missing-constraints.d.ts.map +1 -0
  175. package/dist/scanners/uploads/missing-constraints.js +109 -0
  176. package/dist/scanners/uploads/public-path.d.ts +11 -0
  177. package/dist/scanners/uploads/public-path.d.ts.map +1 -0
  178. package/dist/scanners/uploads/public-path.js +87 -0
  179. package/dist/scanners/validation/client-side-only.d.ts +14 -0
  180. package/dist/scanners/validation/client-side-only.d.ts.map +1 -0
  181. package/dist/scanners/validation/client-side-only.js +140 -0
  182. package/dist/scanners/validation/ignored-validation.d.ts +12 -0
  183. package/dist/scanners/validation/ignored-validation.d.ts.map +1 -0
  184. package/dist/scanners/validation/ignored-validation.js +119 -0
  185. package/dist/scanners/validation/index.d.ts +5 -0
  186. package/dist/scanners/validation/index.d.ts.map +1 -0
  187. package/dist/scanners/validation/index.js +9 -0
  188. package/dist/utils/exclude-patterns.d.ts +35 -0
  189. package/dist/utils/exclude-patterns.d.ts.map +1 -0
  190. package/dist/utils/exclude-patterns.js +78 -0
  191. package/dist/utils/file-utils.d.ts +37 -0
  192. package/dist/utils/file-utils.d.ts.map +1 -0
  193. package/dist/utils/file-utils.js +77 -0
  194. package/dist/utils/fingerprint.d.ts +25 -0
  195. package/dist/utils/fingerprint.d.ts.map +1 -0
  196. package/dist/utils/fingerprint.js +28 -0
  197. package/dist/utils/git-info.d.ts +14 -0
  198. package/dist/utils/git-info.d.ts.map +1 -0
  199. package/dist/utils/git-info.js +55 -0
  200. package/dist/utils/index.d.ts +4 -0
  201. package/dist/utils/index.d.ts.map +1 -0
  202. package/dist/utils/index.js +3 -0
  203. package/dist/utils/progress.d.ts +42 -0
  204. package/dist/utils/progress.d.ts.map +1 -0
  205. package/dist/utils/progress.js +165 -0
  206. package/dist/utils/sarif-formatter.d.ts +92 -0
  207. package/dist/utils/sarif-formatter.d.ts.map +1 -0
  208. package/dist/utils/sarif-formatter.js +172 -0
  209. package/package.json +66 -0
@@ -0,0 +1,258 @@
1
+ import path from "node:path";
2
+ import { readFileSync, fileExists, resolvePath, writeFileSync, ensureDir } from "../utils/file-utils.js";
3
+ import { evaluate, PROFILE_NAMES, PROFILE_DESCRIPTIONS, WaiversFileSchema, PolicyConfigSchema, } from "@vibecheck/policy";
4
+ import { validateArtifact } from "@vibecheck/schema";
5
+ /**
6
+ * Load and validate scan artifact from file
7
+ */
8
+ function loadArtifact(filepath) {
9
+ const absolutePath = resolvePath(filepath);
10
+ if (!fileExists(absolutePath)) {
11
+ throw new Error(`Artifact file not found: ${filepath}`);
12
+ }
13
+ const content = readFileSync(absolutePath);
14
+ if (!content) {
15
+ throw new Error(`Failed to read artifact file: ${filepath}`);
16
+ }
17
+ const data = JSON.parse(content);
18
+ // Validate the artifact
19
+ return validateArtifact(data);
20
+ }
21
+ /**
22
+ * Load and validate waivers file
23
+ */
24
+ function loadWaivers(filepath) {
25
+ const absolutePath = resolvePath(filepath);
26
+ if (!fileExists(absolutePath)) {
27
+ throw new Error(`Waivers file not found: ${filepath}`);
28
+ }
29
+ const content = readFileSync(absolutePath);
30
+ if (!content) {
31
+ throw new Error(`Failed to read waivers file: ${filepath}`);
32
+ }
33
+ const data = JSON.parse(content);
34
+ const result = WaiversFileSchema.safeParse(data);
35
+ if (!result.success) {
36
+ throw new Error(`Invalid waivers file: ${result.error.message}`);
37
+ }
38
+ return result.data.waivers;
39
+ }
40
+ /**
41
+ * Load and validate policy config file
42
+ */
43
+ function loadConfig(filepath) {
44
+ const absolutePath = resolvePath(filepath);
45
+ if (!fileExists(absolutePath)) {
46
+ throw new Error(`Config file not found: ${filepath}`);
47
+ }
48
+ const content = readFileSync(absolutePath);
49
+ if (!content) {
50
+ throw new Error(`Failed to read config file: ${filepath}`);
51
+ }
52
+ const data = JSON.parse(content);
53
+ const result = PolicyConfigSchema.safeParse(data);
54
+ if (!result.success) {
55
+ throw new Error(`Invalid config file: ${result.error.message}`);
56
+ }
57
+ return result.data;
58
+ }
59
+ /**
60
+ * Format severity for console output with color
61
+ */
62
+ function formatSeverity(severity) {
63
+ const colors = {
64
+ critical: "\x1b[35m", // Magenta
65
+ high: "\x1b[31m", // Red
66
+ medium: "\x1b[33m", // Yellow
67
+ low: "\x1b[36m", // Cyan
68
+ info: "\x1b[90m", // Gray
69
+ };
70
+ const reset = "\x1b[0m";
71
+ return `${colors[severity]}${severity.toUpperCase()}${reset}`;
72
+ }
73
+ /**
74
+ * Format status for console output with color
75
+ */
76
+ function formatStatus(status) {
77
+ const colors = {
78
+ pass: "\x1b[32m", // Green
79
+ warn: "\x1b[33m", // Yellow
80
+ fail: "\x1b[31m", // Red
81
+ };
82
+ const reset = "\x1b[0m";
83
+ return `${colors[status]}${status.toUpperCase()}${reset}`;
84
+ }
85
+ /**
86
+ * Print evaluation summary to console
87
+ */
88
+ function printSummary(report) {
89
+ console.log("\n" + "=".repeat(60));
90
+ console.log("VibeCheck Policy Evaluation");
91
+ console.log("=".repeat(60));
92
+ console.log(`\nProfile: ${report.profileName ?? "custom"}`);
93
+ console.log(`Status: ${formatStatus(report.status)}`);
94
+ console.log(`Exit Code: ${report.exitCode}`);
95
+ console.log("\nSummary:");
96
+ console.log(` Total active findings: ${report.summary.total}`);
97
+ console.log(` Waived findings: ${report.summary.waived}`);
98
+ console.log(` Ignored by override: ${report.summary.ignored}`);
99
+ if (report.summary.total > 0) {
100
+ console.log("\nBy severity:");
101
+ if (report.summary.bySeverity.critical > 0) {
102
+ console.log(` ${formatSeverity("critical")}: ${report.summary.bySeverity.critical}`);
103
+ }
104
+ if (report.summary.bySeverity.high > 0) {
105
+ console.log(` ${formatSeverity("high")}: ${report.summary.bySeverity.high}`);
106
+ }
107
+ if (report.summary.bySeverity.medium > 0) {
108
+ console.log(` ${formatSeverity("medium")}: ${report.summary.bySeverity.medium}`);
109
+ }
110
+ if (report.summary.bySeverity.low > 0) {
111
+ console.log(` ${formatSeverity("low")}: ${report.summary.bySeverity.low}`);
112
+ }
113
+ if (report.summary.bySeverity.info > 0) {
114
+ console.log(` ${formatSeverity("info")}: ${report.summary.bySeverity.info}`);
115
+ }
116
+ }
117
+ // Show thresholds
118
+ console.log("\nThresholds:");
119
+ console.log(` Fail on severity >= ${report.thresholds.failOnSeverity} (confidence >= ${report.thresholds.minConfidenceForFail})`);
120
+ console.log(` Warn on severity >= ${report.thresholds.warnOnSeverity} (confidence >= ${report.thresholds.minConfidenceForWarn})`);
121
+ if (report.thresholds.maxFindings > 0) {
122
+ console.log(` Max findings: ${report.thresholds.maxFindings}`);
123
+ }
124
+ // Show regression if present
125
+ if (report.regression) {
126
+ console.log("\nRegression:");
127
+ console.log(` New findings: ${report.regression.newFindings.length}`);
128
+ console.log(` Resolved findings: ${report.regression.resolvedFindings.length}`);
129
+ console.log(` Severity regressions: ${report.regression.severityRegressions.length}`);
130
+ console.log(` Net change: ${report.regression.netChange > 0 ? "+" : ""}${report.regression.netChange}`);
131
+ }
132
+ // Show reasons
133
+ if (report.reasons.length > 0) {
134
+ console.log("\nReasons:");
135
+ for (const reason of report.reasons) {
136
+ const statusIcon = reason.status === "fail" ? "\x1b[31m✗\x1b[0m"
137
+ : reason.status === "warn" ? "\x1b[33m!\x1b[0m"
138
+ : "\x1b[32m✓\x1b[0m";
139
+ console.log(` ${statusIcon} ${reason.message}`);
140
+ }
141
+ }
142
+ // Show waived findings if any
143
+ if (report.waivedFindings.length > 0) {
144
+ console.log("\nWaived findings:");
145
+ for (const wf of report.waivedFindings.slice(0, 5)) {
146
+ const expiredNote = wf.expired ? " (EXPIRED)" : "";
147
+ console.log(` - [${wf.finding.ruleId}] ${wf.finding.title}${expiredNote}`);
148
+ console.log(` Reason: ${wf.waiver.reason}`);
149
+ }
150
+ if (report.waivedFindings.length > 5) {
151
+ console.log(` ... and ${report.waivedFindings.length - 5} more`);
152
+ }
153
+ }
154
+ console.log("");
155
+ }
156
+ /**
157
+ * Execute the evaluate command
158
+ */
159
+ export async function executeEvaluate(options) {
160
+ // Load artifact
161
+ const artifact = loadArtifact(options.artifact);
162
+ // Load baseline if provided
163
+ let baseline;
164
+ if (options.baseline) {
165
+ baseline = loadArtifact(options.baseline);
166
+ }
167
+ // Load config if provided, otherwise use profile
168
+ let config;
169
+ if (options.config) {
170
+ config = loadConfig(options.config);
171
+ }
172
+ // Load waivers if provided
173
+ let waivers = [];
174
+ if (options.waivers) {
175
+ waivers = loadWaivers(options.waivers);
176
+ }
177
+ // Run evaluation
178
+ const report = evaluate({
179
+ artifact,
180
+ baseline,
181
+ config,
182
+ profile: config ? undefined : options.profile,
183
+ waivers,
184
+ artifactPath: options.artifact,
185
+ });
186
+ // Output report
187
+ if (options.out) {
188
+ const outPath = resolvePath(options.out);
189
+ ensureDir(path.dirname(outPath));
190
+ writeFileSync(outPath, JSON.stringify(report, null, 2));
191
+ if (!options.quiet) {
192
+ console.log(`Policy report written to: ${outPath}`);
193
+ }
194
+ }
195
+ // Print summary unless quiet mode
196
+ if (!options.quiet) {
197
+ printSummary(report);
198
+ }
199
+ else {
200
+ // In quiet mode, just output JSON
201
+ if (!options.out) {
202
+ console.log(JSON.stringify(report, null, 2));
203
+ }
204
+ }
205
+ return report.exitCode;
206
+ }
207
+ /**
208
+ * Register evaluate command with commander
209
+ */
210
+ export function registerEvaluateCommand(program) {
211
+ program
212
+ .command("evaluate")
213
+ .description("Evaluate a scan artifact against a policy")
214
+ .requiredOption("-a, --artifact <path>", "Path to scan artifact (JSON)")
215
+ .option("-b, --baseline <path>", "Path to baseline artifact for regression detection")
216
+ .option("-p, --profile <name>", `Policy profile (${PROFILE_NAMES.join(", ")})`, "startup")
217
+ .option("-c, --config <path>", "Path to policy config file (overrides profile)")
218
+ .option("-w, --waivers <path>", "Path to waivers file")
219
+ .option("-o, --out <path>", "Output report to file")
220
+ .option("-q, --quiet", "JSON output only (no console summary)")
221
+ .addHelpText("after", `
222
+ Profiles:
223
+ ${PROFILE_NAMES.map((name) => ` ${name}: ${PROFILE_DESCRIPTIONS[name]}`).join("\n")}
224
+
225
+ Examples:
226
+ $ vibecheck evaluate -a ./vibecheck-scan.json
227
+ $ vibecheck evaluate -a ./scan.json -p strict
228
+ $ vibecheck evaluate -a ./scan.json -b ./baseline.json
229
+ $ vibecheck evaluate -a ./scan.json -w ./waivers.json
230
+ $ vibecheck evaluate -a ./scan.json -c ./policy.json
231
+ $ vibecheck evaluate -a ./scan.json -o ./report.json -q
232
+ `)
233
+ .action(async (cmdOptions) => {
234
+ // Validate profile
235
+ const profileStr = cmdOptions.profile;
236
+ if (!PROFILE_NAMES.includes(profileStr)) {
237
+ console.error(`\x1b[31mError: Invalid profile "${profileStr}". Valid options: ${PROFILE_NAMES.join(", ")}\x1b[0m`);
238
+ process.exit(1);
239
+ }
240
+ const options = {
241
+ artifact: cmdOptions.artifact,
242
+ baseline: cmdOptions.baseline,
243
+ profile: profileStr,
244
+ config: cmdOptions.config,
245
+ waivers: cmdOptions.waivers,
246
+ out: cmdOptions.out,
247
+ quiet: Boolean(cmdOptions.quiet),
248
+ };
249
+ try {
250
+ const exitCode = await executeEvaluate(options);
251
+ process.exit(exitCode);
252
+ }
253
+ catch (error) {
254
+ console.error(`\x1b[31mError: ${error instanceof Error ? error.message : String(error)}\x1b[0m`);
255
+ process.exit(1);
256
+ }
257
+ });
258
+ }
@@ -0,0 +1,12 @@
1
+ import type { Command } from "commander";
2
+ /**
3
+ * Execute the explain command
4
+ */
5
+ export declare function executeExplain(artifactPath: string, options: {
6
+ limit: number;
7
+ }): Promise<number>;
8
+ /**
9
+ * Register explain command with commander
10
+ */
11
+ export declare function registerExplainCommand(program: Command): void;
12
+ //# sourceMappingURL=explain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explain.d.ts","sourceRoot":"","sources":["../../src/commands/explain.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0MzC;;GAEG;AACH,wBAAsB,cAAc,CAClC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GACzB,OAAO,CAAC,MAAM,CAAC,CAkCjB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAU7D"}
@@ -0,0 +1,214 @@
1
+ import { validateArtifact, } from "@vibecheck/schema";
2
+ import { readFileSync, resolvePath } from "../utils/file-utils.js";
3
+ /**
4
+ * Format severity for console output with color
5
+ */
6
+ function formatSeverity(severity) {
7
+ const colors = {
8
+ critical: "\x1b[35m", // Magenta
9
+ high: "\x1b[31m", // Red
10
+ medium: "\x1b[33m", // Yellow
11
+ low: "\x1b[36m", // Cyan
12
+ info: "\x1b[90m", // Gray
13
+ };
14
+ const reset = "\x1b[0m";
15
+ return `${colors[severity]}${severity.toUpperCase().padEnd(8)}${reset}`;
16
+ }
17
+ /**
18
+ * Format category for display
19
+ */
20
+ function formatCategory(category) {
21
+ return `\x1b[34m${category}\x1b[0m`;
22
+ }
23
+ /**
24
+ * Print a horizontal divider
25
+ */
26
+ function divider(char = "-", length = 70) {
27
+ return char.repeat(length);
28
+ }
29
+ /**
30
+ * Print artifact summary
31
+ */
32
+ function printSummary(artifact) {
33
+ const { summary, tool, repo, generatedAt, metrics } = artifact;
34
+ console.log("\n" + divider("="));
35
+ console.log("VIBECHECK SCAN REPORT");
36
+ console.log(divider("="));
37
+ console.log(`\nTool: ${tool.name} v${tool.version}`);
38
+ if (repo) {
39
+ console.log(`Repository: ${repo.name}`);
40
+ if (repo.git?.branch) {
41
+ console.log(`Branch: ${repo.git.branch}`);
42
+ }
43
+ if (repo.git?.commit) {
44
+ console.log(`Commit: ${repo.git.commit.slice(0, 8)}`);
45
+ }
46
+ }
47
+ console.log(`Generated: ${new Date(generatedAt).toLocaleString()}`);
48
+ if (metrics) {
49
+ console.log(`\nScan Metrics:`);
50
+ console.log(` Files scanned: ${metrics.filesScanned}`);
51
+ console.log(` Duration: ${metrics.scanDurationMs.toFixed(0)}ms`);
52
+ console.log(` Rules run: ${metrics.rulesExecuted}`);
53
+ }
54
+ console.log(`\n${divider()}`);
55
+ console.log("SUMMARY");
56
+ console.log(divider());
57
+ console.log(`\nTotal Findings: ${summary.totalFindings}`);
58
+ if (summary.totalFindings > 0) {
59
+ console.log("\nBy Severity:");
60
+ const severities = ["critical", "high", "medium", "low", "info"];
61
+ for (const sev of severities) {
62
+ const count = summary.bySeverity[sev];
63
+ if (count > 0) {
64
+ const bar = "\u2588".repeat(Math.min(count * 2, 40));
65
+ console.log(` ${formatSeverity(sev)} ${count.toString().padStart(3)} ${bar}`);
66
+ }
67
+ }
68
+ console.log("\nBy Category:");
69
+ const categories = Object.entries(summary.byCategory)
70
+ .filter(([, count]) => count > 0)
71
+ .sort(([, a], [, b]) => b - a);
72
+ for (const [cat, count] of categories) {
73
+ console.log(` ${formatCategory(cat.padEnd(12))} ${count}`);
74
+ }
75
+ }
76
+ }
77
+ /**
78
+ * Sort findings by severity (highest first)
79
+ */
80
+ function sortBySeverity(findings) {
81
+ const order = {
82
+ critical: 4,
83
+ high: 3,
84
+ medium: 2,
85
+ low: 1,
86
+ info: 0,
87
+ };
88
+ return [...findings].sort((a, b) => order[b.severity] - order[a.severity]);
89
+ }
90
+ /**
91
+ * Print detailed finding information
92
+ */
93
+ function printFinding(finding, index) {
94
+ console.log(`\n${divider()}`);
95
+ console.log(`FINDING #${index + 1}: ${finding.title}`);
96
+ console.log(divider());
97
+ console.log(`\nSeverity: ${formatSeverity(finding.severity)}`);
98
+ console.log(`Category: ${formatCategory(finding.category)}`);
99
+ console.log(`Rule: ${finding.ruleId}`);
100
+ console.log(`Confidence: ${(finding.confidence * 100).toFixed(0)}%`);
101
+ console.log(`ID: ${finding.id}`);
102
+ console.log(`\nDescription:`);
103
+ console.log(` ${finding.description}`);
104
+ console.log(`\nEvidence:`);
105
+ for (const ev of finding.evidence) {
106
+ console.log(` - ${ev.file}:${ev.startLine}-${ev.endLine}`);
107
+ console.log(` ${ev.label}`);
108
+ if (ev.snippet) {
109
+ console.log(` \x1b[90m${ev.snippet}\x1b[0m`);
110
+ }
111
+ }
112
+ if (finding.claim) {
113
+ console.log(`\nClaim:`);
114
+ console.log(` Type: ${finding.claim.type}`);
115
+ console.log(` Source: ${finding.claim.source}`);
116
+ console.log(` Strength: ${finding.claim.strength}`);
117
+ console.log(` Evidence: "${finding.claim.textEvidence}"`);
118
+ }
119
+ if (finding.proof) {
120
+ console.log(`\nProof Trace:`);
121
+ console.log(` ${finding.proof.summary}`);
122
+ for (const node of finding.proof.nodes) {
123
+ const loc = node.file ? ` (${node.file}:${node.line})` : "";
124
+ console.log(` [${node.kind}] ${node.label}${loc}`);
125
+ }
126
+ }
127
+ console.log(`\nRemediation:`);
128
+ console.log(` ${finding.remediation.recommendedFix}`);
129
+ if (finding.remediation.patch) {
130
+ console.log(`\n Suggested patch:`);
131
+ console.log(` \x1b[90m${finding.remediation.patch}\x1b[0m`);
132
+ }
133
+ if (finding.links) {
134
+ console.log(`\nReferences:`);
135
+ if (finding.links.owasp) {
136
+ console.log(` OWASP: ${finding.links.owasp}`);
137
+ }
138
+ if (finding.links.cwe) {
139
+ console.log(` CWE: ${finding.links.cwe}`);
140
+ }
141
+ }
142
+ }
143
+ /**
144
+ * Print top findings with details
145
+ */
146
+ function printTopFindings(artifact, limit) {
147
+ const { findings } = artifact;
148
+ if (findings.length === 0) {
149
+ console.log("\n\x1b[32mNo findings to display.\x1b[0m");
150
+ return;
151
+ }
152
+ const sorted = sortBySeverity(findings);
153
+ const top = sorted.slice(0, limit);
154
+ console.log(`\n${divider("=")}`);
155
+ console.log(`TOP ${top.length} FINDINGS`);
156
+ console.log(divider("="));
157
+ for (let i = 0; i < top.length; i++) {
158
+ printFinding(top[i], i);
159
+ }
160
+ if (findings.length > limit) {
161
+ console.log(`\n${divider()}`);
162
+ console.log(`\x1b[90m... and ${findings.length - limit} more findings\x1b[0m`);
163
+ }
164
+ console.log("");
165
+ }
166
+ /**
167
+ * Execute the explain command
168
+ */
169
+ export async function executeExplain(artifactPath, options) {
170
+ const absolutePath = resolvePath(artifactPath);
171
+ // Read artifact file
172
+ const content = readFileSync(absolutePath);
173
+ if (!content) {
174
+ console.error(`\x1b[31mError: Could not read file: ${absolutePath}\x1b[0m`);
175
+ return 1;
176
+ }
177
+ // Parse JSON
178
+ let json;
179
+ try {
180
+ json = JSON.parse(content);
181
+ }
182
+ catch {
183
+ console.error(`\x1b[31mError: Invalid JSON in file: ${absolutePath}\x1b[0m`);
184
+ return 1;
185
+ }
186
+ // Validate artifact
187
+ let artifact;
188
+ try {
189
+ artifact = validateArtifact(json);
190
+ }
191
+ catch (error) {
192
+ console.error(`\x1b[31mError: Invalid artifact format\x1b[0m`);
193
+ console.error(error);
194
+ return 1;
195
+ }
196
+ // Print report
197
+ printSummary(artifact);
198
+ printTopFindings(artifact, options.limit);
199
+ return 0;
200
+ }
201
+ /**
202
+ * Register explain command with commander
203
+ */
204
+ export function registerExplainCommand(program) {
205
+ program
206
+ .command("explain <artifactPath>")
207
+ .description("Display a human-readable summary of a scan artifact")
208
+ .option("-l, --limit <number>", "Maximum findings to display", "5")
209
+ .action(async (artifactPath, options) => {
210
+ const limit = parseInt(options.limit, 10) || 5;
211
+ const exitCode = await executeExplain(artifactPath, { limit });
212
+ process.exit(exitCode);
213
+ });
214
+ }
@@ -0,0 +1,7 @@
1
+ export { registerScanCommand, executeScan, type ScanOptions } from "./scan.js";
2
+ export { registerExplainCommand, executeExplain } from "./explain.js";
3
+ export { registerDemoArtifactCommand, executeDemoArtifact } from "./demo-artifact.js";
4
+ export { registerIntentCommand, executeIntent, type IntentOptions } from "./intent.js";
5
+ export { registerEvaluateCommand, executeEvaluate, type EvaluateOptions } from "./evaluate.js";
6
+ export { registerWaiversCommand } from "./waivers.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAC/E,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EAAE,2BAA2B,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACtF,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACvF,OAAO,EAAE,uBAAuB,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAC/F,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { registerScanCommand, executeScan } from "./scan.js";
2
+ export { registerExplainCommand, executeExplain } from "./explain.js";
3
+ export { registerDemoArtifactCommand, executeDemoArtifact } from "./demo-artifact.js";
4
+ export { registerIntentCommand, executeIntent } from "./intent.js";
5
+ export { registerEvaluateCommand, executeEvaluate } from "./evaluate.js";
6
+ export { registerWaiversCommand } from "./waivers.js";
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Intent Command
3
+ *
4
+ * Generates a security intent map baseline for the codebase.
5
+ * Useful for tracking drift over time.
6
+ */
7
+ import type { Command } from "commander";
8
+ export interface IntentOptions {
9
+ out: string;
10
+ format: string;
11
+ repoName?: string;
12
+ }
13
+ /**
14
+ * Execute the intent command
15
+ */
16
+ export declare function executeIntent(targetDir: string, options: IntentOptions): Promise<number>;
17
+ /**
18
+ * Register intent command with commander
19
+ */
20
+ export declare function registerIntentCommand(program: Command): void;
21
+ //# sourceMappingURL=intent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intent.d.ts","sourceRoot":"","sources":["../../src/commands/intent.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqBzC,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAuCD;;GAEG;AACH,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,MAAM,CAAC,CA8DjB;AA+ID;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAgB5D"}