fivocell 4.0.0 → 4.1.1
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/dist/__tests__/behavior-intelligence-bug.test.d.ts +2 -0
- package/dist/__tests__/behavior-intelligence-bug.test.d.ts.map +1 -0
- package/dist/__tests__/behavior-intelligence-bug.test.js +21 -0
- package/dist/__tests__/behavior-intelligence-bug.test.js.map +1 -0
- package/dist/__tests__/code-scanner-arrow-return-type.test.d.ts +2 -0
- package/dist/__tests__/code-scanner-arrow-return-type.test.d.ts.map +1 -0
- package/dist/__tests__/code-scanner-arrow-return-type.test.js +76 -0
- package/dist/__tests__/code-scanner-arrow-return-type.test.js.map +1 -0
- package/dist/__tests__/code-scanner-blindspot.test.d.ts +2 -0
- package/dist/__tests__/code-scanner-blindspot.test.d.ts.map +1 -0
- package/dist/__tests__/code-scanner-blindspot.test.js +18 -0
- package/dist/__tests__/code-scanner-blindspot.test.js.map +1 -0
- package/dist/__tests__/code-scanner-error-recovery.test.d.ts +2 -0
- package/dist/__tests__/code-scanner-error-recovery.test.d.ts.map +1 -0
- package/dist/__tests__/code-scanner-error-recovery.test.js +21 -0
- package/dist/__tests__/code-scanner-error-recovery.test.js.map +1 -0
- package/dist/__tests__/code-scanner-n1-detection.test.d.ts +2 -0
- package/dist/__tests__/code-scanner-n1-detection.test.d.ts.map +1 -0
- package/dist/__tests__/code-scanner-n1-detection.test.js +113 -0
- package/dist/__tests__/code-scanner-n1-detection.test.js.map +1 -0
- package/dist/__tests__/code-scanner-nesting.test.d.ts +2 -0
- package/dist/__tests__/code-scanner-nesting.test.d.ts.map +1 -0
- package/dist/__tests__/code-scanner-nesting.test.js +113 -0
- package/dist/__tests__/code-scanner-nesting.test.js.map +1 -0
- package/dist/__tests__/code-scanner-null-check.test.d.ts +2 -0
- package/dist/__tests__/code-scanner-null-check.test.d.ts.map +1 -0
- package/dist/__tests__/code-scanner-null-check.test.js +126 -0
- package/dist/__tests__/code-scanner-null-check.test.js.map +1 -0
- package/dist/__tests__/code-scanner-sql-fix.test.d.ts +2 -0
- package/dist/__tests__/code-scanner-sql-fix.test.d.ts.map +1 -0
- package/dist/__tests__/code-scanner-sql-fix.test.js +21 -0
- package/dist/__tests__/code-scanner-sql-fix.test.js.map +1 -0
- package/dist/__tests__/code-scanner-trust-score.test.d.ts +1 -0
- package/dist/__tests__/code-scanner-trust-score.test.d.ts.map +1 -0
- package/dist/__tests__/code-scanner-trust-score.test.js +39 -0
- package/dist/__tests__/code-scanner-trust-score.test.js.map +1 -0
- package/dist/__tests__/code-scanner-validation.test.d.ts +2 -0
- package/dist/__tests__/code-scanner-validation.test.d.ts.map +1 -0
- package/dist/__tests__/code-scanner-validation.test.js +131 -0
- package/dist/__tests__/code-scanner-validation.test.js.map +1 -0
- package/dist/__tests__/community-store.test.d.ts +2 -0
- package/dist/__tests__/community-store.test.d.ts.map +1 -0
- package/dist/__tests__/community-store.test.js +231 -0
- package/dist/__tests__/community-store.test.js.map +1 -0
- package/dist/__tests__/enhanced-blind-spots.test.d.ts +2 -0
- package/dist/__tests__/enhanced-blind-spots.test.d.ts.map +1 -0
- package/dist/__tests__/enhanced-blind-spots.test.js +347 -0
- package/dist/__tests__/enhanced-blind-spots.test.js.map +1 -0
- package/dist/__tests__/knowledge-graph-store.test.d.ts +2 -0
- package/dist/__tests__/knowledge-graph-store.test.d.ts.map +1 -0
- package/dist/__tests__/knowledge-graph-store.test.js +252 -0
- package/dist/__tests__/knowledge-graph-store.test.js.map +1 -0
- package/dist/__tests__/live-watcher.test.d.ts +2 -0
- package/dist/__tests__/live-watcher.test.d.ts.map +1 -0
- package/dist/__tests__/live-watcher.test.js +312 -0
- package/dist/__tests__/live-watcher.test.js.map +1 -0
- package/dist/__tests__/mcp-cell-tools.test.d.ts +2 -0
- package/dist/__tests__/mcp-cell-tools.test.d.ts.map +1 -0
- package/dist/__tests__/mcp-cell-tools.test.js +176 -0
- package/dist/__tests__/mcp-cell-tools.test.js.map +1 -0
- package/dist/__tests__/multi-project.test.d.ts +2 -0
- package/dist/__tests__/multi-project.test.d.ts.map +1 -0
- package/dist/__tests__/multi-project.test.js +145 -0
- package/dist/__tests__/multi-project.test.js.map +1 -0
- package/dist/__tests__/pc-scanner-paths.test.d.ts +2 -0
- package/dist/__tests__/pc-scanner-paths.test.d.ts.map +1 -0
- package/dist/__tests__/pc-scanner-paths.test.js +16 -0
- package/dist/__tests__/pc-scanner-paths.test.js.map +1 -0
- package/dist/__tests__/prompt-builder-realdata.test.d.ts +2 -0
- package/dist/__tests__/prompt-builder-realdata.test.d.ts.map +1 -0
- package/dist/__tests__/prompt-builder-realdata.test.js +94 -0
- package/dist/__tests__/prompt-builder-realdata.test.js.map +1 -0
- package/dist/__tests__/prompt-builder-sessions.test.d.ts +2 -0
- package/dist/__tests__/prompt-builder-sessions.test.d.ts.map +1 -0
- package/dist/__tests__/prompt-builder-sessions.test.js +124 -0
- package/dist/__tests__/prompt-builder-sessions.test.js.map +1 -0
- package/dist/__tests__/security.test.d.ts +1 -0
- package/dist/__tests__/security.test.d.ts.map +1 -0
- package/dist/__tests__/security.test.js +161 -0
- package/dist/__tests__/security.test.js.map +1 -0
- package/dist/__tests__/session-bridge.test.d.ts +2 -0
- package/dist/__tests__/session-bridge.test.d.ts.map +1 -0
- package/dist/__tests__/session-bridge.test.js +158 -0
- package/dist/__tests__/session-bridge.test.js.map +1 -0
- package/dist/__tests__/session-memory-tables.test.d.ts +2 -0
- package/dist/__tests__/session-memory-tables.test.d.ts.map +1 -0
- package/dist/__tests__/session-memory-tables.test.js +169 -0
- package/dist/__tests__/session-memory-tables.test.js.map +1 -0
- package/dist/__tests__/staleness-detection.test.d.ts +2 -0
- package/dist/__tests__/staleness-detection.test.d.ts.map +1 -0
- package/dist/__tests__/staleness-detection.test.js +105 -0
- package/dist/__tests__/staleness-detection.test.js.map +1 -0
- package/dist/__tests__/team-collaboration.test.d.ts +2 -0
- package/dist/__tests__/team-collaboration.test.d.ts.map +1 -0
- package/dist/__tests__/team-collaboration.test.js +224 -0
- package/dist/__tests__/team-collaboration.test.js.map +1 -0
- package/dist/__tests__/tool-specific-format.test.d.ts +2 -0
- package/dist/__tests__/tool-specific-format.test.d.ts.map +1 -0
- package/dist/__tests__/tool-specific-format.test.js +130 -0
- package/dist/__tests__/tool-specific-format.test.js.map +1 -0
- package/dist/__tests__/usage-intelligence-store.test.d.ts +2 -0
- package/dist/__tests__/usage-intelligence-store.test.d.ts.map +1 -0
- package/dist/__tests__/usage-intelligence-store.test.js +266 -0
- package/dist/__tests__/usage-intelligence-store.test.js.map +1 -0
- package/dist/behavior-intelligence.d.ts.map +1 -1
- package/dist/behavior-intelligence.js +12 -1
- package/dist/behavior-intelligence.js.map +1 -1
- package/dist/cli.js +403 -4
- package/dist/cli.js.map +1 -1
- package/dist/code-scanner.d.ts.map +1 -1
- package/dist/code-scanner.js +344 -102
- package/dist/code-scanner.js.map +1 -1
- package/dist/core/community-store.d.ts +128 -0
- package/dist/core/community-store.d.ts.map +1 -0
- package/dist/core/community-store.js +329 -0
- package/dist/core/community-store.js.map +1 -0
- package/dist/core/database.d.ts +0 -3
- package/dist/core/database.d.ts.map +1 -1
- package/dist/core/database.js +287 -15
- package/dist/core/database.js.map +1 -1
- package/dist/core/enhanced-blind-spots.d.ts +27 -0
- package/dist/core/enhanced-blind-spots.d.ts.map +1 -0
- package/dist/core/enhanced-blind-spots.js +710 -0
- package/dist/core/enhanced-blind-spots.js.map +1 -0
- package/dist/core/knowledge-graph-store.d.ts +69 -0
- package/dist/core/knowledge-graph-store.d.ts.map +1 -0
- package/dist/core/knowledge-graph-store.js +269 -0
- package/dist/core/knowledge-graph-store.js.map +1 -0
- package/dist/core/live-watcher.d.ts +52 -0
- package/dist/core/live-watcher.d.ts.map +1 -0
- package/dist/core/live-watcher.js +369 -0
- package/dist/core/live-watcher.js.map +1 -0
- package/dist/core/project-registry.d.ts +24 -0
- package/dist/core/project-registry.d.ts.map +1 -0
- package/dist/core/project-registry.js +70 -0
- package/dist/core/project-registry.js.map +1 -0
- package/dist/core/prompt-builder.d.ts +49 -0
- package/dist/core/prompt-builder.d.ts.map +1 -1
- package/dist/core/prompt-builder.js +540 -67
- package/dist/core/prompt-builder.js.map +1 -1
- package/dist/core/security.d.ts +23 -0
- package/dist/core/security.d.ts.map +1 -0
- package/dist/core/security.js +117 -0
- package/dist/core/security.js.map +1 -0
- package/dist/core/session-memory.d.ts +64 -0
- package/dist/core/session-memory.d.ts.map +1 -1
- package/dist/core/session-memory.js +111 -0
- package/dist/core/session-memory.js.map +1 -1
- package/dist/core/syntax-engine.d.ts.map +1 -1
- package/dist/core/syntax-engine.js +5 -2
- package/dist/core/syntax-engine.js.map +1 -1
- package/dist/core/usage-intelligence-store.d.ts +126 -0
- package/dist/core/usage-intelligence-store.d.ts.map +1 -0
- package/dist/core/usage-intelligence-store.js +405 -0
- package/dist/core/usage-intelligence-store.js.map +1 -0
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +936 -17
- package/dist/daemon/server.js.map +1 -1
- package/dist/knowledge-graph-builder.d.ts.map +1 -1
- package/dist/knowledge-graph-builder.js +18 -2
- package/dist/knowledge-graph-builder.js.map +1 -1
- package/dist/pc-scanner.d.ts +1 -0
- package/dist/pc-scanner.d.ts.map +1 -1
- package/dist/pc-scanner.js +64 -33
- package/dist/pc-scanner.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,710 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.scanFileForBlindSpots = scanFileForBlindSpots;
|
|
37
|
+
exports.scanDirectoryForBlindSpots = scanDirectoryForBlindSpots;
|
|
38
|
+
exports.summarizeBlindSpots = summarizeBlindSpots;
|
|
39
|
+
exports.formatBlindSpotReport = formatBlindSpotReport;
|
|
40
|
+
exports.getCategoryCounts = getCategoryCounts;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────
|
|
44
|
+
const REACT_EXT = new Set(['.jsx', '.tsx']);
|
|
45
|
+
const JS_EXT = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs']);
|
|
46
|
+
const IGNORE_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', '.nuxt', 'coverage', '__pycache__', 'archived']);
|
|
47
|
+
function shouldScan(p) {
|
|
48
|
+
const parts = p.split(path.sep);
|
|
49
|
+
return !parts.some(dir => IGNORE_DIRS.has(dir) || dir.startsWith('.'));
|
|
50
|
+
}
|
|
51
|
+
function readLines(filePath) {
|
|
52
|
+
try {
|
|
53
|
+
return fs.readFileSync(filePath, 'utf-8').split(/\r?\n/);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function lineAt(lines, idx) {
|
|
60
|
+
if (idx < 0 || idx >= lines.length)
|
|
61
|
+
return '';
|
|
62
|
+
return lines[idx].trim();
|
|
63
|
+
}
|
|
64
|
+
function findLine(lines, predicate, startFrom = 0) {
|
|
65
|
+
for (let i = startFrom; i < lines.length; i++) {
|
|
66
|
+
if (predicate(lines[i]))
|
|
67
|
+
return i + 1;
|
|
68
|
+
}
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
function isAsyncFunc(line) {
|
|
72
|
+
return /^\s*(export\s+)?(async\s+)?(function|const|let|var|class|\([^)]*\)|\w+\s*=\s*async|\w+\s*:\s*async)/.test(line)
|
|
73
|
+
&& /async\s/.test(line);
|
|
74
|
+
}
|
|
75
|
+
function hasTryCatchBetween(lines, start, depth = 1) {
|
|
76
|
+
// Walk forward, track brace depth, look for matching try/catch pair
|
|
77
|
+
let tries = 0, catches = 0;
|
|
78
|
+
for (let i = start; i < lines.length && i < start + 80; i++) {
|
|
79
|
+
const l = lines[i];
|
|
80
|
+
if (/\btry\s*\{/.test(l))
|
|
81
|
+
tries++;
|
|
82
|
+
if (/\}\s*catch\b/.test(l) || /\bcatch\s*\(/.test(l))
|
|
83
|
+
catches++;
|
|
84
|
+
}
|
|
85
|
+
return catches > 0;
|
|
86
|
+
}
|
|
87
|
+
function makeId() {
|
|
88
|
+
return 'bs_' + Math.random().toString(36).slice(2, 10) + Date.now().toString(36);
|
|
89
|
+
}
|
|
90
|
+
// ─── Detectors ────────────────────────────────────────────────────────────
|
|
91
|
+
function detectPromisesWithoutCatch(filePath, lines) {
|
|
92
|
+
const ext = path.extname(filePath);
|
|
93
|
+
if (!JS_EXT.has(ext))
|
|
94
|
+
return [];
|
|
95
|
+
const out = [];
|
|
96
|
+
for (let i = 0; i < lines.length; i++) {
|
|
97
|
+
const l = lineAt(lines, i);
|
|
98
|
+
if (/\.then\s*\(/.test(l) && !/\.catch\s*\(/.test(l)) {
|
|
99
|
+
// Look ahead 5 lines for a .catch
|
|
100
|
+
let hasCatch = false;
|
|
101
|
+
for (let j = i + 1; j <= i + 5 && j < lines.length; j++) {
|
|
102
|
+
if (/\.catch\s*\(/.test(lines[j])) {
|
|
103
|
+
hasCatch = true;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (!hasCatch) {
|
|
108
|
+
out.push({
|
|
109
|
+
id: makeId(), category: 'error_handling', severity: 'high',
|
|
110
|
+
message: 'Promise.then() without .catch() — unhandled rejection risk',
|
|
111
|
+
filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
|
|
112
|
+
recommendation: 'Add .catch() handler, or use try/await with proper error handling',
|
|
113
|
+
detectedAt: new Date().toISOString(),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
function detectMissingReactErrorBoundaries(filePath, lines) {
|
|
121
|
+
const ext = path.extname(filePath);
|
|
122
|
+
if (!REACT_EXT.has(ext))
|
|
123
|
+
return [];
|
|
124
|
+
const out = [];
|
|
125
|
+
// Check for render functions / class components without ErrorBoundary wrap
|
|
126
|
+
let hasErrorBoundary = false;
|
|
127
|
+
for (const l of lines) {
|
|
128
|
+
if (/ErrorBoundary|componentDidCatch|getDerivedStateFromError/.test(l)) {
|
|
129
|
+
hasErrorBoundary = true;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (hasErrorBoundary)
|
|
134
|
+
return [];
|
|
135
|
+
for (let i = 0; i < lines.length; i++) {
|
|
136
|
+
const l = lineAt(lines, i);
|
|
137
|
+
if (/(return\s*\(\s*<|render\s*\(\s*\)\s*\{|class\s+\w+\s+extends\s+(React\.)?Component)/.test(l)) {
|
|
138
|
+
out.push({
|
|
139
|
+
id: makeId(), category: 'error_handling', severity: 'medium',
|
|
140
|
+
message: 'React component without ErrorBoundary — unhandled render errors crash tree',
|
|
141
|
+
filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
|
|
142
|
+
recommendation: 'Wrap render in <ErrorBoundary> or add componentDidCatch',
|
|
143
|
+
detectedAt: new Date().toISOString(),
|
|
144
|
+
});
|
|
145
|
+
break; // one per file
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
function detectApiCallsWithoutErrorState(filePath, lines) {
|
|
151
|
+
const ext = path.extname(filePath);
|
|
152
|
+
if (!JS_EXT.has(ext))
|
|
153
|
+
return [];
|
|
154
|
+
const out = [];
|
|
155
|
+
for (let i = 0; i < lines.length; i++) {
|
|
156
|
+
const l = lineAt(lines, i);
|
|
157
|
+
if (/\b(fetch|axios|got)\s*\(/.test(l) || /\b(fetch|axios|got)\.\w+\s*\(/.test(l)) {
|
|
158
|
+
// Look back 10 lines and ahead 15 lines for .catch, try/catch, or error state
|
|
159
|
+
let hasError = false;
|
|
160
|
+
for (let j = Math.max(0, i - 10); j < i + 15 && j < lines.length; j++) {
|
|
161
|
+
if (/\.catch\s*\(/.test(lines[j]) || /\btry\s*\{/.test(lines[j])) {
|
|
162
|
+
hasError = true;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (!hasError) {
|
|
167
|
+
out.push({
|
|
168
|
+
id: makeId(), category: 'error_handling', severity: 'high',
|
|
169
|
+
message: 'API call without error handling — silent failures on network errors',
|
|
170
|
+
filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
|
|
171
|
+
recommendation: 'Add try/catch or .catch() handler; expose error state to UI',
|
|
172
|
+
detectedAt: new Date().toISOString(),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
function detectDbOperationsWithoutRollback(filePath, lines) {
|
|
180
|
+
const ext = path.extname(filePath);
|
|
181
|
+
if (!JS_EXT.has(ext))
|
|
182
|
+
return [];
|
|
183
|
+
const out = [];
|
|
184
|
+
for (let i = 0; i < lines.length; i++) {
|
|
185
|
+
const l = lineAt(lines, i);
|
|
186
|
+
if (/\b(prisma\.\w+\.\w+|db\.\w+\.\w+|knex\(.+?\)\(|sequelize\.\w+|mongoose\.\w+\.\w+|pool\.query)\s*\(/.test(l)) {
|
|
187
|
+
let hasTransaction = false;
|
|
188
|
+
for (let j = i; j < i + 20 && j < lines.length; j++) {
|
|
189
|
+
if (/\$transaction|\$transaction\(|BEGIN|transaction\(\)|\.transaction\(/.test(lines[j])) {
|
|
190
|
+
hasTransaction = true;
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (!hasTransaction) {
|
|
195
|
+
out.push({
|
|
196
|
+
id: makeId(), category: 'error_handling', severity: 'medium',
|
|
197
|
+
message: 'DB operation without transaction — partial writes on failure',
|
|
198
|
+
filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
|
|
199
|
+
recommendation: 'Wrap multi-step DB ops in $transaction or .transaction()',
|
|
200
|
+
detectedAt: new Date().toISOString(),
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
207
|
+
function detectFileOpsWithoutErrorHandling(filePath, lines) {
|
|
208
|
+
const ext = path.extname(filePath);
|
|
209
|
+
if (!JS_EXT.has(ext))
|
|
210
|
+
return [];
|
|
211
|
+
const out = [];
|
|
212
|
+
for (let i = 0; i < lines.length; i++) {
|
|
213
|
+
const l = lineAt(lines, i);
|
|
214
|
+
if (/\b(readFile|writeFile|appendFile|unlink|rm|mkdir|readdir|createReadStream|createWriteStream)\s*\(/.test(l)
|
|
215
|
+
&& !/Sync\s*\(/.test(l)) {
|
|
216
|
+
// Check for await + try/catch nearby
|
|
217
|
+
let hasError = false;
|
|
218
|
+
for (let j = Math.max(0, i - 5); j < i + 10 && j < lines.length; j++) {
|
|
219
|
+
if (/\btry\s*\{/.test(lines[j])) {
|
|
220
|
+
hasError = true;
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (!hasError) {
|
|
225
|
+
out.push({
|
|
226
|
+
id: makeId(), category: 'error_handling', severity: 'low',
|
|
227
|
+
message: 'File operation without error handling — crashes on ENOENT/EACCES',
|
|
228
|
+
filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
|
|
229
|
+
recommendation: 'Wrap in try/catch and handle ENOENT explicitly',
|
|
230
|
+
detectedAt: new Date().toISOString(),
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return out;
|
|
236
|
+
}
|
|
237
|
+
function detectApiWithoutTimeout(filePath, lines) {
|
|
238
|
+
const ext = path.extname(filePath);
|
|
239
|
+
if (!JS_EXT.has(ext))
|
|
240
|
+
return [];
|
|
241
|
+
const out = [];
|
|
242
|
+
for (let i = 0; i < lines.length; i++) {
|
|
243
|
+
const l = lineAt(lines, i);
|
|
244
|
+
if (/\baxios\s*\(/.test(l) || /axios\.\w+\s*\(/.test(l)) {
|
|
245
|
+
// Look at next 10 lines for timeout config
|
|
246
|
+
let hasTimeout = false;
|
|
247
|
+
for (let j = i; j < i + 10 && j < lines.length; j++) {
|
|
248
|
+
if (/timeout\s*:/.test(lines[j])) {
|
|
249
|
+
hasTimeout = true;
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (!hasTimeout) {
|
|
254
|
+
out.push({
|
|
255
|
+
id: makeId(), category: 'error_handling', severity: 'medium',
|
|
256
|
+
message: 'Axios call without timeout — hangs forever on unresponsive servers',
|
|
257
|
+
filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
|
|
258
|
+
recommendation: 'Add timeout: 5000 (or appropriate) in axios config',
|
|
259
|
+
detectedAt: new Date().toISOString(),
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return out;
|
|
265
|
+
}
|
|
266
|
+
function detectAwaitWithoutNullCheck(filePath, lines) {
|
|
267
|
+
const ext = path.extname(filePath);
|
|
268
|
+
if (!JS_EXT.has(ext))
|
|
269
|
+
return [];
|
|
270
|
+
const out = [];
|
|
271
|
+
for (let i = 0; i < lines.length; i++) {
|
|
272
|
+
const l = lineAt(lines, i);
|
|
273
|
+
const m = l.match(/(?:const|let|var)\s+(\w+)\s*=\s*await\s+(?:this\.)?(\w+)\s*\(/);
|
|
274
|
+
if (!m)
|
|
275
|
+
continue;
|
|
276
|
+
const varName = m[1];
|
|
277
|
+
// Look ahead 10 lines for `if (!varName)` or `?.` or null check
|
|
278
|
+
let checked = false;
|
|
279
|
+
for (let j = i + 1; j < i + 10 && j < lines.length; j++) {
|
|
280
|
+
if (new RegExp(`if\\s*\\(\\s*!?\\s*${varName}\\b|${varName}\\s*\\?\\.|${varName}\\s*&&|${varName}\\s*\\|\\|`).test(lines[j])) {
|
|
281
|
+
checked = true;
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (!checked) {
|
|
286
|
+
out.push({
|
|
287
|
+
id: makeId(), category: 'null_safety', severity: 'high',
|
|
288
|
+
message: `Awaited value '${varName}' used without null check`,
|
|
289
|
+
filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
|
|
290
|
+
recommendation: `Add null check: if (!${varName}) return; or use optional chaining (${varName}?.x)`,
|
|
291
|
+
detectedAt: new Date().toISOString(),
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return out;
|
|
296
|
+
}
|
|
297
|
+
function detectArrayAccessWithoutLengthCheck(filePath, lines) {
|
|
298
|
+
const ext = path.extname(filePath);
|
|
299
|
+
if (!JS_EXT.has(ext))
|
|
300
|
+
return [];
|
|
301
|
+
const out = [];
|
|
302
|
+
for (let i = 0; i < lines.length; i++) {
|
|
303
|
+
const l = lineAt(lines, i);
|
|
304
|
+
// Only flag array[0] (not string[0] which is always valid char access)
|
|
305
|
+
const arrMatch = l.match(/\b(\w+)\s*\[\s*0\s*\]/);
|
|
306
|
+
if (!arrMatch)
|
|
307
|
+
continue;
|
|
308
|
+
const varName = arrMatch[1];
|
|
309
|
+
// Skip known non-array identifiers (single-char, common words)
|
|
310
|
+
if (/^(args|params|argv|flags|opts|options|props)$/i.test(varName))
|
|
311
|
+
continue;
|
|
312
|
+
// Check if length was checked within 5 lines above
|
|
313
|
+
let checked = false;
|
|
314
|
+
for (let j = Math.max(0, i - 5); j < i; j++) {
|
|
315
|
+
if (new RegExp(`\\b${varName}\\.length\\s*[><!]|\\b${varName}\\?\\.length`).test(lines[j])) {
|
|
316
|
+
checked = true;
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (!checked) {
|
|
321
|
+
out.push({
|
|
322
|
+
id: makeId(), category: 'null_safety', severity: 'low',
|
|
323
|
+
message: `Array index [0] access on '${varName}' without length check — undefined on empty array`,
|
|
324
|
+
filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
|
|
325
|
+
recommendation: `Check \`${varName}.length > 0\` before access, or use \`${varName}[0] ?? default\``,
|
|
326
|
+
detectedAt: new Date().toISOString(),
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return out;
|
|
331
|
+
}
|
|
332
|
+
function detectProcessEnvWithoutFallback(filePath, lines) {
|
|
333
|
+
const ext = path.extname(filePath);
|
|
334
|
+
if (!JS_EXT.has(ext))
|
|
335
|
+
return [];
|
|
336
|
+
const out = [];
|
|
337
|
+
for (let i = 0; i < lines.length; i++) {
|
|
338
|
+
const l = lineAt(lines, i);
|
|
339
|
+
if (/process\.env\.\w+/.test(l) && !/\|\|/.test(l) && !/\?\?/.test(l) && !/process\.env\.\w+\s*=/.test(l)) {
|
|
340
|
+
out.push({
|
|
341
|
+
id: makeId(), category: 'validation', severity: 'low',
|
|
342
|
+
message: 'process.env used without fallback — undefined if missing',
|
|
343
|
+
filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
|
|
344
|
+
recommendation: 'Add fallback: `process.env.X || "default"` or use ?? operator',
|
|
345
|
+
detectedAt: new Date().toISOString(),
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return out;
|
|
350
|
+
}
|
|
351
|
+
function detectN1QueryPattern(filePath, lines) {
|
|
352
|
+
const ext = path.extname(filePath);
|
|
353
|
+
if (!JS_EXT.has(ext))
|
|
354
|
+
return [];
|
|
355
|
+
const out = [];
|
|
356
|
+
for (let i = 0; i < lines.length; i++) {
|
|
357
|
+
const l = lineAt(lines, i);
|
|
358
|
+
if (/\bfor\s*\(/.test(l) || /\.forEach\s*\(/.test(l) || /\.map\s*\(/.test(l)) {
|
|
359
|
+
// Look inside for await with DB call
|
|
360
|
+
for (let j = i + 1; j < i + 15 && j < lines.length; j++) {
|
|
361
|
+
if (/await\s+(prisma\.|db\.|sequelize|mongoose|knex|pool\.)/.test(lines[j])) {
|
|
362
|
+
out.push({
|
|
363
|
+
id: makeId(), category: 'performance', severity: 'high',
|
|
364
|
+
message: 'N+1 query pattern: DB call inside loop',
|
|
365
|
+
filePath, lineNumber: j + 1, codeSnippet: lines[j].trim().slice(0, 120),
|
|
366
|
+
recommendation: 'Batch query: use `findMany({ where: { id: { in: ids } } })` or include relation',
|
|
367
|
+
detectedAt: new Date().toISOString(),
|
|
368
|
+
});
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return out;
|
|
375
|
+
}
|
|
376
|
+
function detectSyncOpsInAsyncCode(filePath, lines) {
|
|
377
|
+
const ext = path.extname(filePath);
|
|
378
|
+
if (!JS_EXT.has(ext))
|
|
379
|
+
return [];
|
|
380
|
+
const out = [];
|
|
381
|
+
let inAsync = false;
|
|
382
|
+
for (let i = 0; i < lines.length; i++) {
|
|
383
|
+
const l = lineAt(lines, i);
|
|
384
|
+
if (/async\s+(function|\w+\s*\(|\w+\s*=\s*async|\(|\w+\s*:\s*async)/.test(l))
|
|
385
|
+
inAsync = true;
|
|
386
|
+
if (inAsync && /readFileSync|writeFileSync|appendFileSync|execSync|spawnSync/.test(l)) {
|
|
387
|
+
out.push({
|
|
388
|
+
id: makeId(), category: 'performance', severity: 'medium',
|
|
389
|
+
message: 'Sync file/exec operation in async function — blocks event loop',
|
|
390
|
+
filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
|
|
391
|
+
recommendation: 'Use async variant: readFile, writeFile, exec (with await)',
|
|
392
|
+
detectedAt: new Date().toISOString(),
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return out;
|
|
397
|
+
}
|
|
398
|
+
function detectConsoleLogInProduction(filePath, lines) {
|
|
399
|
+
const ext = path.extname(filePath);
|
|
400
|
+
if (!JS_EXT.has(ext))
|
|
401
|
+
return [];
|
|
402
|
+
const out = [];
|
|
403
|
+
let count = 0;
|
|
404
|
+
for (let i = 0; i < lines.length; i++) {
|
|
405
|
+
const l = lineAt(lines, i);
|
|
406
|
+
if (/(^|\s)console\.log\s*\(/.test(l)) {
|
|
407
|
+
count++;
|
|
408
|
+
if (count <= 2) {
|
|
409
|
+
out.push({
|
|
410
|
+
id: makeId(), category: 'quality', severity: 'low',
|
|
411
|
+
message: 'console.log in source — use structured logger in production',
|
|
412
|
+
filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
|
|
413
|
+
recommendation: 'Replace with logger.info() or remove debug logs',
|
|
414
|
+
detectedAt: new Date().toISOString(),
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
// Cap at 3 console.log warnings per file to avoid noise
|
|
420
|
+
return out.slice(0, 3);
|
|
421
|
+
}
|
|
422
|
+
function detectAnyUsage(filePath, lines) {
|
|
423
|
+
const ext = path.extname(filePath);
|
|
424
|
+
if (!JS_EXT.has(ext))
|
|
425
|
+
return [];
|
|
426
|
+
const out = [];
|
|
427
|
+
for (let i = 0; i < lines.length; i++) {
|
|
428
|
+
const l = lineAt(lines, i);
|
|
429
|
+
// Match `: any` or `<any>` or `as any` but not `Many` or `company`
|
|
430
|
+
if (/:\s*any[\s,;\)\]\}]/.test(l) || /<any>/.test(l) || /\bas\s+any\b/.test(l)) {
|
|
431
|
+
out.push({
|
|
432
|
+
id: makeId(), category: 'quality', severity: 'medium',
|
|
433
|
+
message: '`any` type used — defeats TypeScript safety',
|
|
434
|
+
filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
|
|
435
|
+
recommendation: 'Use `unknown` and narrow with type guards, or define proper interface',
|
|
436
|
+
detectedAt: new Date().toISOString(),
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return out;
|
|
441
|
+
}
|
|
442
|
+
function detectNonNullAssertionOveruse(filePath, lines) {
|
|
443
|
+
const ext = path.extname(filePath);
|
|
444
|
+
if (!JS_EXT.has(ext))
|
|
445
|
+
return [];
|
|
446
|
+
let count = 0;
|
|
447
|
+
for (const l of lines) {
|
|
448
|
+
if (/\w![\s\.\(\[]/.test(l) && !/!==/.test(l) && !/!=/.test(l))
|
|
449
|
+
count++;
|
|
450
|
+
}
|
|
451
|
+
if (count < 3)
|
|
452
|
+
return [];
|
|
453
|
+
return [{
|
|
454
|
+
id: makeId(), category: 'null_safety', severity: 'medium',
|
|
455
|
+
message: `High non-null assertion usage (${count}x in file) — possible unsafe null bypasses`,
|
|
456
|
+
filePath, lineNumber: 1, codeSnippet: `(${count} non-null assertions in this file)`,
|
|
457
|
+
recommendation: 'Replace `x!` with explicit null checks or `x ?? default`',
|
|
458
|
+
detectedAt: new Date().toISOString(),
|
|
459
|
+
}];
|
|
460
|
+
}
|
|
461
|
+
function detectMissingPagination(filePath, lines) {
|
|
462
|
+
const ext = path.extname(filePath);
|
|
463
|
+
if (!JS_EXT.has(ext))
|
|
464
|
+
return [];
|
|
465
|
+
const out = [];
|
|
466
|
+
for (let i = 0; i < lines.length; i++) {
|
|
467
|
+
const l = lineAt(lines, i);
|
|
468
|
+
if (/prisma\.\w+\.findMany\s*\(/.test(l) || /\.find\s*\(\s*\{\s*\}\s*\)/.test(l)) {
|
|
469
|
+
// Check next 5 lines for skip/take/limit
|
|
470
|
+
let hasPagination = false;
|
|
471
|
+
for (let j = i; j < i + 5 && j < lines.length; j++) {
|
|
472
|
+
if (/\b(skip|take|limit|offset)\s*:/.test(lines[j])) {
|
|
473
|
+
hasPagination = true;
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (!hasPagination) {
|
|
478
|
+
out.push({
|
|
479
|
+
id: makeId(), category: 'performance', severity: 'medium',
|
|
480
|
+
message: 'findMany() without pagination — loads entire table into memory',
|
|
481
|
+
filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
|
|
482
|
+
recommendation: 'Add `take: 50` and `skip: offset` for pagination',
|
|
483
|
+
detectedAt: new Date().toISOString(),
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return out;
|
|
489
|
+
}
|
|
490
|
+
// ─── Additional detectors (Test 4 fixes) ───────────────────────────────────
|
|
491
|
+
// Detect async functions whose body contains no try/catch
|
|
492
|
+
function detectAsyncWithoutTryCatch(filePath, lines) {
|
|
493
|
+
const ext = path.extname(filePath);
|
|
494
|
+
if (!JS_EXT.has(ext))
|
|
495
|
+
return [];
|
|
496
|
+
const out = [];
|
|
497
|
+
for (let i = 0; i < lines.length; i++) {
|
|
498
|
+
const l = lineAt(lines, i);
|
|
499
|
+
// Match declarations: "async function name(" or "async ("
|
|
500
|
+
if (!/^\s*(export\s+)?(async\s+function\b|async\s+[\w$]+\s*[(=]|async\s*\()/i.test(l))
|
|
501
|
+
continue;
|
|
502
|
+
// Find the body by brace matching from the end of the line
|
|
503
|
+
const openIdx = l.indexOf('{');
|
|
504
|
+
if (openIdx < 0) {
|
|
505
|
+
// Body may open on next line; defer detection by 1 line
|
|
506
|
+
if (i + 1 < lines.length && lines[i + 1].includes('{')) {
|
|
507
|
+
// continue below using i+1 as body start
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
// Find closing brace by tracking depth from this line
|
|
514
|
+
let depth = 0;
|
|
515
|
+
let sawTry = false;
|
|
516
|
+
let sawReturnAwait = false;
|
|
517
|
+
let lastBraceLine = i;
|
|
518
|
+
for (let j = i; j < lines.length && j < i + 60; j++) {
|
|
519
|
+
const line = lines[j];
|
|
520
|
+
for (let c = 0; c < line.length; c++) {
|
|
521
|
+
if (line[c] === '{')
|
|
522
|
+
depth++;
|
|
523
|
+
else if (line[c] === '}') {
|
|
524
|
+
depth--;
|
|
525
|
+
if (depth === 0) {
|
|
526
|
+
lastBraceLine = j;
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if (/\btry\s*\{/.test(line))
|
|
532
|
+
sawTry = true;
|
|
533
|
+
if (/\bawait\s+/.test(line))
|
|
534
|
+
sawReturnAwait = true;
|
|
535
|
+
if (depth === 0 && j > i) {
|
|
536
|
+
lastBraceLine = j;
|
|
537
|
+
break;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
if (depth !== 0)
|
|
541
|
+
continue; // malformed, skip
|
|
542
|
+
if (sawReturnAwait && !sawTry) {
|
|
543
|
+
out.push({
|
|
544
|
+
id: makeId(), category: 'error_handling', severity: 'high',
|
|
545
|
+
message: 'Async function awaits a value but has no try/catch — unhandled rejection risk',
|
|
546
|
+
filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
|
|
547
|
+
recommendation: 'Wrap awaits in try/catch or add .catch() on the promise',
|
|
548
|
+
detectedAt: new Date().toISOString(),
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return out;
|
|
553
|
+
}
|
|
554
|
+
// Detect chained property access after await without null check (e.g. result.data.items)
|
|
555
|
+
function detectChainedPropertyAccess(filePath, lines) {
|
|
556
|
+
const ext = path.extname(filePath);
|
|
557
|
+
if (!JS_EXT.has(ext))
|
|
558
|
+
return [];
|
|
559
|
+
const out = [];
|
|
560
|
+
for (let i = 0; i < lines.length; i++) {
|
|
561
|
+
const l = lineAt(lines, i);
|
|
562
|
+
// Match patterns like: return result.data.items; or const x = foo.bar.baz;
|
|
563
|
+
const m = l.match(/(?:return\s+|=\s*|\(\s*)([a-zA-Z_$][\w$]*)\s*\.\s*([a-zA-Z_$][\w$]*)\s*\.\s*([a-zA-Z_$][\w$]*)/);
|
|
564
|
+
if (!m)
|
|
565
|
+
continue;
|
|
566
|
+
const root = m[1];
|
|
567
|
+
// Look back 20 lines for: const root = await ... or similar source
|
|
568
|
+
let awaitedRoot = false;
|
|
569
|
+
for (let j = Math.max(0, i - 20); j < i; j++) {
|
|
570
|
+
const back = lines[j];
|
|
571
|
+
if (new RegExp(`\\b(const|let|var)\\s+${root}\\s*=\\s*await\\b`).test(back)) {
|
|
572
|
+
awaitedRoot = true;
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
if (!awaitedRoot)
|
|
577
|
+
continue;
|
|
578
|
+
// Look at the SAME line and the few lines after for a null guard on root
|
|
579
|
+
let guarded = false;
|
|
580
|
+
for (let j = Math.max(0, i - 3); j < i + 5 && j < lines.length; j++) {
|
|
581
|
+
if (new RegExp(`\\b${root}\\s*\\?\\.|\\bif\\s*\\(\\s*!?\\s*${root}\\b|\\b${root}\\s*&&|\\b${root}\\s*\\|\\|`).test(lines[j])) {
|
|
582
|
+
guarded = true;
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
if (!guarded) {
|
|
587
|
+
out.push({
|
|
588
|
+
id: makeId(), category: 'null_safety', severity: 'high',
|
|
589
|
+
message: `Chained access on awaited value '${root}.${m[2]}.${m[3]}' without null check — undefined on partial response`,
|
|
590
|
+
filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
|
|
591
|
+
recommendation: `Use optional chaining: ${root}?.${m[2]}?.${m[3]}, or guard with if (!${root}) return`,
|
|
592
|
+
detectedAt: new Date().toISOString(),
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return out;
|
|
597
|
+
}
|
|
598
|
+
// ─── Scanner ──────────────────────────────────────────────────────────────
|
|
599
|
+
const ALL_DETECTORS = [
|
|
600
|
+
detectPromisesWithoutCatch,
|
|
601
|
+
detectMissingReactErrorBoundaries,
|
|
602
|
+
detectApiCallsWithoutErrorState,
|
|
603
|
+
detectDbOperationsWithoutRollback,
|
|
604
|
+
detectFileOpsWithoutErrorHandling,
|
|
605
|
+
detectApiWithoutTimeout,
|
|
606
|
+
detectAwaitWithoutNullCheck,
|
|
607
|
+
detectArrayAccessWithoutLengthCheck,
|
|
608
|
+
detectProcessEnvWithoutFallback,
|
|
609
|
+
detectN1QueryPattern,
|
|
610
|
+
detectSyncOpsInAsyncCode,
|
|
611
|
+
detectConsoleLogInProduction,
|
|
612
|
+
detectAnyUsage,
|
|
613
|
+
detectNonNullAssertionOveruse,
|
|
614
|
+
detectMissingPagination,
|
|
615
|
+
detectAsyncWithoutTryCatch,
|
|
616
|
+
detectChainedPropertyAccess,
|
|
617
|
+
];
|
|
618
|
+
function scanFileForBlindSpots(filePath) {
|
|
619
|
+
if (!shouldScan(filePath))
|
|
620
|
+
return [];
|
|
621
|
+
const ext = path.extname(filePath);
|
|
622
|
+
if (!JS_EXT.has(ext))
|
|
623
|
+
return [];
|
|
624
|
+
const lines = readLines(filePath);
|
|
625
|
+
if (lines.length === 0)
|
|
626
|
+
return [];
|
|
627
|
+
const out = [];
|
|
628
|
+
for (const detector of ALL_DETECTORS) {
|
|
629
|
+
try {
|
|
630
|
+
out.push(...detector(filePath, lines));
|
|
631
|
+
}
|
|
632
|
+
catch { /* skip on parse error */ }
|
|
633
|
+
}
|
|
634
|
+
// Cap to top 25 issues per file (highest severity first) to avoid noise
|
|
635
|
+
const sevRank = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
636
|
+
return out.sort((a, b) => sevRank[a.severity] - sevRank[b.severity]).slice(0, 25);
|
|
637
|
+
}
|
|
638
|
+
function scanDirectoryForBlindSpots(dir, maxFiles = 500) {
|
|
639
|
+
const results = [];
|
|
640
|
+
const queue = [dir];
|
|
641
|
+
let scanned = 0;
|
|
642
|
+
while (queue.length > 0 && scanned < maxFiles) {
|
|
643
|
+
const current = queue.shift();
|
|
644
|
+
let entries;
|
|
645
|
+
try {
|
|
646
|
+
entries = fs.readdirSync(current, { withFileTypes: true });
|
|
647
|
+
}
|
|
648
|
+
catch {
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
for (const entry of entries) {
|
|
652
|
+
if (IGNORE_DIRS.has(entry.name) || entry.name.startsWith('.'))
|
|
653
|
+
continue;
|
|
654
|
+
const full = path.join(current, entry.name);
|
|
655
|
+
if (entry.isDirectory()) {
|
|
656
|
+
queue.push(full);
|
|
657
|
+
}
|
|
658
|
+
else if (entry.isFile()) {
|
|
659
|
+
const ext = path.extname(entry.name);
|
|
660
|
+
if (JS_EXT.has(ext)) {
|
|
661
|
+
results.push(...scanFileForBlindSpots(full));
|
|
662
|
+
scanned++;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return results;
|
|
668
|
+
}
|
|
669
|
+
function summarizeBlindSpots(spots) {
|
|
670
|
+
const summary = {
|
|
671
|
+
total: spots.length, critical: 0, high: 0, medium: 0, low: 0,
|
|
672
|
+
byCategory: {}, byFile: {},
|
|
673
|
+
};
|
|
674
|
+
for (const s of spots) {
|
|
675
|
+
summary[s.severity]++;
|
|
676
|
+
summary.byCategory[s.category] = (summary.byCategory[s.category] || 0) + 1;
|
|
677
|
+
summary.byFile[s.filePath] = (summary.byFile[s.filePath] || 0) + 1;
|
|
678
|
+
}
|
|
679
|
+
return summary;
|
|
680
|
+
}
|
|
681
|
+
function formatBlindSpotReport(spots) {
|
|
682
|
+
if (spots.length === 0)
|
|
683
|
+
return '✅ No blind spots detected.\n';
|
|
684
|
+
const summary = summarizeBlindSpots(spots);
|
|
685
|
+
const lines = [];
|
|
686
|
+
lines.push('⚠️ BLIND SPOTS DETECTED\n');
|
|
687
|
+
const bySev = ['critical', 'high', 'medium', 'low'];
|
|
688
|
+
const icons = { critical: '🔴', high: '🟡', medium: '🟠', low: '⚪' };
|
|
689
|
+
const labels = { critical: 'CRITICAL', high: 'HIGH', medium: 'MEDIUM', low: 'LOW' };
|
|
690
|
+
for (const sev of bySev) {
|
|
691
|
+
const items = spots.filter(s => s.severity === sev);
|
|
692
|
+
if (items.length === 0)
|
|
693
|
+
continue;
|
|
694
|
+
lines.push(`${icons[sev]} ${labels[sev]} (${items.length}):`);
|
|
695
|
+
for (const s of items.slice(0, 5)) {
|
|
696
|
+
lines.push(` → ${s.message}`);
|
|
697
|
+
lines.push(` ${s.filePath}:${s.lineNumber}`);
|
|
698
|
+
}
|
|
699
|
+
if (items.length > 5)
|
|
700
|
+
lines.push(` ... ${items.length - 5} more`);
|
|
701
|
+
lines.push('');
|
|
702
|
+
}
|
|
703
|
+
lines.push(`Total: ${summary.total} (🔴 ${summary.critical} | 🟡 ${summary.high} | 🟠 ${summary.medium} | ⚪ ${summary.low})`);
|
|
704
|
+
return lines.join('\n');
|
|
705
|
+
}
|
|
706
|
+
function getCategoryCounts(dir) {
|
|
707
|
+
const spots = scanDirectoryForBlindSpots(dir, 200);
|
|
708
|
+
return summarizeBlindSpots(spots).byCategory;
|
|
709
|
+
}
|
|
710
|
+
//# sourceMappingURL=enhanced-blind-spots.js.map
|