@vibecheckai/cli 3.7.0 → 3.9.0

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 (118) hide show
  1. package/README.md +135 -63
  2. package/bin/_deprecations.js +447 -19
  3. package/bin/_router.js +1 -1
  4. package/bin/registry.js +347 -280
  5. package/bin/runners/context/generators/cursor-enhanced.js +2439 -0
  6. package/bin/runners/lib/agent-firewall/enforcement/gateway.js +1059 -0
  7. package/bin/runners/lib/agent-firewall/enforcement/index.js +98 -0
  8. package/bin/runners/lib/agent-firewall/enforcement/mode.js +318 -0
  9. package/bin/runners/lib/agent-firewall/enforcement/orchestrator.js +484 -0
  10. package/bin/runners/lib/agent-firewall/enforcement/proof-artifact.js +418 -0
  11. package/bin/runners/lib/agent-firewall/enforcement/schemas/change-event.schema.json +173 -0
  12. package/bin/runners/lib/agent-firewall/enforcement/schemas/intent.schema.json +181 -0
  13. package/bin/runners/lib/agent-firewall/enforcement/schemas/verdict.schema.json +222 -0
  14. package/bin/runners/lib/agent-firewall/enforcement/verdict-v2.js +333 -0
  15. package/bin/runners/lib/agent-firewall/index.js +200 -0
  16. package/bin/runners/lib/agent-firewall/integration/index.js +20 -0
  17. package/bin/runners/lib/agent-firewall/integration/ship-gate.js +437 -0
  18. package/bin/runners/lib/agent-firewall/intent/alignment-engine.js +634 -0
  19. package/bin/runners/lib/agent-firewall/intent/auto-detect.js +426 -0
  20. package/bin/runners/lib/agent-firewall/intent/index.js +102 -0
  21. package/bin/runners/lib/agent-firewall/intent/schema.js +352 -0
  22. package/bin/runners/lib/agent-firewall/intent/store.js +283 -0
  23. package/bin/runners/lib/agent-firewall/interception/fs-interceptor.js +502 -0
  24. package/bin/runners/lib/agent-firewall/interception/index.js +23 -0
  25. package/bin/runners/lib/agent-firewall/interceptor/base.js +7 -3
  26. package/bin/runners/lib/agent-firewall/session/collector.js +451 -0
  27. package/bin/runners/lib/agent-firewall/session/index.js +26 -0
  28. package/bin/runners/lib/artifact-envelope.js +540 -0
  29. package/bin/runners/lib/auth-shared.js +977 -0
  30. package/bin/runners/lib/checkpoint.js +941 -0
  31. package/bin/runners/lib/cleanup/engine.js +571 -0
  32. package/bin/runners/lib/cleanup/index.js +53 -0
  33. package/bin/runners/lib/cleanup/output.js +375 -0
  34. package/bin/runners/lib/cleanup/rules.js +1060 -0
  35. package/bin/runners/lib/doctor/diagnosis-receipt.js +454 -0
  36. package/bin/runners/lib/doctor/failure-signatures.js +526 -0
  37. package/bin/runners/lib/doctor/fix-script.js +336 -0
  38. package/bin/runners/lib/doctor/modules/build-tools.js +453 -0
  39. package/bin/runners/lib/doctor/modules/index.js +62 -3
  40. package/bin/runners/lib/doctor/modules/os-quirks.js +706 -0
  41. package/bin/runners/lib/doctor/modules/repo-integrity.js +485 -0
  42. package/bin/runners/lib/doctor/safe-repair.js +384 -0
  43. package/bin/runners/lib/engine/ast-cache.js +210 -210
  44. package/bin/runners/lib/engine/auth-extractor.js +211 -211
  45. package/bin/runners/lib/engine/billing-extractor.js +112 -112
  46. package/bin/runners/lib/engine/enforcement-extractor.js +100 -100
  47. package/bin/runners/lib/engine/env-extractor.js +207 -207
  48. package/bin/runners/lib/engine/express-extractor.js +208 -208
  49. package/bin/runners/lib/engine/extractors.js +849 -849
  50. package/bin/runners/lib/engine/index.js +207 -207
  51. package/bin/runners/lib/engine/repo-index.js +514 -514
  52. package/bin/runners/lib/engine/types.js +124 -124
  53. package/bin/runners/lib/engines/attack-detector.js +1192 -0
  54. package/bin/runners/lib/entitlements-v2.js +2 -2
  55. package/bin/runners/lib/missions/briefing.js +427 -0
  56. package/bin/runners/lib/missions/checkpoint.js +753 -0
  57. package/bin/runners/lib/missions/hardening.js +851 -0
  58. package/bin/runners/lib/missions/plan.js +421 -32
  59. package/bin/runners/lib/missions/safety-gates.js +645 -0
  60. package/bin/runners/lib/missions/schema.js +478 -0
  61. package/bin/runners/lib/packs/bundle.js +675 -0
  62. package/bin/runners/lib/packs/evidence-pack.js +671 -0
  63. package/bin/runners/lib/packs/pack-factory.js +837 -0
  64. package/bin/runners/lib/packs/permissions-pack.js +686 -0
  65. package/bin/runners/lib/packs/proof-graph-pack.js +779 -0
  66. package/bin/runners/lib/safelist/index.js +96 -0
  67. package/bin/runners/lib/safelist/integration.js +334 -0
  68. package/bin/runners/lib/safelist/matcher.js +696 -0
  69. package/bin/runners/lib/safelist/schema.js +948 -0
  70. package/bin/runners/lib/safelist/store.js +438 -0
  71. package/bin/runners/lib/schemas/ship-manifest.schema.json +251 -0
  72. package/bin/runners/lib/ship-gate.js +832 -0
  73. package/bin/runners/lib/ship-manifest.js +1153 -0
  74. package/bin/runners/lib/ship-output.js +1 -1
  75. package/bin/runners/lib/unified-cli-output.js +710 -383
  76. package/bin/runners/lib/upsell.js +3 -3
  77. package/bin/runners/lib/why-tree.js +650 -0
  78. package/bin/runners/runAllowlist.js +33 -4
  79. package/bin/runners/runApprove.js +240 -1122
  80. package/bin/runners/runAudit.js +692 -0
  81. package/bin/runners/runAuth.js +325 -29
  82. package/bin/runners/runCheckpoint.js +442 -494
  83. package/bin/runners/runCleanup.js +343 -0
  84. package/bin/runners/runDoctor.js +269 -19
  85. package/bin/runners/runFix.js +411 -32
  86. package/bin/runners/runForge.js +411 -0
  87. package/bin/runners/runIntent.js +906 -0
  88. package/bin/runners/runKickoff.js +878 -0
  89. package/bin/runners/runLaunch.js +2000 -0
  90. package/bin/runners/runLink.js +785 -0
  91. package/bin/runners/runMcp.js +1741 -837
  92. package/bin/runners/runPacks.js +2089 -0
  93. package/bin/runners/runPolish.js +41 -0
  94. package/bin/runners/runReality.js +178 -1
  95. package/bin/runners/runSafelist.js +1190 -0
  96. package/bin/runners/runScan.js +21 -9
  97. package/bin/runners/runShield.js +1282 -0
  98. package/bin/runners/runShip.js +395 -16
  99. package/bin/vibecheck.js +34 -6
  100. package/mcp-server/README.md +117 -158
  101. package/mcp-server/handlers/index.ts +2 -2
  102. package/mcp-server/handlers/tool-handler.ts +50 -11
  103. package/mcp-server/index.js +16 -0
  104. package/mcp-server/intent-firewall-interceptor.js +529 -0
  105. package/mcp-server/lib/executor.ts +5 -5
  106. package/mcp-server/lib/index.ts +14 -4
  107. package/mcp-server/lib/sandbox.test.ts +4 -4
  108. package/mcp-server/lib/sandbox.ts +2 -2
  109. package/mcp-server/manifest.json +473 -0
  110. package/mcp-server/package.json +1 -1
  111. package/mcp-server/registry/tool-registry.js +315 -523
  112. package/mcp-server/registry/tools.json +442 -428
  113. package/mcp-server/registry.test.ts +18 -12
  114. package/mcp-server/tier-auth.js +68 -11
  115. package/mcp-server/tools-v3.js +70 -16
  116. package/mcp-server/tsconfig.json +1 -0
  117. package/package.json +2 -1
  118. package/bin/runners/runProof.zip +0 -0
@@ -0,0 +1,96 @@
1
+ /**
2
+ * vibecheck safelist Module Index
3
+ *
4
+ * ═══════════════════════════════════════════════════════════════════════════════
5
+ * A SCALPEL, NOT A TRASH CAN
6
+ * ═══════════════════════════════════════════════════════════════════════════════
7
+ *
8
+ * Responsible suppression of known findings with:
9
+ * - Required justification
10
+ * - Owner accountability
11
+ * - Optional expiration
12
+ * - Audit trail
13
+ * - Scope control (repo-wide vs local)
14
+ * - Abuse prevention limits
15
+ * - Conflict detection
16
+ */
17
+
18
+ "use strict";
19
+
20
+ const schema = require("./schema");
21
+ const store = require("./store");
22
+ const matcher = require("./matcher");
23
+ const integration = require("./integration");
24
+
25
+ module.exports = {
26
+ // ═══════════════════════════════════════════════════════════════════════════
27
+ // SCHEMA & VALIDATION
28
+ // ═══════════════════════════════════════════════════════════════════════════
29
+ SCHEMA_VERSION: schema.SCHEMA_VERSION,
30
+ LIMITS: schema.LIMITS,
31
+ JUSTIFICATION_CATEGORIES: schema.JUSTIFICATION_CATEGORIES,
32
+ SCOPE_TYPES: schema.SCOPE_TYPES,
33
+ SAFELIST_COMMANDS: schema.SAFELIST_COMMANDS,
34
+ DANGEROUS_PATTERNS: schema.DANGEROUS_PATTERNS,
35
+ SafelistValidationError: schema.SafelistValidationError,
36
+ isDangerousPattern: schema.isDangerousPattern,
37
+ isValidISODate: schema.isValidISODate,
38
+ isValidEmail: schema.isValidEmail,
39
+ isValidUrl: schema.isValidUrl,
40
+ validateEntry: schema.validateEntry,
41
+ validateSafelist: schema.validateSafelist,
42
+ patternsOverlap: schema.patternsOverlap,
43
+ filesOverlap: schema.filesOverlap,
44
+ generateEntryId: schema.generateEntryId,
45
+ createEntry: schema.createEntry,
46
+ createEmptySafelist: schema.createEmptySafelist,
47
+
48
+ // ═══════════════════════════════════════════════════════════════════════════
49
+ // STORAGE & PERSISTENCE
50
+ // ═══════════════════════════════════════════════════════════════════════════
51
+ getSafelistPaths: store.getSafelistPaths,
52
+ loadSafelistFile: store.loadSafelistFile,
53
+ loadSafelist: store.loadSafelist,
54
+ saveSafelistFile: store.saveSafelistFile,
55
+ saveEntry: store.saveEntry,
56
+ removeEntry: store.removeEntry,
57
+ updateEntry: store.updateEntry,
58
+ migrateLegacyAllowlist: store.migrateLegacyAllowlist,
59
+ ensureGitIgnore: store.ensureGitIgnore,
60
+
61
+ // ═══════════════════════════════════════════════════════════════════════════
62
+ // MATCHING & FILTERING
63
+ // ═══════════════════════════════════════════════════════════════════════════
64
+ matchEntry: matcher.matchEntry,
65
+ isSuppressed: matcher.isSuppressed,
66
+ filterFindings: matcher.filterFindings,
67
+ previewSuppression: matcher.previewSuppression,
68
+ findUnusedEntries: matcher.findUnusedEntries,
69
+ calculateConfidence: matcher.calculateConfidence,
70
+
71
+ // ═══════════════════════════════════════════════════════════════════════════
72
+ // LIFECYCLE MANAGEMENT
73
+ // ═══════════════════════════════════════════════════════════════════════════
74
+ updateMatchCounts: matcher.updateMatchCounts,
75
+ getExpiringSoon: matcher.getExpiringSoon,
76
+ getDueForReview: matcher.getDueForReview,
77
+ getExpired: matcher.getExpired,
78
+ getUnused: matcher.getUnused,
79
+
80
+ // ═══════════════════════════════════════════════════════════════════════════
81
+ // UTILITIES
82
+ // ═══════════════════════════════════════════════════════════════════════════
83
+ normalizePath: matcher.normalizePath,
84
+ matchGlob: matcher.matchGlob,
85
+ matchBranch: matcher.matchBranch,
86
+ getCachedRegex: matcher.getCachedRegex,
87
+
88
+ // ═══════════════════════════════════════════════════════════════════════════
89
+ // INTEGRATION (for audit, ship, scan, etc.)
90
+ // ═══════════════════════════════════════════════════════════════════════════
91
+ applySafelist: integration.applySafelist,
92
+ calculateHealth: integration.calculateHealth,
93
+ formatSuppressedFindings: integration.formatSuppressedFindings,
94
+ getSafelistSummary: integration.getSafelistSummary,
95
+ quickSuppress: integration.quickSuppress,
96
+ };
@@ -0,0 +1,334 @@
1
+ /**
2
+ * vibecheck safelist - Integration Hooks
3
+ *
4
+ * ═══════════════════════════════════════════════════════════════════════════════
5
+ * Provides integration points for other commands (audit, ship, scan, etc.)
6
+ * to apply safelist filtering and report suppressed findings.
7
+ * ═══════════════════════════════════════════════════════════════════════════════
8
+ */
9
+
10
+ "use strict";
11
+
12
+ const { loadSafelist, updateEntry } = require("./store");
13
+ const { filterFindings, getExpired, getExpiringSoon, getDueForReview } = require("./matcher");
14
+ const { SAFELIST_COMMANDS } = require("./schema");
15
+
16
+ // ═══════════════════════════════════════════════════════════════════════════════
17
+ // INTEGRATION API
18
+ // ═══════════════════════════════════════════════════════════════════════════════
19
+
20
+ /**
21
+ * Apply safelist filtering to findings
22
+ * This is the main integration point for other commands.
23
+ *
24
+ * @param {Array} findings - Findings to filter
25
+ * @param {Object} options - Options
26
+ * @param {string} options.projectRoot - Project root directory
27
+ * @param {string} options.command - Command name (e.g., "audit", "ship")
28
+ * @param {string} [options.branch] - Current git branch
29
+ * @param {boolean} [options.updateMatchCounts=true] - Update match counts in safelist
30
+ * @param {boolean} [options.includeHealthWarnings=true] - Include safelist health warnings
31
+ * @returns {{
32
+ * active: Array,
33
+ * suppressed: Array,
34
+ * stats: Object,
35
+ * warnings: string[],
36
+ * safelistHealth: Object
37
+ * }}
38
+ */
39
+ function applySafelist(findings, options = {}) {
40
+ const {
41
+ projectRoot = process.cwd(),
42
+ command = "audit",
43
+ branch = null,
44
+ updateMatchCounts = true,
45
+ includeHealthWarnings = true,
46
+ } = options;
47
+
48
+ // Check if command supports safelist
49
+ if (!SAFELIST_COMMANDS.includes(command)) {
50
+ return {
51
+ active: findings,
52
+ suppressed: [],
53
+ stats: { total: findings.length, active: findings.length, suppressed: 0 },
54
+ warnings: [],
55
+ safelistHealth: null,
56
+ };
57
+ }
58
+
59
+ // Load safelist
60
+ const {
61
+ safelist,
62
+ warnings: loadWarnings,
63
+ errors: loadErrors
64
+ } = loadSafelist(projectRoot);
65
+
66
+ const warnings = [...loadWarnings];
67
+
68
+ // Handle load errors gracefully - don't fail, just don't filter
69
+ if (loadErrors.length > 0) {
70
+ warnings.push(...loadErrors.map(e => `Safelist load error: ${e}`));
71
+ return {
72
+ active: findings,
73
+ suppressed: [],
74
+ stats: { total: findings.length, active: findings.length, suppressed: 0 },
75
+ warnings,
76
+ safelistHealth: { status: "error", errors: loadErrors },
77
+ };
78
+ }
79
+
80
+ // No entries? Skip filtering
81
+ if (!safelist.entries || safelist.entries.length === 0) {
82
+ return {
83
+ active: findings,
84
+ suppressed: [],
85
+ stats: { total: findings.length, active: findings.length, suppressed: 0 },
86
+ warnings: [],
87
+ safelistHealth: { status: "empty", entries: 0 },
88
+ };
89
+ }
90
+
91
+ // Apply filtering
92
+ const { active, suppressed, stats } = filterFindings(findings, safelist, {
93
+ command,
94
+ branch,
95
+ projectRoot,
96
+ });
97
+
98
+ // Update match counts if requested
99
+ if (updateMatchCounts && stats.matchedEntries && stats.matchedEntries.length > 0) {
100
+ const now = new Date().toISOString();
101
+
102
+ for (const entryId of stats.matchedEntries) {
103
+ const entry = safelist.entries.find(e => e.id === entryId);
104
+ if (entry) {
105
+ try {
106
+ updateEntry(projectRoot, entryId, {
107
+ lifecycle: {
108
+ lastMatchedAt: now,
109
+ matchCount: (entry.lifecycle?.matchCount || 0) + stats.byEntry[entryId],
110
+ },
111
+ });
112
+ } catch {
113
+ // Ignore update errors - non-critical
114
+ }
115
+ }
116
+ }
117
+ }
118
+
119
+ // Calculate safelist health
120
+ const safelistHealth = calculateHealth(safelist, includeHealthWarnings ? warnings : []);
121
+
122
+ return {
123
+ active,
124
+ suppressed,
125
+ stats,
126
+ warnings,
127
+ safelistHealth,
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Calculate safelist health metrics
133
+ */
134
+ function calculateHealth(safelist, warnings = []) {
135
+ const expired = getExpired(safelist);
136
+ const expiringSoon = getExpiringSoon(safelist);
137
+ const dueForReview = getDueForReview(safelist);
138
+
139
+ let status = "healthy";
140
+
141
+ if (expired.length > 0) {
142
+ status = "needs-attention";
143
+ warnings.push(`${expired.length} safelist entries have expired - run 'vibecheck safelist clean'`);
144
+ }
145
+
146
+ if (expiringSoon.length > 0) {
147
+ warnings.push(`${expiringSoon.length} safelist entries expiring within 7 days`);
148
+ }
149
+
150
+ if (dueForReview.length > 0) {
151
+ status = "needs-attention";
152
+ warnings.push(`${dueForReview.length} safelist entries are due for review`);
153
+ }
154
+
155
+ return {
156
+ status,
157
+ entries: safelist.entries.length,
158
+ expired: expired.length,
159
+ expiringSoon: expiringSoon.length,
160
+ dueForReview: dueForReview.length,
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Format suppressed findings for output
166
+ *
167
+ * @param {Array} suppressed - Suppressed findings
168
+ * @param {Object} options - Formatting options
169
+ * @returns {string} Formatted output
170
+ */
171
+ function formatSuppressedFindings(suppressed, options = {}) {
172
+ const {
173
+ format = "text",
174
+ verbose = false,
175
+ maxItems = 10,
176
+ } = options;
177
+
178
+ if (suppressed.length === 0) {
179
+ return "";
180
+ }
181
+
182
+ if (format === "json") {
183
+ return JSON.stringify({
184
+ suppressedCount: suppressed.length,
185
+ suppressed: suppressed.slice(0, verbose ? Infinity : maxItems).map(f => ({
186
+ id: f.id,
187
+ message: f.message,
188
+ file: f.file,
189
+ line: f.line,
190
+ suppressedBy: f._suppression?.entryId,
191
+ reason: f._suppression?.reason,
192
+ category: f._suppression?.category,
193
+ owner: f._suppression?.owner,
194
+ })),
195
+ }, null, 2);
196
+ }
197
+
198
+ // Text format
199
+ const lines = [];
200
+ lines.push("");
201
+ lines.push("╔══════════════════════════════════════════════════════════════════════════════╗");
202
+ lines.push("║ 🔇 SUPPRESSED FINDINGS ║");
203
+ lines.push("╚══════════════════════════════════════════════════════════════════════════════╝");
204
+ lines.push("");
205
+ lines.push(` ${suppressed.length} finding(s) suppressed by safelist entries.`);
206
+ lines.push("");
207
+
208
+ const displayItems = verbose ? suppressed : suppressed.slice(0, maxItems);
209
+
210
+ // Group by entry
211
+ const byEntry = {};
212
+ for (const finding of displayItems) {
213
+ const entryId = finding._suppression?.entryId || "unknown";
214
+ if (!byEntry[entryId]) {
215
+ byEntry[entryId] = {
216
+ entry: finding._suppression,
217
+ findings: [],
218
+ };
219
+ }
220
+ byEntry[entryId].findings.push(finding);
221
+ }
222
+
223
+ for (const [entryId, group] of Object.entries(byEntry)) {
224
+ lines.push(` ┌─ ${entryId} (${group.entry?.category || "unknown"})`);
225
+ lines.push(` │ Reason: ${group.entry?.reason || "No reason"}`);
226
+ lines.push(` │ Owner: ${group.entry?.owner || "Unknown"}`);
227
+ lines.push(` │`);
228
+
229
+ for (const finding of group.findings.slice(0, 5)) {
230
+ const loc = finding.file ? `${finding.file}${finding.line ? `:${finding.line}` : ""}` : "unknown";
231
+ lines.push(` │ • ${finding.id || finding.type}: ${(finding.message || "").substring(0, 50)}...`);
232
+ lines.push(` │ at ${loc}`);
233
+ }
234
+
235
+ if (group.findings.length > 5) {
236
+ lines.push(` │ ... and ${group.findings.length - 5} more`);
237
+ }
238
+
239
+ lines.push(` └────────────────────────────────────────`);
240
+ lines.push("");
241
+ }
242
+
243
+ if (!verbose && suppressed.length > maxItems) {
244
+ lines.push(` ... and ${suppressed.length - maxItems} more suppressed findings.`);
245
+ lines.push(` Run with --verbose to see all, or use 'vibecheck safelist list' to review entries.`);
246
+ lines.push("");
247
+ }
248
+
249
+ return lines.join("\n");
250
+ }
251
+
252
+ /**
253
+ * Get a summary of safelist status for CLI output
254
+ */
255
+ function getSafelistSummary(projectRoot) {
256
+ const { safelist, warnings, errors } = loadSafelist(projectRoot);
257
+
258
+ if (errors.length > 0) {
259
+ return {
260
+ status: "error",
261
+ message: `Safelist error: ${errors[0]}`,
262
+ entries: 0,
263
+ };
264
+ }
265
+
266
+ const health = calculateHealth(safelist, []);
267
+
268
+ return {
269
+ status: health.status,
270
+ message: health.status === "healthy"
271
+ ? `${safelist.entries.length} safelist entries active`
272
+ : `Safelist needs attention (${health.expired} expired, ${health.dueForReview} due for review)`,
273
+ entries: safelist.entries.length,
274
+ expired: health.expired,
275
+ expiringSoon: health.expiringSoon,
276
+ dueForReview: health.dueForReview,
277
+ };
278
+ }
279
+
280
+ /**
281
+ * Create a quick suppression entry (for inline suppression from CLI)
282
+ * This is a convenience function for rapid safelist entry creation.
283
+ *
284
+ * @param {Object} finding - Finding to suppress
285
+ * @param {Object} options - Options
286
+ * @returns {{ success: boolean, entryId: string|null, error: string|null }}
287
+ */
288
+ function quickSuppress(finding, options = {}) {
289
+ const {
290
+ projectRoot = process.cwd(),
291
+ reason = "Manually suppressed via CLI",
292
+ category = "false-positive",
293
+ owner = process.env.USER || process.env.USERNAME || "cli",
294
+ scope = "repo",
295
+ expires = null,
296
+ } = options;
297
+
298
+ const { createEntry } = require("./schema");
299
+ const { saveEntry } = require("./store");
300
+
301
+ try {
302
+ const entry = createEntry({
303
+ type: "finding",
304
+ findingId: finding.id,
305
+ reason,
306
+ category,
307
+ ownerName: owner,
308
+ scopeType: scope,
309
+ expiresIn: expires,
310
+ });
311
+
312
+ const result = saveEntry(projectRoot, entry, scope);
313
+
314
+ if (result.success) {
315
+ return { success: true, entryId: entry.id, error: null };
316
+ } else {
317
+ return { success: false, entryId: null, error: result.error };
318
+ }
319
+ } catch (err) {
320
+ return { success: false, entryId: null, error: err.message };
321
+ }
322
+ }
323
+
324
+ // ═══════════════════════════════════════════════════════════════════════════════
325
+ // EXPORTS
326
+ // ═══════════════════════════════════════════════════════════════════════════════
327
+
328
+ module.exports = {
329
+ applySafelist,
330
+ calculateHealth,
331
+ formatSuppressedFindings,
332
+ getSafelistSummary,
333
+ quickSuppress,
334
+ };