@vibecheckai/cli 3.4.0 → 3.5.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 (166) hide show
  1. package/bin/registry.js +243 -152
  2. package/bin/runners/cli-utils.js +2 -33
  3. package/bin/runners/context/generators/cursor.js +49 -2
  4. package/bin/runners/lib/agent-firewall/learning/learning-engine.js +849 -0
  5. package/bin/runners/lib/analyzers.js +544 -19
  6. package/bin/runners/lib/audit-logger.js +532 -0
  7. package/bin/runners/lib/authority/authorities/architecture.js +364 -0
  8. package/bin/runners/lib/authority/authorities/compliance.js +341 -0
  9. package/bin/runners/lib/authority/authorities/human.js +343 -0
  10. package/bin/runners/lib/authority/authorities/quality.js +420 -0
  11. package/bin/runners/lib/authority/authorities/security.js +228 -0
  12. package/bin/runners/lib/authority/index.js +293 -0
  13. package/bin/runners/lib/authority-badge.js +425 -425
  14. package/bin/runners/lib/bundle/bundle-intelligence.js +846 -0
  15. package/bin/runners/lib/cli-charts.js +368 -0
  16. package/bin/runners/lib/cli-config-display.js +405 -0
  17. package/bin/runners/lib/cli-demo.js +275 -0
  18. package/bin/runners/lib/cli-errors.js +438 -0
  19. package/bin/runners/lib/cli-help-formatter.js +439 -0
  20. package/bin/runners/lib/cli-interactive-menu.js +509 -0
  21. package/bin/runners/lib/cli-prompts.js +441 -0
  22. package/bin/runners/lib/cli-scan-cards.js +362 -0
  23. package/bin/runners/lib/compliance-reporter.js +710 -0
  24. package/bin/runners/lib/conductor/index.js +671 -0
  25. package/bin/runners/lib/easy/README.md +123 -0
  26. package/bin/runners/lib/easy/index.js +140 -0
  27. package/bin/runners/lib/easy/interactive-wizard.js +788 -0
  28. package/bin/runners/lib/easy/one-click-firewall.js +564 -0
  29. package/bin/runners/lib/easy/zero-config-reality.js +714 -0
  30. package/bin/runners/lib/engines/accessibility-engine.js +218 -18
  31. package/bin/runners/lib/engines/api-consistency-engine.js +335 -30
  32. package/bin/runners/lib/engines/async-patterns-engine.js +444 -0
  33. package/bin/runners/lib/engines/bundle-size-engine.js +433 -0
  34. package/bin/runners/lib/engines/confidence-scoring.js +276 -0
  35. package/bin/runners/lib/engines/context-detection.js +264 -0
  36. package/bin/runners/lib/engines/cross-file-analysis-engine.js +292 -27
  37. package/bin/runners/lib/engines/database-patterns-engine.js +429 -0
  38. package/bin/runners/lib/engines/duplicate-code-engine.js +354 -0
  39. package/bin/runners/lib/engines/empty-catch-engine.js +127 -17
  40. package/bin/runners/lib/engines/env-variables-engine.js +458 -0
  41. package/bin/runners/lib/engines/error-handling-engine.js +437 -0
  42. package/bin/runners/lib/engines/false-positive-prevention.js +630 -0
  43. package/bin/runners/lib/engines/framework-adapters/index.js +607 -0
  44. package/bin/runners/lib/engines/framework-detection.js +508 -0
  45. package/bin/runners/lib/engines/import-order-engine.js +429 -0
  46. package/bin/runners/lib/engines/mock-data-engine.js +53 -10
  47. package/bin/runners/lib/engines/naming-conventions-engine.js +544 -0
  48. package/bin/runners/lib/engines/noise-reduction-engine.js +452 -0
  49. package/bin/runners/lib/engines/orchestrator.js +334 -0
  50. package/bin/runners/lib/engines/performance-issues-engine.js +176 -36
  51. package/bin/runners/lib/engines/react-patterns-engine.js +457 -0
  52. package/bin/runners/lib/engines/security-vulnerabilities-engine.js +382 -54
  53. package/bin/runners/lib/engines/type-aware-engine.js +263 -39
  54. package/bin/runners/lib/engines/vibecheck-engines/index.js +122 -13
  55. package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +806 -0
  56. package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +373 -73
  57. package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +577 -0
  58. package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +543 -0
  59. package/bin/runners/lib/engines/vibecheck-engines.js +514 -0
  60. package/bin/runners/lib/enhanced-features/index.js +305 -0
  61. package/bin/runners/lib/enhanced-output.js +631 -0
  62. package/bin/runners/lib/enterprise.js +300 -0
  63. package/bin/runners/lib/entitlements-v2.js +103 -11
  64. package/bin/runners/lib/firewall/command-validator.js +351 -0
  65. package/bin/runners/lib/firewall/config.js +341 -0
  66. package/bin/runners/lib/firewall/content-validator.js +519 -0
  67. package/bin/runners/lib/firewall/index.js +101 -0
  68. package/bin/runners/lib/firewall/path-validator.js +256 -0
  69. package/bin/runners/lib/html-proof-report.js +350 -700
  70. package/bin/runners/lib/intelligence/cross-repo-intelligence.js +817 -0
  71. package/bin/runners/lib/mcp-utils.js +425 -0
  72. package/bin/runners/lib/missions/plan.js +46 -6
  73. package/bin/runners/lib/missions/templates.js +232 -0
  74. package/bin/runners/lib/output/index.js +1022 -0
  75. package/bin/runners/lib/policy-engine.js +652 -0
  76. package/bin/runners/lib/polish/autofix/accessibility-fixes.js +333 -0
  77. package/bin/runners/lib/polish/autofix/async-handlers.js +273 -0
  78. package/bin/runners/lib/polish/autofix/dead-code.js +280 -0
  79. package/bin/runners/lib/polish/autofix/imports-optimizer.js +344 -0
  80. package/bin/runners/lib/polish/autofix/index.js +200 -0
  81. package/bin/runners/lib/polish/autofix/remove-consoles.js +209 -0
  82. package/bin/runners/lib/polish/autofix/strengthen-types.js +245 -0
  83. package/bin/runners/lib/polish/backend-checks.js +148 -0
  84. package/bin/runners/lib/polish/documentation-checks.js +111 -0
  85. package/bin/runners/lib/polish/frontend-checks.js +168 -0
  86. package/bin/runners/lib/polish/index.js +71 -0
  87. package/bin/runners/lib/polish/infrastructure-checks.js +131 -0
  88. package/bin/runners/lib/polish/library-detection.js +175 -0
  89. package/bin/runners/lib/polish/performance-checks.js +100 -0
  90. package/bin/runners/lib/polish/security-checks.js +148 -0
  91. package/bin/runners/lib/polish/utils.js +203 -0
  92. package/bin/runners/lib/prompt-builder.js +540 -0
  93. package/bin/runners/lib/proof-certificate.js +634 -0
  94. package/bin/runners/lib/reality/accessibility-audit.js +946 -0
  95. package/bin/runners/lib/reality/api-contract-validator.js +1012 -0
  96. package/bin/runners/lib/reality/chaos-engineering.js +1084 -0
  97. package/bin/runners/lib/reality/performance-tracker.js +1077 -0
  98. package/bin/runners/lib/reality/scenario-generator.js +1404 -0
  99. package/bin/runners/lib/reality/visual-regression.js +852 -0
  100. package/bin/runners/lib/reality-profiler.js +717 -0
  101. package/bin/runners/lib/replay/flight-recorder-viewer.js +1160 -0
  102. package/bin/runners/lib/review/ai-code-review.js +832 -0
  103. package/bin/runners/lib/rules/custom-rule-engine.js +985 -0
  104. package/bin/runners/lib/sbom-generator.js +641 -0
  105. package/bin/runners/lib/scan-output-enhanced.js +512 -0
  106. package/bin/runners/lib/scan-output.js +47 -0
  107. package/bin/runners/lib/security/owasp-scanner.js +939 -0
  108. package/bin/runners/lib/terminal-ui.js +113 -1
  109. package/bin/runners/lib/unified-cli-output.js +603 -430
  110. package/bin/runners/lib/validators/contract-validator.js +283 -0
  111. package/bin/runners/lib/validators/dead-export-detector.js +279 -0
  112. package/bin/runners/lib/validators/dep-audit.js +245 -0
  113. package/bin/runners/lib/validators/env-validator.js +319 -0
  114. package/bin/runners/lib/validators/index.js +120 -0
  115. package/bin/runners/lib/validators/license-checker.js +252 -0
  116. package/bin/runners/lib/validators/route-validator.js +290 -0
  117. package/bin/runners/runAIAgent.js +5 -10
  118. package/bin/runners/runAgent.js +3 -0
  119. package/bin/runners/runApprove.js +1233 -1200
  120. package/bin/runners/runAuth.js +22 -1
  121. package/bin/runners/runAuthority.js +528 -0
  122. package/bin/runners/runCheckpoint.js +4 -24
  123. package/bin/runners/runClassify.js +862 -859
  124. package/bin/runners/runConductor.js +772 -0
  125. package/bin/runners/runContainer.js +366 -0
  126. package/bin/runners/runContext.js +3 -0
  127. package/bin/runners/runDoctor.js +28 -41
  128. package/bin/runners/runEasy.js +410 -0
  129. package/bin/runners/runFirewall.js +3 -0
  130. package/bin/runners/runFirewallHook.js +3 -0
  131. package/bin/runners/runFix.js +76 -66
  132. package/bin/runners/runGuard.js +411 -18
  133. package/bin/runners/runIaC.js +372 -0
  134. package/bin/runners/runInit.js +10 -60
  135. package/bin/runners/runMcp.js +11 -12
  136. package/bin/runners/runPolish.js +240 -64
  137. package/bin/runners/runPromptFirewall.js +5 -12
  138. package/bin/runners/runProve.js +20 -55
  139. package/bin/runners/runReality.js +68 -59
  140. package/bin/runners/runReport.js +31 -5
  141. package/bin/runners/runRuntime.js +5 -8
  142. package/bin/runners/runScan.js +194 -1286
  143. package/bin/runners/runShip.js +695 -47
  144. package/bin/runners/runTruth.js +3 -0
  145. package/bin/runners/runValidate.js +7 -11
  146. package/bin/runners/runVibe.js +791 -0
  147. package/bin/runners/runWatch.js +14 -23
  148. package/bin/vibecheck.js +175 -56
  149. package/mcp-server/index.js +190 -14
  150. package/mcp-server/package.json +1 -1
  151. package/mcp-server/tools-v3.js +397 -64
  152. package/mcp-server/tools.js +495 -0
  153. package/package.json +1 -1
  154. package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +0 -164
  155. package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +0 -291
  156. package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +0 -83
  157. package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +0 -198
  158. package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +0 -275
  159. package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +0 -167
  160. package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +0 -217
  161. package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +0 -140
  162. package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +0 -164
  163. package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +0 -234
  164. package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +0 -217
  165. package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +0 -78
  166. package/mcp-server/index-v1.js +0 -698
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Context-Aware Detection Utilities
3
+ *
4
+ * Provides smarter detection logic to reduce false positives in vibecheck scans.
5
+ * Handles cases like:
6
+ * - Tailwind CSS modifiers (placeholder:text-white/40)
7
+ * - UI button labels (Mock/Live toggles)
8
+ * - Valid example data (Todo App in project lists)
9
+ */
10
+
11
+ /**
12
+ * Patterns that indicate Tailwind CSS class context
13
+ * These are styling directives, not placeholder content
14
+ */
15
+ const TAILWIND_MODIFIER_PATTERNS = [
16
+ // Tailwind placeholder: modifier (styles placeholder text in inputs)
17
+ /\bplaceholder:/,
18
+ // Other Tailwind state modifiers that might contain keywords
19
+ /\b(?:hover|focus|active|disabled|checked|invalid|required|empty|default):/,
20
+ // Tailwind arbitrary value syntax
21
+ /\[placeholder[:\]]/,
22
+ ];
23
+
24
+ /**
25
+ * Patterns that indicate className or CSS context
26
+ */
27
+ const CSS_CLASS_PATTERNS = [
28
+ // className="..." or className={...}
29
+ /className\s*[=:]\s*[{"`']/,
30
+ // class="..." (HTML)
31
+ /\bclass\s*=\s*["']/,
32
+ // Tailwind @apply directive
33
+ /@apply\s+/,
34
+ // CSS-in-JS style objects
35
+ /\bstyle\s*[=:]\s*\{/,
36
+ // clsx, classnames, cn utilities
37
+ /\b(?:clsx|classnames|cn)\s*\(/,
38
+ // twMerge, tailwind-merge
39
+ /\b(?:twMerge|tailwind-merge)\s*\(/,
40
+ ];
41
+
42
+ /**
43
+ * Patterns for UI labels/buttons that legitimately use "mock", "todo", etc.
44
+ */
45
+ const LEGITIMATE_UI_LABEL_PATTERNS = [
46
+ // Button onClick handlers with mode labels
47
+ /onClick\s*=.*['"](?:mock|live|demo|test)["']/i,
48
+ // Mode change handlers
49
+ /onModeChange\s*\?\.\s*\(['"](?:mock|live|demo)["']\)/i,
50
+ // Radio button or toggle values
51
+ /value\s*[=:]\s*['"](?:mock|live|demo|test)["']/i,
52
+ // UI state labels
53
+ /mode\s*===?\s*['"](?:mock|live|demo|test)["']/i,
54
+ // Tab or button labels in arrays/maps
55
+ /\[\s*['"](?:mock|live)["']\s*,/i,
56
+ // Option/select values
57
+ /option\s*value\s*=\s*['"](?:mock|live|demo)["']/i,
58
+ // Button children with mode text
59
+ />\s*(?:Mock|Live|Demo|Test)\s*</i,
60
+ ];
61
+
62
+ /**
63
+ * Patterns for legitimate example/template data
64
+ */
65
+ const LEGITIMATE_EXAMPLE_DATA_PATTERNS = [
66
+ // Array of project/template names (common in UI)
67
+ /\[\s*['"](?:Todo\s*App|Landing\s*Page|Dashboard|Blog|Portfolio|E-?commerce)["']/i,
68
+ // Project name selectors or dropdowns
69
+ /projectName|templateName|appName|recentProjects/i,
70
+ // Example/sample data arrays in UI components
71
+ /examples?\s*[=:]\s*\[/i,
72
+ /templates?\s*[=:]\s*\[/i,
73
+ /presets?\s*[=:]\s*\[/i,
74
+ // Map over example items
75
+ /\.map\(\s*\(\s*(?:name|item|template)\s*\)/i,
76
+ ];
77
+
78
+ /**
79
+ * Check if a line contains Tailwind CSS placeholder modifier
80
+ */
81
+ function isTailwindPlaceholderModifier(line) {
82
+ return TAILWIND_MODIFIER_PATTERNS.some(pattern => pattern.test(line));
83
+ }
84
+
85
+ /**
86
+ * Check if the context is a CSS class definition
87
+ */
88
+ function isCSSClassContext(line, surroundingLines) {
89
+ // Check the current line
90
+ if (CSS_CLASS_PATTERNS.some(pattern => pattern.test(line))) {
91
+ return true;
92
+ }
93
+
94
+ // Check surrounding lines for context
95
+ if (surroundingLines && surroundingLines.length > 0) {
96
+ const contextBlock = surroundingLines.join('\n');
97
+ return CSS_CLASS_PATTERNS.some(pattern => pattern.test(contextBlock));
98
+ }
99
+
100
+ return false;
101
+ }
102
+
103
+ /**
104
+ * Check if "mock" or similar words are used as legitimate UI labels
105
+ */
106
+ function isLegitimateUILabel(line, surroundingLines) {
107
+ const fullContext = surroundingLines
108
+ ? [...surroundingLines, line].join('\n')
109
+ : line;
110
+
111
+ return LEGITIMATE_UI_LABEL_PATTERNS.some(pattern => pattern.test(fullContext));
112
+ }
113
+
114
+ /**
115
+ * Check if the content is legitimate example/template data
116
+ */
117
+ function isLegitimateExampleData(line, surroundingLines) {
118
+ const fullContext = surroundingLines
119
+ ? [...surroundingLines, line].join('\n')
120
+ : line;
121
+
122
+ return LEGITIMATE_EXAMPLE_DATA_PATTERNS.some(pattern => pattern.test(fullContext));
123
+ }
124
+
125
+ /**
126
+ * Check if the match is in a JSX prop context (not user-visible content)
127
+ */
128
+ function isJSXPropContext(line, matchedPattern) {
129
+ // Check if the pattern is part of a JSX prop name (not value)
130
+ const propNamePatterns = [
131
+ // Prop names that legitimately contain keywords
132
+ /\b(?:placeholder|mock|test)(?:Id|Key|Ref|Name|Type|Mode|Data)\s*[=:]/i,
133
+ // Data attributes
134
+ /data-(?:placeholder|mock|test)\s*=/i,
135
+ // Aria labels (visible content, but intentional)
136
+ /aria-(?:label|description)\s*=\s*["'][^"']*(?:placeholder|mock|test)/i,
137
+ ];
138
+
139
+ return propNamePatterns.some(pattern => pattern.test(line));
140
+ }
141
+
142
+ /**
143
+ * Main context detection function
144
+ * Analyzes a line and its context to determine if a match is a false positive
145
+ */
146
+ function detectFalsePositive(line, matchedPattern, options = {}) {
147
+ const { lineIndex, allLines, filePath, patternType } = options;
148
+
149
+ // Get surrounding lines for context (2 before, 2 after)
150
+ let surroundingLines = [];
151
+ if (allLines && lineIndex !== undefined) {
152
+ const start = Math.max(0, lineIndex - 2);
153
+ const end = Math.min(allLines.length, lineIndex + 3);
154
+ surroundingLines = allLines.slice(start, end);
155
+ }
156
+
157
+ // Check for Tailwind CSS placeholder modifier
158
+ const lowerPattern = (matchedPattern || '').toLowerCase();
159
+ if (lowerPattern.includes('placeholder') || patternType === 'placeholder') {
160
+ if (isTailwindPlaceholderModifier(line)) {
161
+ return {
162
+ isFalsePositive: true,
163
+ reason: 'Tailwind CSS placeholder: modifier (styles input placeholder text)',
164
+ confidence: 0.95,
165
+ };
166
+ }
167
+ }
168
+
169
+ // Check for CSS class context
170
+ if (isCSSClassContext(line, surroundingLines)) {
171
+ return {
172
+ isFalsePositive: true,
173
+ reason: 'CSS class/className context (styling, not content)',
174
+ confidence: 0.9,
175
+ };
176
+ }
177
+
178
+ // Check for legitimate UI labels (Mock/Live toggles, etc.)
179
+ if (lowerPattern.includes('mock') || patternType === 'mock') {
180
+ if (isLegitimateUILabel(line, surroundingLines)) {
181
+ return {
182
+ isFalsePositive: true,
183
+ reason: 'Legitimate UI label (mode toggle, button text)',
184
+ confidence: 0.85,
185
+ };
186
+ }
187
+ }
188
+
189
+ // Check for legitimate example data ("Todo App", etc.)
190
+ if (lowerPattern.includes('todo') || patternType === 'todo') {
191
+ if (isLegitimateExampleData(line, surroundingLines)) {
192
+ return {
193
+ isFalsePositive: true,
194
+ reason: 'Legitimate example/template data in UI',
195
+ confidence: 0.8,
196
+ };
197
+ }
198
+ }
199
+
200
+ // Check for JSX props that are not content
201
+ if (isJSXPropContext(line, matchedPattern)) {
202
+ return {
203
+ isFalsePositive: true,
204
+ reason: 'JSX prop context (attribute, not visible content)',
205
+ confidence: 0.75,
206
+ };
207
+ }
208
+
209
+ // Not a false positive
210
+ return {
211
+ isFalsePositive: false,
212
+ confidence: 0.5,
213
+ };
214
+ }
215
+
216
+ /**
217
+ * Quick check if a line is likely a false positive
218
+ * Use this for fast pre-filtering before detailed analysis
219
+ */
220
+ function quickFalsePositiveCheck(line) {
221
+ // Fast check for common false positive patterns
222
+ const quickPatterns = [
223
+ /className.*placeholder:/,
224
+ /class=.*placeholder:/,
225
+ /placeholder:text-/,
226
+ /placeholder:opacity-/,
227
+ /placeholder:italic/,
228
+ /onModeChange.*['"]mock["']/i,
229
+ /onClick.*['"]mock["']/i,
230
+ /value=['"](?:mock|live)["']/i,
231
+ /\[['"]Todo App["']/i,
232
+ /\[['"]Mock["'],\s*['"]Live["']\]/i,
233
+ />\s*Mock\s*</i,
234
+ />\s*Live\s*</i,
235
+ ];
236
+
237
+ return quickPatterns.some(pattern => pattern.test(line));
238
+ }
239
+
240
+ /**
241
+ * Filter findings to remove false positives
242
+ */
243
+ function filterFalsePositives(findings, allLines, options = {}) {
244
+ return findings.filter(finding => {
245
+ const lineContent = finding.content || finding.codeSnippet || allLines[finding.line - 1] || '';
246
+ const result = detectFalsePositive(lineContent, lineContent, {
247
+ lineIndex: finding.line - 1,
248
+ allLines,
249
+ ...options,
250
+ });
251
+ return !result.isFalsePositive;
252
+ });
253
+ }
254
+
255
+ module.exports = {
256
+ isTailwindPlaceholderModifier,
257
+ isCSSClassContext,
258
+ isLegitimateUILabel,
259
+ isLegitimateExampleData,
260
+ isJSXPropContext,
261
+ detectFalsePositive,
262
+ quickFalsePositiveCheck,
263
+ filterFalsePositives,
264
+ };
@@ -1,6 +1,11 @@
1
1
  /**
2
2
  * Cross-File Analysis Engine
3
3
  * Analyzes relationships between files: unused exports, circular dependencies, import consistency
4
+ * Enhanced with monorepo support:
5
+ * - tsconfig.json path alias resolution
6
+ * - package.json exports field support
7
+ * - Workspace package detection
8
+ * - Better barrel export handling
4
9
  */
5
10
 
6
11
  const fs = require("fs");
@@ -9,6 +14,162 @@ const { getAST } = require("./ast-cache");
9
14
  const traverse = require("@babel/traverse").default;
10
15
  const t = require("@babel/types");
11
16
 
17
+ /**
18
+ * Load and cache tsconfig.json paths
19
+ */
20
+ let cachedPathAliases = null;
21
+ let cachedPathsRoot = null;
22
+
23
+ function loadPathAliases(repoRoot) {
24
+ if (cachedPathAliases && cachedPathsRoot === repoRoot) {
25
+ return cachedPathAliases;
26
+ }
27
+
28
+ cachedPathsRoot = repoRoot;
29
+ cachedPathAliases = new Map();
30
+
31
+ // Try to load tsconfig.json
32
+ const tsconfigPaths = [
33
+ path.join(repoRoot, "tsconfig.json"),
34
+ path.join(repoRoot, "tsconfig.base.json"),
35
+ path.join(repoRoot, "jsconfig.json"),
36
+ ];
37
+
38
+ for (const tsconfigPath of tsconfigPaths) {
39
+ if (fs.existsSync(tsconfigPath)) {
40
+ try {
41
+ // Remove comments from JSON (tsconfig allows them)
42
+ const content = fs.readFileSync(tsconfigPath, "utf8")
43
+ .replace(/\/\*[\s\S]*?\*\//g, "")
44
+ .replace(/\/\/.*$/gm, "");
45
+ const tsconfig = JSON.parse(content);
46
+ const compilerOptions = tsconfig.compilerOptions || {};
47
+ const baseUrl = compilerOptions.baseUrl || ".";
48
+ const paths = compilerOptions.paths || {};
49
+
50
+ for (const [alias, targets] of Object.entries(paths)) {
51
+ // Convert alias pattern to regex
52
+ const aliasPattern = alias.replace(/\*/g, "(.*)");
53
+ const aliasRegex = new RegExp(`^${aliasPattern}$`);
54
+
55
+ cachedPathAliases.set(aliasRegex, {
56
+ alias,
57
+ targets: targets.map(t => path.join(repoRoot, baseUrl, t)),
58
+ });
59
+ }
60
+ break; // Use first found config
61
+ } catch (e) {
62
+ // Ignore parse errors
63
+ }
64
+ }
65
+ }
66
+
67
+ return cachedPathAliases;
68
+ }
69
+
70
+ /**
71
+ * Resolve path alias to actual path
72
+ */
73
+ function resolvePathAlias(importPath, repoRoot) {
74
+ const pathAliases = loadPathAliases(repoRoot);
75
+
76
+ for (const [aliasRegex, { alias, targets }] of pathAliases.entries()) {
77
+ const match = importPath.match(aliasRegex);
78
+ if (match) {
79
+ const captured = match[1] || "";
80
+
81
+ for (const target of targets) {
82
+ const resolved = target.replace(/\*/g, captured);
83
+
84
+ // Try common extensions
85
+ const extensions = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
86
+ for (const ext of extensions) {
87
+ const candidate = resolved + ext;
88
+ if (fs.existsSync(candidate)) {
89
+ return candidate;
90
+ }
91
+ }
92
+ }
93
+ }
94
+ }
95
+
96
+ return null;
97
+ }
98
+
99
+ /**
100
+ * Detect workspace packages in monorepo
101
+ */
102
+ let cachedWorkspaces = null;
103
+
104
+ function detectWorkspaces(repoRoot) {
105
+ if (cachedWorkspaces) return cachedWorkspaces;
106
+
107
+ cachedWorkspaces = new Map();
108
+ const packageJsonPath = path.join(repoRoot, "package.json");
109
+
110
+ if (!fs.existsSync(packageJsonPath)) return cachedWorkspaces;
111
+
112
+ try {
113
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
114
+ const workspaces = packageJson.workspaces || [];
115
+
116
+ // Handle both array and object format
117
+ const workspacePatterns = Array.isArray(workspaces)
118
+ ? workspaces
119
+ : workspaces.packages || [];
120
+
121
+ for (const pattern of workspacePatterns) {
122
+ // Simple glob matching (handles common patterns like "packages/*")
123
+ const baseDir = pattern.replace(/\*.*$/, "");
124
+ const fullBaseDir = path.join(repoRoot, baseDir);
125
+
126
+ if (fs.existsSync(fullBaseDir)) {
127
+ const entries = fs.readdirSync(fullBaseDir, { withFileTypes: true });
128
+ for (const entry of entries) {
129
+ if (entry.isDirectory()) {
130
+ const pkgJsonPath = path.join(fullBaseDir, entry.name, "package.json");
131
+ if (fs.existsSync(pkgJsonPath)) {
132
+ try {
133
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"));
134
+ cachedWorkspaces.set(pkgJson.name, {
135
+ name: pkgJson.name,
136
+ path: path.join(baseDir, entry.name),
137
+ exports: pkgJson.exports || {},
138
+ });
139
+ } catch (e) {
140
+ // Ignore parse errors
141
+ }
142
+ }
143
+ }
144
+ }
145
+ }
146
+ }
147
+ } catch (e) {
148
+ // Ignore errors
149
+ }
150
+
151
+ return cachedWorkspaces;
152
+ }
153
+
154
+ /**
155
+ * Check if import is a workspace package
156
+ */
157
+ function isWorkspaceImport(importPath, repoRoot) {
158
+ const workspaces = detectWorkspaces(repoRoot);
159
+ return workspaces.has(importPath) ||
160
+ Array.from(workspaces.keys()).some(ws => importPath.startsWith(ws + "/"));
161
+ }
162
+
163
+ /**
164
+ * Check if file is a barrel/index file (re-exports)
165
+ */
166
+ function isBarrelFile(fileRel) {
167
+ return fileRel.endsWith("/index.ts") ||
168
+ fileRel.endsWith("/index.tsx") ||
169
+ fileRel.endsWith("/index.js") ||
170
+ fileRel.endsWith("/index.jsx");
171
+ }
172
+
12
173
  /**
13
174
  * Build export map for all files
14
175
  */
@@ -98,8 +259,43 @@ function buildExportMap(files, repoRoot) {
98
259
 
99
260
  /**
100
261
  * Resolve import path to actual file path
262
+ * Enhanced with path alias and monorepo support
101
263
  */
102
264
  function resolveImportPath(importPath, fromFile, repoRoot) {
265
+ // Skip external packages (node_modules)
266
+ if (!importPath.startsWith(".") && !importPath.startsWith("/") && !importPath.startsWith("@/")) {
267
+ // Check if it's a workspace import
268
+ if (isWorkspaceImport(importPath, repoRoot)) {
269
+ // Resolve workspace import
270
+ const workspaces = detectWorkspaces(repoRoot);
271
+ const wsName = Array.from(workspaces.keys()).find(ws =>
272
+ importPath === ws || importPath.startsWith(ws + "/")
273
+ );
274
+
275
+ if (wsName) {
276
+ const ws = workspaces.get(wsName);
277
+ const subPath = importPath.slice(wsName.length + 1) || "index";
278
+ const wsFullPath = path.join(repoRoot, ws.path, "src", subPath);
279
+
280
+ const extensions = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx"];
281
+ for (const ext of extensions) {
282
+ const candidate = wsFullPath + ext;
283
+ if (fs.existsSync(candidate)) {
284
+ return path.relative(repoRoot, candidate).replace(/\\/g, "/");
285
+ }
286
+ }
287
+ }
288
+ }
289
+
290
+ // Try path alias resolution
291
+ const aliasResolved = resolvePathAlias(importPath, repoRoot);
292
+ if (aliasResolved) {
293
+ return path.relative(repoRoot, aliasResolved).replace(/\\/g, "/");
294
+ }
295
+
296
+ return null; // External package
297
+ }
298
+
103
299
  const dir = path.dirname(path.join(repoRoot, fromFile));
104
300
  let resolved = path.resolve(dir, importPath);
105
301
 
@@ -157,17 +353,41 @@ function analyzeCrossFile(files, repoRoot) {
157
353
 
158
354
  for (const exp of exports) {
159
355
  if (!used.has(exp) && exp !== "default") {
160
- // Check if it's a test file or config file (likely to have unused exports)
161
- if (!fileRel.includes(".test.") && !fileRel.includes(".spec.") && !fileRel.includes("config")) {
356
+ // Skip known patterns that legitimately have unused exports
357
+ const shouldSkip =
358
+ // Test/spec files
359
+ fileRel.includes(".test.") ||
360
+ fileRel.includes(".spec.") ||
361
+ fileRel.includes("__tests__") ||
362
+ // Config files
363
+ fileRel.includes("config") ||
364
+ fileRel.includes(".config.") ||
365
+ // Type definitions
366
+ fileRel.endsWith(".d.ts") ||
367
+ // Barrel/index files (re-exports are intentionally not all used)
368
+ isBarrelFile(fileRel) ||
369
+ // Entry points
370
+ fileRel.includes("/pages/") ||
371
+ fileRel.includes("/app/") ||
372
+ fileRel.endsWith("/index.ts") ||
373
+ // API routes (exported handlers are used by framework)
374
+ fileRel.includes("/api/") ||
375
+ // Stories
376
+ fileRel.includes(".stories.") ||
377
+ // Migrations
378
+ fileRel.includes("/migrations/");
379
+
380
+ if (!shouldSkip) {
162
381
  findings.push({
163
382
  type: "unused_export",
164
- severity: "WARN",
383
+ severity: "INFO", // Downgraded from WARN - often intentional
165
384
  category: "CodeQuality",
166
385
  file: fileRel,
167
- line: 0, // Would need to track line numbers
168
- title: `Unused export: ${exp}`,
169
- message: `Export "${exp}" from ${fileRel} is never imported`,
170
- confidence: "med",
386
+ line: 0,
387
+ title: `Potentially unused export: ${exp}`,
388
+ message: `Export "${exp}" from ${fileRel} is not imported by other analyzed files`,
389
+ confidence: "low", // Low confidence - might be used externally
390
+ fixHint: "Verify if this export is used externally or remove if truly unused",
171
391
  });
172
392
  }
173
393
  }
@@ -211,20 +431,42 @@ function analyzeCrossFile(files, repoRoot) {
211
431
  return null;
212
432
  }
213
433
 
434
+ const reportedCycles = new Set();
435
+
214
436
  for (const fileRel of exportMap.keys()) {
215
437
  if (!visited.has(fileRel)) {
216
438
  const cycle = detectCycle(fileRel);
217
439
  if (cycle && cycle.length > 2) {
218
- findings.push({
219
- type: "circular_dependency",
220
- severity: "WARN",
221
- category: "CodeQuality",
222
- file: cycle[0],
223
- line: 0,
224
- title: `Circular dependency detected`,
225
- message: `Circular dependency: ${cycle.join(" ")}`,
226
- confidence: "high",
227
- });
440
+ // Create a normalized cycle key to avoid duplicate reports
441
+ const cycleKey = [...cycle].sort().join("|");
442
+
443
+ if (!reportedCycles.has(cycleKey)) {
444
+ reportedCycles.add(cycleKey);
445
+
446
+ // Determine severity based on cycle type
447
+ // Type-only imports often don't cause runtime issues
448
+ let severity = "WARN";
449
+ let confidence = "high";
450
+
451
+ // Check if any files in the cycle are barrel files (common and often harmless)
452
+ const hasBarrel = cycle.some(f => isBarrelFile(f));
453
+ if (hasBarrel) {
454
+ severity = "INFO";
455
+ confidence = "med";
456
+ }
457
+
458
+ findings.push({
459
+ type: "circular_dependency",
460
+ severity,
461
+ category: "CodeQuality",
462
+ file: cycle[0],
463
+ line: 0,
464
+ title: `Circular dependency detected (${cycle.length - 1} files)`,
465
+ message: `Circular dependency: ${cycle.join(" → ")}`,
466
+ confidence,
467
+ fixHint: "Break the cycle by extracting shared code to a separate module, or use dynamic imports",
468
+ });
469
+ }
228
470
  }
229
471
  }
230
472
  }
@@ -246,23 +488,46 @@ function analyzeCrossFile(files, repoRoot) {
246
488
 
247
489
  for (const [targetFile, styles] of importStyles.entries()) {
248
490
  if (styles.size > 1) {
249
- findings.push({
250
- type: "inconsistent_imports",
251
- severity: "WARN",
252
- category: "CodeQuality",
253
- file: targetFile,
254
- line: 0,
255
- title: `Inconsistent import paths`,
256
- message: `Module imported with ${styles.size} different paths: ${Array.from(styles).join(", ")}`,
257
- confidence: "low",
258
- });
491
+ // Filter out path alias variations that resolve to the same file
492
+ const uniqueStyles = new Set(
493
+ Array.from(styles).filter(s => !s.startsWith("@/") || styles.size === 1)
494
+ );
495
+
496
+ if (uniqueStyles.size > 1) {
497
+ findings.push({
498
+ type: "inconsistent_imports",
499
+ severity: "INFO", // Downgraded - often just style preference
500
+ category: "CodeQuality",
501
+ file: targetFile,
502
+ line: 0,
503
+ title: `Inconsistent import paths`,
504
+ message: `Module imported with ${uniqueStyles.size} different paths: ${Array.from(uniqueStyles).join(", ")}`,
505
+ confidence: "low",
506
+ fixHint: "Standardize import paths using path aliases (e.g., @/) for consistency",
507
+ });
508
+ }
259
509
  }
260
510
  }
261
511
 
262
512
  return findings;
263
513
  }
264
514
 
515
+ /**
516
+ * Clear all caches (useful for testing)
517
+ */
518
+ function clearCaches() {
519
+ cachedPathAliases = null;
520
+ cachedPathsRoot = null;
521
+ cachedWorkspaces = null;
522
+ }
523
+
265
524
  module.exports = {
266
525
  analyzeCrossFile,
267
526
  buildExportMap,
527
+ resolveImportPath,
528
+ loadPathAliases,
529
+ detectWorkspaces,
530
+ isWorkspaceImport,
531
+ isBarrelFile,
532
+ clearCaches,
268
533
  };