@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.
- package/README.md +135 -63
- package/bin/_deprecations.js +447 -19
- package/bin/_router.js +1 -1
- package/bin/registry.js +347 -280
- package/bin/runners/context/generators/cursor-enhanced.js +2439 -0
- package/bin/runners/lib/agent-firewall/enforcement/gateway.js +1059 -0
- package/bin/runners/lib/agent-firewall/enforcement/index.js +98 -0
- package/bin/runners/lib/agent-firewall/enforcement/mode.js +318 -0
- package/bin/runners/lib/agent-firewall/enforcement/orchestrator.js +484 -0
- package/bin/runners/lib/agent-firewall/enforcement/proof-artifact.js +418 -0
- package/bin/runners/lib/agent-firewall/enforcement/schemas/change-event.schema.json +173 -0
- package/bin/runners/lib/agent-firewall/enforcement/schemas/intent.schema.json +181 -0
- package/bin/runners/lib/agent-firewall/enforcement/schemas/verdict.schema.json +222 -0
- package/bin/runners/lib/agent-firewall/enforcement/verdict-v2.js +333 -0
- package/bin/runners/lib/agent-firewall/index.js +200 -0
- package/bin/runners/lib/agent-firewall/integration/index.js +20 -0
- package/bin/runners/lib/agent-firewall/integration/ship-gate.js +437 -0
- package/bin/runners/lib/agent-firewall/intent/alignment-engine.js +634 -0
- package/bin/runners/lib/agent-firewall/intent/auto-detect.js +426 -0
- package/bin/runners/lib/agent-firewall/intent/index.js +102 -0
- package/bin/runners/lib/agent-firewall/intent/schema.js +352 -0
- package/bin/runners/lib/agent-firewall/intent/store.js +283 -0
- package/bin/runners/lib/agent-firewall/interception/fs-interceptor.js +502 -0
- package/bin/runners/lib/agent-firewall/interception/index.js +23 -0
- package/bin/runners/lib/agent-firewall/interceptor/base.js +7 -3
- package/bin/runners/lib/agent-firewall/session/collector.js +451 -0
- package/bin/runners/lib/agent-firewall/session/index.js +26 -0
- package/bin/runners/lib/artifact-envelope.js +540 -0
- package/bin/runners/lib/auth-shared.js +977 -0
- package/bin/runners/lib/checkpoint.js +941 -0
- package/bin/runners/lib/cleanup/engine.js +571 -0
- package/bin/runners/lib/cleanup/index.js +53 -0
- package/bin/runners/lib/cleanup/output.js +375 -0
- package/bin/runners/lib/cleanup/rules.js +1060 -0
- package/bin/runners/lib/doctor/diagnosis-receipt.js +454 -0
- package/bin/runners/lib/doctor/failure-signatures.js +526 -0
- package/bin/runners/lib/doctor/fix-script.js +336 -0
- package/bin/runners/lib/doctor/modules/build-tools.js +453 -0
- package/bin/runners/lib/doctor/modules/index.js +62 -3
- package/bin/runners/lib/doctor/modules/os-quirks.js +706 -0
- package/bin/runners/lib/doctor/modules/repo-integrity.js +485 -0
- package/bin/runners/lib/doctor/safe-repair.js +384 -0
- package/bin/runners/lib/engine/ast-cache.js +210 -210
- package/bin/runners/lib/engine/auth-extractor.js +211 -211
- package/bin/runners/lib/engine/billing-extractor.js +112 -112
- package/bin/runners/lib/engine/enforcement-extractor.js +100 -100
- package/bin/runners/lib/engine/env-extractor.js +207 -207
- package/bin/runners/lib/engine/express-extractor.js +208 -208
- package/bin/runners/lib/engine/extractors.js +849 -849
- package/bin/runners/lib/engine/index.js +207 -207
- package/bin/runners/lib/engine/repo-index.js +514 -514
- package/bin/runners/lib/engine/types.js +124 -124
- package/bin/runners/lib/engines/attack-detector.js +1192 -0
- package/bin/runners/lib/entitlements-v2.js +2 -2
- package/bin/runners/lib/missions/briefing.js +427 -0
- package/bin/runners/lib/missions/checkpoint.js +753 -0
- package/bin/runners/lib/missions/hardening.js +851 -0
- package/bin/runners/lib/missions/plan.js +421 -32
- package/bin/runners/lib/missions/safety-gates.js +645 -0
- package/bin/runners/lib/missions/schema.js +478 -0
- package/bin/runners/lib/packs/bundle.js +675 -0
- package/bin/runners/lib/packs/evidence-pack.js +671 -0
- package/bin/runners/lib/packs/pack-factory.js +837 -0
- package/bin/runners/lib/packs/permissions-pack.js +686 -0
- package/bin/runners/lib/packs/proof-graph-pack.js +779 -0
- package/bin/runners/lib/safelist/index.js +96 -0
- package/bin/runners/lib/safelist/integration.js +334 -0
- package/bin/runners/lib/safelist/matcher.js +696 -0
- package/bin/runners/lib/safelist/schema.js +948 -0
- package/bin/runners/lib/safelist/store.js +438 -0
- package/bin/runners/lib/schemas/ship-manifest.schema.json +251 -0
- package/bin/runners/lib/ship-gate.js +832 -0
- package/bin/runners/lib/ship-manifest.js +1153 -0
- package/bin/runners/lib/ship-output.js +1 -1
- package/bin/runners/lib/unified-cli-output.js +710 -383
- package/bin/runners/lib/upsell.js +3 -3
- package/bin/runners/lib/why-tree.js +650 -0
- package/bin/runners/runAllowlist.js +33 -4
- package/bin/runners/runApprove.js +240 -1122
- package/bin/runners/runAudit.js +692 -0
- package/bin/runners/runAuth.js +325 -29
- package/bin/runners/runCheckpoint.js +442 -494
- package/bin/runners/runCleanup.js +343 -0
- package/bin/runners/runDoctor.js +269 -19
- package/bin/runners/runFix.js +411 -32
- package/bin/runners/runForge.js +411 -0
- package/bin/runners/runIntent.js +906 -0
- package/bin/runners/runKickoff.js +878 -0
- package/bin/runners/runLaunch.js +2000 -0
- package/bin/runners/runLink.js +785 -0
- package/bin/runners/runMcp.js +1741 -837
- package/bin/runners/runPacks.js +2089 -0
- package/bin/runners/runPolish.js +41 -0
- package/bin/runners/runReality.js +178 -1
- package/bin/runners/runSafelist.js +1190 -0
- package/bin/runners/runScan.js +21 -9
- package/bin/runners/runShield.js +1282 -0
- package/bin/runners/runShip.js +395 -16
- package/bin/vibecheck.js +34 -6
- package/mcp-server/README.md +117 -158
- package/mcp-server/handlers/index.ts +2 -2
- package/mcp-server/handlers/tool-handler.ts +50 -11
- package/mcp-server/index.js +16 -0
- package/mcp-server/intent-firewall-interceptor.js +529 -0
- package/mcp-server/lib/executor.ts +5 -5
- package/mcp-server/lib/index.ts +14 -4
- package/mcp-server/lib/sandbox.test.ts +4 -4
- package/mcp-server/lib/sandbox.ts +2 -2
- package/mcp-server/manifest.json +473 -0
- package/mcp-server/package.json +1 -1
- package/mcp-server/registry/tool-registry.js +315 -523
- package/mcp-server/registry/tools.json +442 -428
- package/mcp-server/registry.test.ts +18 -12
- package/mcp-server/tier-auth.js +68 -11
- package/mcp-server/tools-v3.js +70 -16
- package/mcp-server/tsconfig.json +1 -0
- package/package.json +2 -1
- 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
|
+
};
|