@vibecheckai/cli 3.0.2 → 3.0.3

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 (68) hide show
  1. package/package.json +9 -1
  2. package/bin/cli-hygiene.js +0 -241
  3. package/bin/guardrail.js +0 -834
  4. package/bin/runners/cli-utils.js +0 -1070
  5. package/bin/runners/context/ai-task-decomposer.js +0 -337
  6. package/bin/runners/context/analyzer.js +0 -462
  7. package/bin/runners/context/api-contracts.js +0 -427
  8. package/bin/runners/context/context-diff.js +0 -342
  9. package/bin/runners/context/context-pruner.js +0 -291
  10. package/bin/runners/context/dependency-graph.js +0 -414
  11. package/bin/runners/context/generators/claude.js +0 -107
  12. package/bin/runners/context/generators/codex.js +0 -108
  13. package/bin/runners/context/generators/copilot.js +0 -119
  14. package/bin/runners/context/generators/cursor.js +0 -514
  15. package/bin/runners/context/generators/mcp.js +0 -151
  16. package/bin/runners/context/generators/windsurf.js +0 -180
  17. package/bin/runners/context/git-context.js +0 -302
  18. package/bin/runners/context/index.js +0 -1042
  19. package/bin/runners/context/insights.js +0 -173
  20. package/bin/runners/context/mcp-server/generate-rules.js +0 -337
  21. package/bin/runners/context/mcp-server/index.js +0 -1176
  22. package/bin/runners/context/mcp-server/package.json +0 -24
  23. package/bin/runners/context/memory.js +0 -200
  24. package/bin/runners/context/monorepo.js +0 -215
  25. package/bin/runners/context/multi-repo-federation.js +0 -404
  26. package/bin/runners/context/patterns.js +0 -253
  27. package/bin/runners/context/proof-context.js +0 -972
  28. package/bin/runners/context/security-scanner.js +0 -303
  29. package/bin/runners/context/semantic-search.js +0 -350
  30. package/bin/runners/context/shared.js +0 -264
  31. package/bin/runners/context/team-conventions.js +0 -310
  32. package/bin/runners/lib/ai-bridge.js +0 -416
  33. package/bin/runners/lib/analysis-core.js +0 -271
  34. package/bin/runners/lib/analyzers.js +0 -541
  35. package/bin/runners/lib/audit-bridge.js +0 -391
  36. package/bin/runners/lib/auth-truth.js +0 -193
  37. package/bin/runners/lib/auth.js +0 -215
  38. package/bin/runners/lib/backup.js +0 -62
  39. package/bin/runners/lib/billing.js +0 -107
  40. package/bin/runners/lib/claims.js +0 -118
  41. package/bin/runners/lib/cli-ui.js +0 -540
  42. package/bin/runners/lib/compliance-bridge-new.js +0 -0
  43. package/bin/runners/lib/compliance-bridge.js +0 -165
  44. package/bin/runners/lib/contracts/auth-contract.js +0 -194
  45. package/bin/runners/lib/contracts/env-contract.js +0 -178
  46. package/bin/runners/lib/contracts/external-contract.js +0 -198
  47. package/bin/runners/lib/contracts/guard.js +0 -168
  48. package/bin/runners/lib/contracts/index.js +0 -89
  49. package/bin/runners/lib/contracts/plan-validator.js +0 -311
  50. package/bin/runners/lib/contracts/route-contract.js +0 -192
  51. package/bin/runners/lib/detect.js +0 -89
  52. package/bin/runners/lib/doctor/autofix.js +0 -254
  53. package/bin/runners/lib/doctor/index.js +0 -37
  54. package/bin/runners/lib/doctor/modules/dependencies.js +0 -325
  55. package/bin/runners/lib/doctor/modules/index.js +0 -46
  56. package/bin/runners/lib/doctor/modules/network.js +0 -250
  57. package/bin/runners/lib/doctor/modules/project.js +0 -312
  58. package/bin/runners/lib/doctor/modules/runtime.js +0 -224
  59. package/bin/runners/lib/doctor/modules/security.js +0 -348
  60. package/bin/runners/lib/doctor/modules/system.js +0 -213
  61. package/bin/runners/lib/doctor/modules/vibecheck.js +0 -394
  62. package/bin/runners/lib/doctor/reporter.js +0 -262
  63. package/bin/runners/lib/doctor/service.js +0 -262
  64. package/bin/runners/lib/doctor/types.js +0 -113
  65. package/bin/runners/lib/doctor/ui.js +0 -263
  66. package/bin/runners/lib/doctor-enhanced.js +0 -233
  67. package/bin/runners/lib/doctor-v2.js +0 -608
  68. package/bin/runners/lib/enforcement.js +0 -72
@@ -1,416 +0,0 @@
1
- const fs = require("fs");
2
- const path = require("path");
3
- const https = require("https");
4
-
5
- // Cache for package existence checks
6
- const packageCache = new Map();
7
-
8
- /**
9
- * Check if a package exists in the NPM registry
10
- */
11
- function checkNpmPackage(name) {
12
- return new Promise((resolve) => {
13
- if (packageCache.has(name)) return resolve(packageCache.get(name));
14
-
15
- // Handle scoped packages and regular packages
16
- const url = `https://registry.npmjs.org/${name}`;
17
-
18
- const req = https.request(url, { method: "HEAD" }, (res) => {
19
- const exists = res.statusCode === 200;
20
- packageCache.set(name, exists);
21
- resolve(exists);
22
- });
23
-
24
- req.on("error", () => resolve(false)); // Assume false on error or offline
25
- req.end();
26
- });
27
- }
28
-
29
- /**
30
- * Extract imports from file content
31
- */
32
- function extractImports(content) {
33
- const imports = new Set();
34
-
35
- // ESM imports
36
- const importMatches = content.matchAll(
37
- /import\s+(?:[\s\S]*?from\s+)?['"]([^'"]+)['"]/g,
38
- );
39
- for (const match of importMatches) {
40
- if (match[1] && !match[1].startsWith(".") && !match[1].startsWith("/")) {
41
- // Extract package name (handle scoped packages)
42
- const parts = match[1].split("/");
43
- const pkgName = match[1].startsWith("@")
44
- ? `${parts[0]}/${parts[1]}`
45
- : parts[0];
46
- imports.add(pkgName);
47
- }
48
- }
49
-
50
- // CommonJS requires
51
- const requireMatches = content.matchAll(
52
- /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
53
- );
54
- for (const match of requireMatches) {
55
- if (match[1] && !match[1].startsWith(".") && !match[1].startsWith("/")) {
56
- const parts = match[1].split("/");
57
- const pkgName = match[1].startsWith("@")
58
- ? `${parts[0]}/${parts[1]}`
59
- : parts[0];
60
- imports.add(pkgName);
61
- }
62
- }
63
-
64
- return Array.from(imports);
65
- }
66
-
67
- /**
68
- * Walk directory to find source files
69
- */
70
- function walkDir(dir, fileList = []) {
71
- if (!fs.existsSync(dir)) return fileList;
72
-
73
- const files = fs.readdirSync(dir);
74
- for (const file of files) {
75
- if (
76
- ["node_modules", ".git", "dist", "build", ".vibecheck", ".next"].includes(
77
- file,
78
- )
79
- )
80
- continue;
81
-
82
- const filePath = path.join(dir, file);
83
- try {
84
- const stat = fs.statSync(filePath);
85
- if (stat.isDirectory()) {
86
- walkDir(filePath, fileList);
87
- } else if (/\.(ts|js|tsx|jsx)$/.test(file) && !file.endsWith(".d.ts")) {
88
- fileList.push(filePath);
89
- }
90
- } catch (e) {}
91
- }
92
- return fileList;
93
- }
94
-
95
- async function runHallucinationCheck(projectPath) {
96
- const issues = [];
97
- let score = 100;
98
-
99
- try {
100
- // 1. Check package.json dependencies
101
- const pkgPath = path.join(projectPath, "package.json");
102
- if (fs.existsSync(pkgPath)) {
103
- const content = fs.readFileSync(pkgPath, "utf8");
104
- if (content.trim()) {
105
- try {
106
- const pkg = JSON.parse(content);
107
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
108
-
109
- // Limit checks to avoid rate limiting
110
- const depNames = Object.keys(deps).slice(0, 20);
111
-
112
- for (const dep of depNames) {
113
- // Skip internal/scoped packages for now if they look private
114
- if (dep.startsWith("@vibecheck") || dep.startsWith("@internal"))
115
- continue;
116
-
117
- const exists = await checkNpmPackage(dep);
118
- if (!exists) {
119
- issues.push({
120
- severity: "critical",
121
- type: "Hallucination",
122
- message: `Dependency '${dep}' listed in package.json does not exist in public npm registry`,
123
- file: "package.json",
124
- });
125
- }
126
- }
127
- } catch (e) {
128
- console.warn("Failed to parse package.json:", e.message);
129
- }
130
- }
131
- }
132
-
133
- // 2. Scan source files for imports (if scanning a full project)
134
- // For specific file validation in runValidate, we might want to check that specific file's imports too.
135
- // However, runHallucinationCheck here is designed for project-level scanning.
136
- } catch (err) {
137
- console.error("AI Bridge Error:", err.message);
138
- }
139
-
140
- // Deduct score
141
- if (issues.length > 0) {
142
- score = Math.max(0, 100 - issues.length * 20);
143
- }
144
-
145
- return {
146
- score,
147
- issues,
148
- };
149
- }
150
-
151
- // --- Intent Matcher Logic (Ported from packages/ai-vibechecks) ---
152
-
153
- function extractCodeIntent(code) {
154
- const entities = [];
155
- const operations = [];
156
-
157
- // Extract function/class names
158
- const functionMatches = code.matchAll(
159
- /(?:function|const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g,
160
- );
161
- for (const match of functionMatches) {
162
- if (match[1]) entities.push(match[1]);
163
- }
164
-
165
- const classMatches = code.matchAll(/class\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g);
166
- for (const match of classMatches) {
167
- if (match[1]) entities.push(match[1]);
168
- }
169
-
170
- // Detect operations
171
- if (code.includes("fetch") || code.includes("axios") || code.includes("http"))
172
- operations.push("API call");
173
- if (
174
- code.includes("fs.") ||
175
- code.includes("writeFile") ||
176
- code.includes("readFile")
177
- )
178
- operations.push("File I/O");
179
- if (
180
- code.includes("database") ||
181
- code.includes("prisma") ||
182
- code.includes("mongoose")
183
- )
184
- operations.push("Database operation");
185
- if (
186
- code.includes("map") ||
187
- code.includes("filter") ||
188
- code.includes("reduce")
189
- )
190
- operations.push("Data transformation");
191
- if (code.includes("useState") || code.includes("useEffect"))
192
- operations.push("React hooks");
193
-
194
- return { entities, operations };
195
- }
196
-
197
- function parseRequestIntent(request) {
198
- const lowerRequest = request.toLowerCase();
199
-
200
- // Extract goal (simplified)
201
- let goal = "Unknown";
202
- if (lowerRequest.includes("create") || lowerRequest.includes("build"))
203
- goal = "Create new functionality";
204
- else if (lowerRequest.includes("fix") || lowerRequest.includes("debug"))
205
- goal = "Fix issue";
206
-
207
- // Extract constraints
208
- const constraints = [];
209
- if (lowerRequest.includes("without")) {
210
- const match = lowerRequest.match(/without\s+([^.,;]+)/);
211
- if (match) constraints.push(`Avoid: ${match[1].trim()}`);
212
- }
213
- if (lowerRequest.includes("using")) {
214
- const match = lowerRequest.match(/using\s+([^.,;]+)/);
215
- if (match) constraints.push(`Use: ${match[1].trim()}`);
216
- }
217
-
218
- // Extract expected entities
219
- const expectedEntities = [];
220
- const commonFrameworks = [
221
- "react",
222
- "vue",
223
- "angular",
224
- "express",
225
- "fastify",
226
- "next",
227
- "prisma",
228
- "postgres",
229
- "mongo",
230
- ];
231
- for (const fw of commonFrameworks) {
232
- if (lowerRequest.includes(fw)) expectedEntities.push(fw);
233
- }
234
-
235
- // Extract expected operations
236
- const expectedOperations = [];
237
- if (lowerRequest.includes("api") || lowerRequest.includes("fetch"))
238
- expectedOperations.push("API call");
239
- if (lowerRequest.includes("database") || lowerRequest.includes("store"))
240
- expectedOperations.push("Database operation");
241
- if (lowerRequest.includes("file")) expectedOperations.push("File I/O");
242
-
243
- return { goal, constraints, expectedEntities, expectedOperations };
244
- }
245
-
246
- function compareIntents(requested, actual) {
247
- const matches = [];
248
- const mismatches = [];
249
- let score = 0;
250
-
251
- // Check expected entities
252
- for (const expected of requested.expectedEntities) {
253
- const found =
254
- actual.entities.some((e) =>
255
- e.toLowerCase().includes(expected.toLowerCase()),
256
- ) ||
257
- // Also check imports for entities (e.g. react)
258
- actual.entities.some((e) =>
259
- e.toLowerCase().includes(expected.toLowerCase()),
260
- );
261
-
262
- if (found) {
263
- matches.push(`Expected entity '${expected}' found`);
264
- score += 20;
265
- } else {
266
- mismatches.push(`Expected entity '${expected}' not found in structure`);
267
- }
268
- }
269
-
270
- // Check expected operations
271
- for (const expectedOp of requested.expectedOperations) {
272
- const found = actual.operations.some((o) =>
273
- o.toLowerCase().includes(expectedOp.toLowerCase()),
274
- );
275
- if (found) {
276
- matches.push(`Expected operation '${expectedOp}' found`);
277
- score += 20;
278
- } else {
279
- mismatches.push(`Expected operation '${expectedOp}' not found`);
280
- }
281
- }
282
-
283
- // Check constraints
284
- for (const constraint of requested.constraints) {
285
- if (constraint.startsWith("Avoid:")) {
286
- const toAvoid = constraint.replace("Avoid:", "").trim().toLowerCase();
287
- const found = actual.entities.some((e) =>
288
- e.toLowerCase().includes(toAvoid),
289
- );
290
- if (!found) {
291
- matches.push(`Successfully avoided '${toAvoid}'`);
292
- score += 10;
293
- } else {
294
- mismatches.push(`Constraint violated: used '${toAvoid}'`);
295
- score -= 20;
296
- }
297
- }
298
- if (constraint.startsWith("Use:")) {
299
- const toUse = constraint.replace("Use:", "").trim().toLowerCase();
300
- const found = actual.entities.some((e) =>
301
- e.toLowerCase().includes(toUse),
302
- );
303
- if (found) {
304
- matches.push(`Required technology '${toUse}' used`);
305
- score += 15;
306
- } else {
307
- mismatches.push(`Required technology '${toUse}' not used`);
308
- score -= 15;
309
- }
310
- }
311
- }
312
-
313
- // Baseline score if nothing specific requested but code looks structured
314
- if (
315
- requested.expectedEntities.length === 0 &&
316
- requested.expectedOperations.length === 0 &&
317
- requested.constraints.length === 0
318
- ) {
319
- // If request is generic, and we found SOME entities, give it a pass
320
- if (actual.entities.length > 0) score = 100;
321
- else score = 50;
322
- }
323
-
324
- return {
325
- alignmentScore: Math.max(0, Math.min(100, score)),
326
- matches,
327
- mismatches,
328
- };
329
- }
330
-
331
- /**
332
- * Validate code against an intent
333
- */
334
- function validateIntent(code, intent) {
335
- const issues = [];
336
-
337
- if (!intent) return { score: 100, issues: [] };
338
-
339
- const codeIntent = extractCodeIntent(code);
340
- const requestIntent = parseRequestIntent(intent);
341
-
342
- // Supplement codeIntent entities with imports for better matching (Bridge enhancement)
343
- const imports = extractImports(code);
344
- codeIntent.entities.push(...imports);
345
-
346
- const comparison = compareIntents(requestIntent, codeIntent);
347
-
348
- if (comparison.alignmentScore < 60) {
349
- issues.push({
350
- severity: "medium",
351
- type: "Intent Mismatch",
352
- message: `Code alignment score: ${comparison.alignmentScore}/100. ${comparison.mismatches.join(", ")}`,
353
- file: "generated-code",
354
- });
355
- }
356
-
357
- return {
358
- score: comparison.alignmentScore,
359
- issues,
360
- };
361
- }
362
-
363
- /**
364
- * Static Analysis/Quality Check
365
- */
366
- function validateQuality(code) {
367
- const issues = [];
368
- let score = 100;
369
-
370
- // Check for hardcoded secrets
371
- if (
372
- /['"][a-zA-Z0-9]{20,}['"]/.test(code) &&
373
- (code.includes("key") || code.includes("token") || code.includes("secret"))
374
- ) {
375
- issues.push({
376
- severity: "high",
377
- type: "Security",
378
- message: "Potential hardcoded secret detected",
379
- file: "generated-code",
380
- });
381
- }
382
-
383
- // Check for syntax errors (basic)
384
- const openBraces = (code.match(/{/g) || []).length;
385
- const closeBraces = (code.match(/}/g) || []).length;
386
- if (openBraces !== closeBraces) {
387
- issues.push({
388
- severity: "critical",
389
- type: "Syntax",
390
- message: "Unbalanced braces detected",
391
- file: "generated-code",
392
- });
393
- }
394
-
395
- // Check for console.log
396
- if (code.includes("console.log")) {
397
- issues.push({
398
- severity: "low",
399
- type: "Quality",
400
- message: "Console.log statement detected",
401
- file: "generated-code",
402
- });
403
- score -= 5;
404
- }
405
-
406
- return {
407
- score: Math.max(0, Math.min(100, score - issues.length * 10)),
408
- issues,
409
- };
410
- }
411
-
412
- module.exports = {
413
- runHallucinationCheck,
414
- validateIntent,
415
- validateQuality,
416
- };
@@ -1,271 +0,0 @@
1
- /**
2
- * Analysis Core - Shared analysis engine for Ship and Scan
3
- *
4
- * Consolidates common logic:
5
- * - Truthpack generation
6
- * - Finding collection
7
- * - Verdict calculation
8
- * - Output formatting
9
- *
10
- * Ship = Fast path (core analyzers only)
11
- * Scan = Deep path (core + extended analyzers + optional layers)
12
- */
13
-
14
- const path = require("path");
15
- const fs = require("fs");
16
- const { buildTruthpack, writeTruthpack, detectFastifyEntry } = require("./truth");
17
- const {
18
- findMissingRoutes,
19
- findEnvGaps,
20
- findFakeSuccess,
21
- findGhostAuth,
22
- findStripeWebhookViolations,
23
- findPaidSurfaceNotEnforced,
24
- findOwnerModeBypass
25
- } = require("./analyzers");
26
- const { findingsFromReality } = require("./reality-findings");
27
-
28
- // ============================================================================
29
- // CORE ANALYSIS (Used by both Ship and Scan)
30
- // ============================================================================
31
-
32
- /**
33
- * Run core analyzers that apply to all projects
34
- * @param {string} root - Project root path
35
- * @param {object} truthpack - Generated truthpack
36
- * @returns {Array} - Array of findings
37
- */
38
- function runCoreAnalyzers(root, truthpack) {
39
- return [
40
- ...findMissingRoutes(truthpack),
41
- ...findEnvGaps(truthpack),
42
- ...findFakeSuccess(root),
43
- ...findGhostAuth(truthpack, root),
44
- ...findStripeWebhookViolations(truthpack),
45
- ...findPaidSurfaceNotEnforced(truthpack),
46
- ...findOwnerModeBypass(root),
47
- ...findingsFromReality(root)
48
- ];
49
- }
50
-
51
- /**
52
- * Calculate verdict from findings
53
- * @param {Array} findings - Array of findings
54
- * @returns {string} - SHIP | WARN | BLOCK
55
- */
56
- function calculateVerdict(findings) {
57
- if (findings.some(f => f.severity === "BLOCK")) return "BLOCK";
58
- if (findings.some(f => f.severity === "WARN")) return "WARN";
59
- return "SHIP";
60
- }
61
-
62
- /**
63
- * Build proof graph from findings
64
- * @param {Array} findings - Array of findings
65
- * @param {object} truthpack - Truthpack data
66
- * @param {string} root - Project root path
67
- * @returns {object} - Proof graph
68
- */
69
- function buildProofGraph(findings, truthpack, root) {
70
- const claims = [];
71
- let claimId = 0;
72
-
73
- for (const finding of findings) {
74
- const claim = {
75
- id: `claim-${++claimId}`,
76
- type: getClaimType(finding.category),
77
- assertion: finding.title || finding.message,
78
- verified: finding.severity !== 'BLOCK',
79
- confidence: finding.confidence === 'high' ? 0.9 : finding.confidence === 'medium' ? 0.7 : 0.5,
80
- evidence: (finding.evidence || []).map((e, i) => ({
81
- id: `evidence-${claimId}-${i}`,
82
- type: 'file_citation',
83
- file: e.file,
84
- line: parseInt(e.lines?.split('-')[0]) || e.line || 1,
85
- snippet: e.snippetHash || '',
86
- strength: 0.8,
87
- verifiedAt: new Date().toISOString(),
88
- method: 'static'
89
- })),
90
- gaps: [{
91
- id: `gap-${claimId}`,
92
- type: getGapType(finding.category),
93
- description: finding.why || finding.title,
94
- severity: finding.severity === 'BLOCK' ? 'critical' : finding.severity === 'WARN' ? 'medium' : 'low',
95
- suggestion: (finding.fixHints || [])[0] || ''
96
- }],
97
- severity: finding.severity === 'BLOCK' ? 'critical' : finding.severity === 'WARN' ? 'medium' : 'low',
98
- file: finding.evidence?.[0]?.file || '',
99
- line: parseInt(finding.evidence?.[0]?.lines?.split('-')[0]) || 1
100
- };
101
- claims.push(claim);
102
- }
103
-
104
- const verifiedClaims = claims.filter(c => c.verified);
105
- const failedClaims = claims.filter(c => !c.verified);
106
- const allGaps = claims.flatMap(c => c.gaps);
107
-
108
- const riskScore = Math.min(100, failedClaims.reduce((sum, c) => {
109
- if (c.severity === 'critical') return sum + 30;
110
- if (c.severity === 'high') return sum + 20;
111
- if (c.severity === 'medium') return sum + 10;
112
- return sum + 5;
113
- }, 0));
114
-
115
- const confidence = claims.length > 0
116
- ? claims.reduce((sum, c) => sum + c.confidence, 0) / claims.length
117
- : 1.0;
118
-
119
- return {
120
- version: '1.0.0',
121
- generatedAt: new Date().toISOString(),
122
- projectPath: root,
123
- claims,
124
- summary: {
125
- totalClaims: claims.length,
126
- verifiedClaims: verifiedClaims.length,
127
- failedClaims: failedClaims.length,
128
- gaps: allGaps.length,
129
- riskScore,
130
- confidence
131
- },
132
- verdict: calculateVerdict(findings),
133
- topBlockers: failedClaims.filter(c => c.severity === 'critical' || c.severity === 'high').slice(0, 5),
134
- topGaps: allGaps.filter(g => g.severity === 'critical' || g.severity === 'high').slice(0, 5)
135
- };
136
- }
137
-
138
- function getClaimType(category) {
139
- const map = {
140
- 'MissingRoute': 'route_exists',
141
- 'EnvContract': 'env_declared',
142
- 'FakeSuccess': 'success_verified',
143
- 'GhostAuth': 'auth_protected',
144
- 'StripeWebhook': 'billing_enforced',
145
- 'PaidSurface': 'billing_enforced',
146
- 'OwnerModeBypass': 'billing_enforced',
147
- 'DeadUI': 'ui_wired'
148
- };
149
- return map[category] || 'ui_wired';
150
- }
151
-
152
- function getGapType(category) {
153
- const map = {
154
- 'MissingRoute': 'missing_handler',
155
- 'EnvContract': 'missing_verification',
156
- 'FakeSuccess': 'missing_verification',
157
- 'GhostAuth': 'missing_gate',
158
- 'StripeWebhook': 'missing_verification',
159
- 'PaidSurface': 'missing_gate',
160
- 'OwnerModeBypass': 'missing_gate',
161
- 'DeadUI': 'missing_handler'
162
- };
163
- return map[category] || 'untested_path';
164
- }
165
-
166
- // ============================================================================
167
- // UNIFIED ANALYSIS RUNNER
168
- // ============================================================================
169
-
170
- /**
171
- * Run unified analysis (used by both ship and scan)
172
- * @param {object} options - Analysis options
173
- * @returns {object} - Analysis result with findings, verdict, proofGraph
174
- */
175
- async function runAnalysis({
176
- repoRoot = process.cwd(),
177
- fastifyEntry = null,
178
- extended = false, // If true, run extended analyzers (scan mode)
179
- noWrite = false
180
- } = {}) {
181
- const root = repoRoot;
182
- const fastEntry = fastifyEntry || detectFastifyEntry(root);
183
-
184
- // Build truthpack
185
- const truthpack = await buildTruthpack({ repoRoot: root, fastifyEntry: fastEntry });
186
- if (!noWrite) {
187
- writeTruthpack(root, truthpack);
188
- }
189
-
190
- // Run core analyzers
191
- const findings = runCoreAnalyzers(root, truthpack);
192
-
193
- // Calculate verdict
194
- const verdict = calculateVerdict(findings);
195
-
196
- // Build proof graph
197
- const proofGraph = buildProofGraph(findings, truthpack, root);
198
-
199
- // Build report
200
- const report = {
201
- meta: {
202
- generatedAt: new Date().toISOString(),
203
- verdict,
204
- mode: extended ? 'scan' : 'ship'
205
- },
206
- truthpackHash: truthpack.index?.hashes?.truthpackHash,
207
- findings,
208
- proofGraph: {
209
- summary: proofGraph.summary,
210
- topBlockers: proofGraph.topBlockers.slice(0, 5),
211
- topGaps: proofGraph.topGaps.slice(0, 5)
212
- }
213
- };
214
-
215
- // Write outputs
216
- if (!noWrite) {
217
- const outDir = path.join(root, ".vibecheck");
218
- fs.mkdirSync(outDir, { recursive: true });
219
- fs.writeFileSync(path.join(outDir, "last_ship.json"), JSON.stringify(report, null, 2));
220
- fs.writeFileSync(path.join(outDir, "proof-graph.json"), JSON.stringify(proofGraph, null, 2));
221
- }
222
-
223
- return { report, truthpack, verdict, proofGraph, findings };
224
- }
225
-
226
- // ============================================================================
227
- // FINDING UTILITIES
228
- // ============================================================================
229
-
230
- /**
231
- * Group findings by category
232
- */
233
- function groupFindings(findings) {
234
- const groups = {};
235
- for (const f of findings) {
236
- const cat = f.category || 'Other';
237
- if (!groups[cat]) groups[cat] = [];
238
- groups[cat].push(f);
239
- }
240
- return groups;
241
- }
242
-
243
- /**
244
- * Count findings by severity
245
- */
246
- function countBySeverity(findings) {
247
- return {
248
- block: findings.filter(f => f.severity === 'BLOCK').length,
249
- warn: findings.filter(f => f.severity === 'WARN').length,
250
- info: findings.filter(f => f.severity === 'INFO').length
251
- };
252
- }
253
-
254
- /**
255
- * Get top N blockers
256
- */
257
- function getTopBlockers(findings, n = 5) {
258
- return findings
259
- .filter(f => f.severity === 'BLOCK')
260
- .slice(0, n);
261
- }
262
-
263
- module.exports = {
264
- runCoreAnalyzers,
265
- calculateVerdict,
266
- buildProofGraph,
267
- runAnalysis,
268
- groupFindings,
269
- countBySeverity,
270
- getTopBlockers
271
- };