@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,571 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibecheck polish --cleanup Engine
|
|
3
|
+
*
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
* CLEANUP ENGINE - Safe Code Transformations
|
|
6
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
+
*
|
|
8
|
+
* Applies cleanup rules to files with:
|
|
9
|
+
* - Full diff preview before changes
|
|
10
|
+
* - Automatic backup for rollback
|
|
11
|
+
* - Risk assessment for each transformation
|
|
12
|
+
* - Support for regex and function-based pattern detection
|
|
13
|
+
* - File type filtering and exclusion patterns
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
"use strict";
|
|
17
|
+
|
|
18
|
+
const fs = require("fs");
|
|
19
|
+
const path = require("path");
|
|
20
|
+
const {
|
|
21
|
+
SAFE_RULES,
|
|
22
|
+
MODERATE_RULES,
|
|
23
|
+
AGGRESSIVE_RULES,
|
|
24
|
+
CATEGORIES,
|
|
25
|
+
RISK_LEVELS,
|
|
26
|
+
} = require("./rules");
|
|
27
|
+
|
|
28
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
29
|
+
// CONFIGURATION
|
|
30
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* File extensions to scan by default
|
|
34
|
+
*/
|
|
35
|
+
const SCANNABLE_EXTENSIONS = new Set([
|
|
36
|
+
".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs",
|
|
37
|
+
".vue", ".svelte", ".astro",
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Directories to always skip
|
|
42
|
+
*/
|
|
43
|
+
const SKIP_DIRS = new Set([
|
|
44
|
+
"node_modules", ".git", "dist", "build", ".next", ".nuxt",
|
|
45
|
+
"coverage", ".vibecheck", ".cache", "__pycache__", ".turbo",
|
|
46
|
+
"out", ".output", ".vercel", ".netlify", ".amplify",
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Files to always skip (patterns)
|
|
51
|
+
*/
|
|
52
|
+
const SKIP_FILES = [
|
|
53
|
+
/\.min\.js$/,
|
|
54
|
+
/\.bundle\.js$/,
|
|
55
|
+
/\.d\.ts$/,
|
|
56
|
+
/\.map$/,
|
|
57
|
+
/package-lock\.json$/,
|
|
58
|
+
/yarn\.lock$/,
|
|
59
|
+
/pnpm-lock\.yaml$/,
|
|
60
|
+
/\.lock$/,
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Maximum file size to scan (in bytes) - skip very large files
|
|
65
|
+
*/
|
|
66
|
+
const MAX_FILE_SIZE = 1024 * 1024; // 1MB
|
|
67
|
+
|
|
68
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
69
|
+
// GLOB MATCHING
|
|
70
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Simple glob pattern matching
|
|
74
|
+
* Supports: *, **, ?, [abc], [!abc]
|
|
75
|
+
*/
|
|
76
|
+
function matchGlob(pattern, filePath) {
|
|
77
|
+
// Normalize paths
|
|
78
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
79
|
+
const normalizedPattern = pattern.replace(/\\/g, "/");
|
|
80
|
+
|
|
81
|
+
// Convert glob to regex
|
|
82
|
+
let regex = normalizedPattern
|
|
83
|
+
.replace(/\./g, "\\.")
|
|
84
|
+
.replace(/\*\*/g, "{{GLOBSTAR}}")
|
|
85
|
+
.replace(/\*/g, "[^/]*")
|
|
86
|
+
.replace(/\?/g, "[^/]")
|
|
87
|
+
.replace(/{{GLOBSTAR}}/g, ".*");
|
|
88
|
+
|
|
89
|
+
// If pattern doesn't start with **, match anywhere
|
|
90
|
+
if (!normalizedPattern.startsWith("**/") && !normalizedPattern.startsWith("/")) {
|
|
91
|
+
regex = `(?:^|/)${regex}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return new RegExp(regex + "$", "i").test(normalizedPath);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check if file matches any exclusion pattern
|
|
99
|
+
*/
|
|
100
|
+
function isExcluded(filePath, excludePatterns = []) {
|
|
101
|
+
if (!excludePatterns || excludePatterns.length === 0) return false;
|
|
102
|
+
|
|
103
|
+
for (const pattern of excludePatterns) {
|
|
104
|
+
if (matchGlob(pattern, filePath)) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
113
|
+
// FILE SCANNING
|
|
114
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Check if a file should be skipped
|
|
118
|
+
*/
|
|
119
|
+
function shouldSkipFile(filePath, fileName) {
|
|
120
|
+
// Check extension
|
|
121
|
+
const ext = path.extname(fileName).toLowerCase();
|
|
122
|
+
if (!SCANNABLE_EXTENSIONS.has(ext)) return true;
|
|
123
|
+
|
|
124
|
+
// Check skip patterns
|
|
125
|
+
for (const pattern of SKIP_FILES) {
|
|
126
|
+
if (pattern.test(fileName)) return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Recursively find all scannable files with robust error handling
|
|
134
|
+
*/
|
|
135
|
+
async function findFiles(dir, opts = {}) {
|
|
136
|
+
const {
|
|
137
|
+
maxDepth = 10,
|
|
138
|
+
maxFiles = 5000,
|
|
139
|
+
exclude = [],
|
|
140
|
+
} = opts;
|
|
141
|
+
|
|
142
|
+
const results = [];
|
|
143
|
+
const errors = [];
|
|
144
|
+
|
|
145
|
+
async function walk(currentDir, depth) {
|
|
146
|
+
if (depth >= maxDepth) return;
|
|
147
|
+
if (results.length >= maxFiles) return;
|
|
148
|
+
|
|
149
|
+
let entries;
|
|
150
|
+
try {
|
|
151
|
+
entries = await fs.promises.readdir(currentDir, { withFileTypes: true });
|
|
152
|
+
} catch (err) {
|
|
153
|
+
if (err.code !== "ENOENT" && err.code !== "EACCES" && err.code !== "EPERM") {
|
|
154
|
+
errors.push({ path: currentDir, error: err.message });
|
|
155
|
+
}
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
for (const entry of entries) {
|
|
160
|
+
if (results.length >= maxFiles) break;
|
|
161
|
+
|
|
162
|
+
// Skip hidden files/dirs (except .cursorrules, etc.)
|
|
163
|
+
if (entry.name.startsWith(".") && !entry.name.startsWith(".cursor")) continue;
|
|
164
|
+
|
|
165
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
166
|
+
|
|
167
|
+
// Check exclusion patterns
|
|
168
|
+
if (isExcluded(fullPath, exclude)) continue;
|
|
169
|
+
|
|
170
|
+
if (entry.isDirectory()) {
|
|
171
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
172
|
+
await walk(fullPath, depth + 1);
|
|
173
|
+
} else if (entry.isFile()) {
|
|
174
|
+
if (shouldSkipFile(fullPath, entry.name)) continue;
|
|
175
|
+
|
|
176
|
+
// Check file size
|
|
177
|
+
try {
|
|
178
|
+
const stat = await fs.promises.stat(fullPath);
|
|
179
|
+
if (stat.size > MAX_FILE_SIZE) continue;
|
|
180
|
+
} catch {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
results.push(fullPath);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
await walk(dir, 0);
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
files: results,
|
|
193
|
+
errors,
|
|
194
|
+
truncated: results.length >= maxFiles,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
199
|
+
// FINDING DETECTION
|
|
200
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Analyze a single file for cleanup findings
|
|
204
|
+
*/
|
|
205
|
+
function analyzeFile(filePath, content, rules) {
|
|
206
|
+
const findings = [];
|
|
207
|
+
const lines = content.split("\n");
|
|
208
|
+
|
|
209
|
+
for (const [ruleId, rule] of Object.entries(rules)) {
|
|
210
|
+
if (!rule.pattern) continue;
|
|
211
|
+
|
|
212
|
+
// Handle array of patterns
|
|
213
|
+
const patterns = Array.isArray(rule.patterns) ? rule.patterns : [rule.pattern];
|
|
214
|
+
|
|
215
|
+
for (const pattern of patterns) {
|
|
216
|
+
// Reset lastIndex for global patterns
|
|
217
|
+
if (pattern.global) {
|
|
218
|
+
pattern.lastIndex = 0;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
let match;
|
|
222
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
223
|
+
// Calculate line number
|
|
224
|
+
const beforeMatch = content.substring(0, match.index);
|
|
225
|
+
const lineNumber = beforeMatch.split("\n").length;
|
|
226
|
+
const lineContent = lines[lineNumber - 1] || "";
|
|
227
|
+
|
|
228
|
+
findings.push({
|
|
229
|
+
ruleId,
|
|
230
|
+
rule,
|
|
231
|
+
file: filePath,
|
|
232
|
+
line: lineNumber,
|
|
233
|
+
column: match.index - beforeMatch.lastIndexOf("\n") - 1,
|
|
234
|
+
match: match[0],
|
|
235
|
+
lineContent: lineContent.trim(),
|
|
236
|
+
startIndex: match.index,
|
|
237
|
+
endIndex: match.index + match[0].length,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Prevent infinite loop for non-global patterns
|
|
241
|
+
if (!pattern.global) break;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return findings;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Analyze entire project
|
|
251
|
+
*/
|
|
252
|
+
async function analyzeProject(projectPath, opts = {}) {
|
|
253
|
+
const {
|
|
254
|
+
aggressive = false,
|
|
255
|
+
includeModerate = true,
|
|
256
|
+
maxFiles = 1000,
|
|
257
|
+
} = opts;
|
|
258
|
+
|
|
259
|
+
// Build rule set based on options
|
|
260
|
+
let rules = { ...SAFE_RULES };
|
|
261
|
+
if (includeModerate) {
|
|
262
|
+
rules = { ...rules, ...MODERATE_RULES };
|
|
263
|
+
}
|
|
264
|
+
if (aggressive) {
|
|
265
|
+
rules = { ...rules, ...AGGRESSIVE_RULES };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const files = await findFiles(projectPath);
|
|
269
|
+
const limitedFiles = files.slice(0, maxFiles);
|
|
270
|
+
|
|
271
|
+
const allFindings = [];
|
|
272
|
+
const fileResults = [];
|
|
273
|
+
|
|
274
|
+
for (const filePath of limitedFiles) {
|
|
275
|
+
try {
|
|
276
|
+
const content = await fs.promises.readFile(filePath, "utf8");
|
|
277
|
+
const findings = analyzeFile(filePath, content, rules);
|
|
278
|
+
|
|
279
|
+
if (findings.length > 0) {
|
|
280
|
+
fileResults.push({
|
|
281
|
+
file: filePath,
|
|
282
|
+
relativePath: path.relative(projectPath, filePath),
|
|
283
|
+
findingCount: findings.length,
|
|
284
|
+
findings,
|
|
285
|
+
});
|
|
286
|
+
allFindings.push(...findings);
|
|
287
|
+
}
|
|
288
|
+
} catch (err) {
|
|
289
|
+
// Skip files that can't be read
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Group by risk level
|
|
294
|
+
const byRisk = {
|
|
295
|
+
safe: allFindings.filter(f => f.rule.riskLevel === "safe"),
|
|
296
|
+
moderate: allFindings.filter(f => f.rule.riskLevel === "moderate"),
|
|
297
|
+
aggressive: allFindings.filter(f => f.rule.riskLevel === "aggressive"),
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// Group by category
|
|
301
|
+
const byCategory = {};
|
|
302
|
+
for (const finding of allFindings) {
|
|
303
|
+
const cat = finding.rule.category;
|
|
304
|
+
if (!byCategory[cat]) byCategory[cat] = [];
|
|
305
|
+
byCategory[cat].push(finding);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
totalFiles: limitedFiles.length,
|
|
310
|
+
filesWithFindings: fileResults.length,
|
|
311
|
+
totalFindings: allFindings.length,
|
|
312
|
+
byRisk,
|
|
313
|
+
byCategory,
|
|
314
|
+
fileResults,
|
|
315
|
+
findings: allFindings,
|
|
316
|
+
truncated: files.length > maxFiles,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
321
|
+
// TRANSFORMATION ENGINE
|
|
322
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Calculate the diff for a single file
|
|
326
|
+
*/
|
|
327
|
+
function calculateFileDiff(content, findings) {
|
|
328
|
+
// Sort findings by position (reverse order for safe replacement)
|
|
329
|
+
const sortedFindings = [...findings].sort((a, b) => b.startIndex - a.startIndex);
|
|
330
|
+
|
|
331
|
+
let newContent = content;
|
|
332
|
+
const changes = [];
|
|
333
|
+
|
|
334
|
+
for (const finding of sortedFindings) {
|
|
335
|
+
if (!finding.rule.autoFixable) continue;
|
|
336
|
+
if (finding.rule.fix === null || finding.rule.fix === undefined) continue;
|
|
337
|
+
|
|
338
|
+
const before = newContent.substring(
|
|
339
|
+
Math.max(0, finding.startIndex - 20),
|
|
340
|
+
finding.startIndex
|
|
341
|
+
);
|
|
342
|
+
const after = newContent.substring(
|
|
343
|
+
finding.endIndex,
|
|
344
|
+
Math.min(newContent.length, finding.endIndex + 20)
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
changes.push({
|
|
348
|
+
ruleId: finding.ruleId,
|
|
349
|
+
line: finding.line,
|
|
350
|
+
removed: finding.match,
|
|
351
|
+
added: finding.rule.fix,
|
|
352
|
+
context: { before, after },
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// Apply the fix
|
|
356
|
+
newContent =
|
|
357
|
+
newContent.substring(0, finding.startIndex) +
|
|
358
|
+
finding.rule.fix +
|
|
359
|
+
newContent.substring(finding.endIndex);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
originalContent: content,
|
|
364
|
+
newContent,
|
|
365
|
+
changes,
|
|
366
|
+
changed: newContent !== content,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Calculate diffs for all files
|
|
372
|
+
*/
|
|
373
|
+
async function calculateDiffs(projectPath, analysisResult, opts = {}) {
|
|
374
|
+
const { dryRun = true } = opts;
|
|
375
|
+
|
|
376
|
+
const diffs = [];
|
|
377
|
+
|
|
378
|
+
for (const fileResult of analysisResult.fileResults) {
|
|
379
|
+
try {
|
|
380
|
+
const content = await fs.promises.readFile(fileResult.file, "utf8");
|
|
381
|
+
const diff = calculateFileDiff(content, fileResult.findings);
|
|
382
|
+
|
|
383
|
+
if (diff.changed) {
|
|
384
|
+
diffs.push({
|
|
385
|
+
file: fileResult.file,
|
|
386
|
+
relativePath: fileResult.relativePath,
|
|
387
|
+
...diff,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
} catch (err) {
|
|
391
|
+
// Skip files that can't be processed
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
filesChanged: diffs.length,
|
|
397
|
+
totalChanges: diffs.reduce((sum, d) => sum + d.changes.length, 0),
|
|
398
|
+
diffs,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Apply transformations to files
|
|
404
|
+
*/
|
|
405
|
+
async function applyTransformations(projectPath, diffs, backupDir) {
|
|
406
|
+
const applied = [];
|
|
407
|
+
const failed = [];
|
|
408
|
+
|
|
409
|
+
// Create backup manifest
|
|
410
|
+
const backupManifest = {
|
|
411
|
+
timestamp: new Date().toISOString(),
|
|
412
|
+
projectPath,
|
|
413
|
+
files: [],
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
// Backup and transform each file
|
|
417
|
+
for (const diff of diffs) {
|
|
418
|
+
try {
|
|
419
|
+
// Backup original
|
|
420
|
+
const backupPath = path.join(backupDir, diff.relativePath);
|
|
421
|
+
await fs.promises.mkdir(path.dirname(backupPath), { recursive: true });
|
|
422
|
+
await fs.promises.writeFile(backupPath, diff.originalContent, "utf8");
|
|
423
|
+
|
|
424
|
+
backupManifest.files.push({
|
|
425
|
+
original: diff.file,
|
|
426
|
+
backup: backupPath,
|
|
427
|
+
relativePath: diff.relativePath,
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// Apply transformation
|
|
431
|
+
await fs.promises.writeFile(diff.file, diff.newContent, "utf8");
|
|
432
|
+
|
|
433
|
+
applied.push({
|
|
434
|
+
file: diff.file,
|
|
435
|
+
relativePath: diff.relativePath,
|
|
436
|
+
changes: diff.changes.length,
|
|
437
|
+
});
|
|
438
|
+
} catch (err) {
|
|
439
|
+
failed.push({
|
|
440
|
+
file: diff.file,
|
|
441
|
+
error: err.message,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Save backup manifest
|
|
447
|
+
await fs.promises.writeFile(
|
|
448
|
+
path.join(backupDir, "_manifest.json"),
|
|
449
|
+
JSON.stringify(backupManifest, null, 2),
|
|
450
|
+
"utf8"
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
return {
|
|
454
|
+
applied,
|
|
455
|
+
failed,
|
|
456
|
+
backupDir,
|
|
457
|
+
backupManifest,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Rollback transformations using backup
|
|
463
|
+
*/
|
|
464
|
+
async function rollbackTransformations(backupDir) {
|
|
465
|
+
const manifestPath = path.join(backupDir, "_manifest.json");
|
|
466
|
+
|
|
467
|
+
if (!fs.existsSync(manifestPath)) {
|
|
468
|
+
return { success: false, error: "Backup manifest not found" };
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const manifest = JSON.parse(await fs.promises.readFile(manifestPath, "utf8"));
|
|
472
|
+
const restored = [];
|
|
473
|
+
const failed = [];
|
|
474
|
+
|
|
475
|
+
for (const file of manifest.files) {
|
|
476
|
+
try {
|
|
477
|
+
const backupContent = await fs.promises.readFile(file.backup, "utf8");
|
|
478
|
+
await fs.promises.writeFile(file.original, backupContent, "utf8");
|
|
479
|
+
restored.push(file.original);
|
|
480
|
+
} catch (err) {
|
|
481
|
+
failed.push({ file: file.original, error: err.message });
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return {
|
|
486
|
+
success: failed.length === 0,
|
|
487
|
+
restored,
|
|
488
|
+
failed,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
493
|
+
// RISK ASSESSMENT
|
|
494
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Generate risk assessment for transformations
|
|
498
|
+
*/
|
|
499
|
+
function assessRisk(analysisResult, diffsResult) {
|
|
500
|
+
const risks = [];
|
|
501
|
+
|
|
502
|
+
// Count by risk level
|
|
503
|
+
const safeCount = analysisResult.byRisk.safe.length;
|
|
504
|
+
const moderateCount = analysisResult.byRisk.moderate.length;
|
|
505
|
+
const aggressiveCount = analysisResult.byRisk.aggressive.length;
|
|
506
|
+
|
|
507
|
+
// Safe transformations
|
|
508
|
+
if (safeCount > 0) {
|
|
509
|
+
risks.push({
|
|
510
|
+
level: "safe",
|
|
511
|
+
count: safeCount,
|
|
512
|
+
description: "Safe transformations (cannot change behavior)",
|
|
513
|
+
recommendation: "Apply automatically",
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Moderate transformations
|
|
518
|
+
if (moderateCount > 0) {
|
|
519
|
+
risks.push({
|
|
520
|
+
level: "moderate",
|
|
521
|
+
count: moderateCount,
|
|
522
|
+
description: "Moderate transformations (generally safe, worth reviewing)",
|
|
523
|
+
recommendation: "Review diff before applying",
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Aggressive transformations
|
|
528
|
+
if (aggressiveCount > 0) {
|
|
529
|
+
risks.push({
|
|
530
|
+
level: "aggressive",
|
|
531
|
+
count: aggressiveCount,
|
|
532
|
+
description: "Aggressive transformations (could change behavior)",
|
|
533
|
+
recommendation: "Manual review required, use --aggressive --yes-i-am-sure",
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Overall risk score (0-100)
|
|
538
|
+
const totalFindings = safeCount + moderateCount + aggressiveCount;
|
|
539
|
+
const riskScore = totalFindings > 0
|
|
540
|
+
? Math.round((moderateCount * 25 + aggressiveCount * 75) / totalFindings)
|
|
541
|
+
: 0;
|
|
542
|
+
|
|
543
|
+
return {
|
|
544
|
+
score: riskScore,
|
|
545
|
+
level: riskScore < 25 ? "low" : riskScore < 50 ? "moderate" : "high",
|
|
546
|
+
risks,
|
|
547
|
+
summary: {
|
|
548
|
+
safe: safeCount,
|
|
549
|
+
moderate: moderateCount,
|
|
550
|
+
aggressive: aggressiveCount,
|
|
551
|
+
total: totalFindings,
|
|
552
|
+
},
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
557
|
+
// EXPORTS
|
|
558
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
559
|
+
|
|
560
|
+
module.exports = {
|
|
561
|
+
findFiles,
|
|
562
|
+
analyzeFile,
|
|
563
|
+
analyzeProject,
|
|
564
|
+
calculateFileDiff,
|
|
565
|
+
calculateDiffs,
|
|
566
|
+
applyTransformations,
|
|
567
|
+
rollbackTransformations,
|
|
568
|
+
assessRisk,
|
|
569
|
+
SCANNABLE_EXTENSIONS,
|
|
570
|
+
SKIP_DIRS,
|
|
571
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibecheck polish --cleanup Module Index
|
|
3
|
+
*
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
* PRODUCTION-GRADE CLEANUP WITHOUT CHAOS
|
|
6
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
"use strict";
|
|
10
|
+
|
|
11
|
+
const rules = require("./rules");
|
|
12
|
+
const engine = require("./engine");
|
|
13
|
+
const output = require("./output");
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
// Rules
|
|
17
|
+
SAFE_RULES: rules.SAFE_RULES,
|
|
18
|
+
MODERATE_RULES: rules.MODERATE_RULES,
|
|
19
|
+
AGGRESSIVE_RULES: rules.AGGRESSIVE_RULES,
|
|
20
|
+
CATEGORIES: rules.CATEGORIES,
|
|
21
|
+
RISK_LEVELS: rules.RISK_LEVELS,
|
|
22
|
+
getAllRules: rules.getAllRules,
|
|
23
|
+
getSafeRules: rules.getSafeRules,
|
|
24
|
+
getModerateRules: rules.getModerateRules,
|
|
25
|
+
getAggressiveRules: rules.getAggressiveRules,
|
|
26
|
+
getRuleById: rules.getRuleById,
|
|
27
|
+
getRulesByCategory: rules.getRulesByCategory,
|
|
28
|
+
getRulesByRiskLevel: rules.getRulesByRiskLevel,
|
|
29
|
+
|
|
30
|
+
// Engine
|
|
31
|
+
findFiles: engine.findFiles,
|
|
32
|
+
analyzeFile: engine.analyzeFile,
|
|
33
|
+
analyzeProject: engine.analyzeProject,
|
|
34
|
+
calculateFileDiff: engine.calculateFileDiff,
|
|
35
|
+
calculateDiffs: engine.calculateDiffs,
|
|
36
|
+
applyTransformations: engine.applyTransformations,
|
|
37
|
+
rollbackTransformations: engine.rollbackTransformations,
|
|
38
|
+
assessRisk: engine.assessRisk,
|
|
39
|
+
|
|
40
|
+
// Output
|
|
41
|
+
printBanner: output.printBanner,
|
|
42
|
+
printAnalysisSummary: output.printAnalysisSummary,
|
|
43
|
+
printRiskAssessment: output.printRiskAssessment,
|
|
44
|
+
printFindingsByCategory: output.printFindingsByCategory,
|
|
45
|
+
printDiffPreview: output.printDiffPreview,
|
|
46
|
+
printApplyResults: output.printApplyResults,
|
|
47
|
+
printRollbackInfo: output.printRollbackInfo,
|
|
48
|
+
printAggressiveWarning: output.printAggressiveWarning,
|
|
49
|
+
printNextSteps: output.printNextSteps,
|
|
50
|
+
formatJsonOutput: output.formatJsonOutput,
|
|
51
|
+
colors: output.c,
|
|
52
|
+
icons: output.icons,
|
|
53
|
+
};
|