claude-flow-novice 2.18.25 → 2.18.27
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/config/hooks/post-edit-pipeline.js +1173 -634
- package/package.json +1 -1
|
@@ -1,20 +1,60 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Enhanced Post-Edit Pipeline - Comprehensive Validation Hook
|
|
4
|
-
* Validates edited files with TypeScript, ESLint, Prettier, Security Analysis, and Code Metrics
|
|
4
|
+
* Validates edited files with TypeScript, Rust, ESLint, Prettier, Security Analysis, and Code Metrics
|
|
5
5
|
*
|
|
6
6
|
* Features:
|
|
7
7
|
* - TypeScript validation with error categorization
|
|
8
|
+
* - Rust validation via cargo check
|
|
8
9
|
* - ESLint integration for code quality
|
|
9
10
|
* - Prettier formatting checks
|
|
10
|
-
* - Security analysis (
|
|
11
|
+
* - Security analysis (eval, password logging, XSS detection)
|
|
11
12
|
* - Code metrics (lines, functions, classes, complexity)
|
|
12
13
|
* - Actionable recommendations engine
|
|
13
14
|
*
|
|
14
15
|
* Usage: node config/hooks/post-edit-pipeline.js <file_path> [--memory-key <key>] [--agent-id <id>]
|
|
16
|
+
*
|
|
17
|
+
* ============================================================================
|
|
18
|
+
* ERROR DETECTION - WHAT'S CAUGHT vs NOT CAUGHT
|
|
19
|
+
* ============================================================================
|
|
20
|
+
*
|
|
21
|
+
* TYPESCRIPT: Runs `npx tsc --noEmit --skipLibCheck ${filePath}`
|
|
22
|
+
* RUST: Runs `cargo check` in the crate directory
|
|
23
|
+
*
|
|
24
|
+
* Both check the EDITED FILE + its IMPORTS (transitively).
|
|
25
|
+
* Rust's cargo check also catches cross-file breaks (better than TS).
|
|
26
|
+
*
|
|
27
|
+
* ✅ CAUGHT (Blocks Edit):
|
|
28
|
+
* ┌────────────────────┬──────────────────┬─────────────────────────────────────┐
|
|
29
|
+
* │ Error Type │ TS / Rust Code │ Example │
|
|
30
|
+
* ├────────────────────┼──────────────────┼─────────────────────────────────────┤
|
|
31
|
+
* │ Missing module │ TS2307 / E0432 │ import from './nonexistent' │
|
|
32
|
+
* │ Missing export │ TS2305 / E0433 │ use crate::nonexistent │
|
|
33
|
+
* │ Syntax errors │ TS1005 / E0xxx │ Missing ), unexpected token │
|
|
34
|
+
* │ Unused imports │ TS6133 / warn │ unused import │
|
|
35
|
+
* │ Type mismatches │ TS2322 / E0308 │ Wrong type assignment │
|
|
36
|
+
* │ Missing properties │ TS2339 / E0609 │ field does not exist │
|
|
37
|
+
* │ Borrow errors │ N/A / E0502 │ cannot borrow as mutable │
|
|
38
|
+
* │ Lifetime errors │ N/A / E0106 │ missing lifetime specifier │
|
|
39
|
+
* └────────────────────┴──────────────────┴─────────────────────────────────────┘
|
|
40
|
+
*
|
|
41
|
+
* ❌ NOT CAUGHT (TypeScript Single-File Limitation):
|
|
42
|
+
* ┌─────────────────────────────────┬────────────────────────────────────────────┐
|
|
43
|
+
* │ Scenario │ Why │
|
|
44
|
+
* ├─────────────────────────────────┼────────────────────────────────────────────┤
|
|
45
|
+
* │ Cross-file type changes (TS) │ Editing types.ts won't catch breaks in │
|
|
46
|
+
* │ │ other.ts that imports it │
|
|
47
|
+
* │ Deleted exports used elsewhere │ Consumers aren't checked until edited │
|
|
48
|
+
* └─────────────────────────────────┴────────────────────────────────────────────┘
|
|
49
|
+
*
|
|
50
|
+
* Note: Rust DOES catch cross-file breaks because cargo check compiles the
|
|
51
|
+
* entire crate. TypeScript only checks the single file + its imports.
|
|
52
|
+
*
|
|
53
|
+
* To catch everything in TS: Run `npx tsc --noEmit` project-wide periodically.
|
|
54
|
+
* ============================================================================
|
|
15
55
|
*/
|
|
16
56
|
|
|
17
|
-
import {
|
|
57
|
+
import { execSync } from 'child_process';
|
|
18
58
|
import { existsSync, readFileSync, appendFileSync, mkdirSync } from 'fs';
|
|
19
59
|
import { dirname, extname, resolve } from 'path';
|
|
20
60
|
|
|
@@ -63,11 +103,11 @@ if (!existsSync(filePath)) {
|
|
|
63
103
|
// Read file content for analysis
|
|
64
104
|
const fileContent = readFileSync(filePath, 'utf-8');
|
|
65
105
|
const ext = extname(filePath);
|
|
66
|
-
const baseName = filePath.replace(ext, '').split('/').pop();
|
|
67
106
|
|
|
68
107
|
// Initialize results object
|
|
69
108
|
const results = {
|
|
70
109
|
typescript: null,
|
|
110
|
+
rust: null,
|
|
71
111
|
eslint: null,
|
|
72
112
|
prettier: null,
|
|
73
113
|
security: null,
|
|
@@ -75,784 +115,1283 @@ const results = {
|
|
|
75
115
|
recommendations: []
|
|
76
116
|
};
|
|
77
117
|
|
|
78
|
-
// [Remaining TypeScript, ESLint, and Prettier validation code remains the same]
|
|
79
|
-
|
|
80
118
|
// ============================================================================
|
|
81
|
-
// PHASE
|
|
119
|
+
// PHASE 1: TypeScript Validation
|
|
82
120
|
// ============================================================================
|
|
83
121
|
|
|
84
|
-
|
|
122
|
+
if (['.ts', '.tsx'].includes(ext)) {
|
|
123
|
+
try {
|
|
124
|
+
log('VALIDATING', 'Running TypeScript validation');
|
|
85
125
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
'.claude/skills/hook-pipeline/security-scanner.sh',
|
|
90
|
-
filePath
|
|
91
|
-
], {
|
|
92
|
-
encoding: 'utf-8',
|
|
93
|
-
timeout: 10000
|
|
94
|
-
});
|
|
126
|
+
// Use tsc to check only this file
|
|
127
|
+
const cmd = `npx tsc --noEmit --skipLibCheck ${filePath}`;
|
|
128
|
+
execSync(cmd, { stdio: 'pipe', encoding: 'utf-8' });
|
|
95
129
|
|
|
96
|
-
|
|
97
|
-
|
|
130
|
+
results.typescript = {
|
|
131
|
+
passed: true,
|
|
132
|
+
errors: []
|
|
133
|
+
};
|
|
134
|
+
log('SUCCESS', 'TypeScript validation passed');
|
|
98
135
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
136
|
+
} catch (error) {
|
|
137
|
+
// Parse TypeScript errors
|
|
138
|
+
const output = error.stdout || error.stderr || '';
|
|
139
|
+
const lines = output.split('\n').filter(line => line.includes('error TS'));
|
|
140
|
+
|
|
141
|
+
if (lines.length === 0) {
|
|
142
|
+
results.typescript = {
|
|
143
|
+
passed: true,
|
|
144
|
+
errors: []
|
|
145
|
+
};
|
|
146
|
+
log('SUCCESS', 'No TypeScript errors detected');
|
|
147
|
+
} else {
|
|
148
|
+
// Categorize errors
|
|
149
|
+
const errorTypes = {
|
|
150
|
+
implicitAny: lines.filter(l => l.includes('TS7006') || l.includes('TS7031')).length,
|
|
151
|
+
propertyMissing: lines.filter(l => l.includes('TS2339')).length,
|
|
152
|
+
typeMismatch: lines.filter(l => l.includes('TS2322') || l.includes('TS2345')).length,
|
|
153
|
+
syntaxError: lines.filter(l => l.includes('TS1005') || l.includes('TS1128')).length,
|
|
154
|
+
other: 0
|
|
155
|
+
};
|
|
156
|
+
errorTypes.other = lines.length - Object.values(errorTypes).reduce((a, b) => a + b, 0);
|
|
104
157
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
results.security = {
|
|
109
|
-
passed: securityScanResults.passed,
|
|
110
|
-
confidence: securityScanResults.confidence || 0,
|
|
111
|
-
issues: Array.isArray(securityScanResults.vulnerabilities)
|
|
112
|
-
? securityScanResults.vulnerabilities
|
|
113
|
-
: JSON.parse(securityScanResults.vulnerabilities || '[]'),
|
|
114
|
-
details: securityScanOutput
|
|
115
|
-
};
|
|
158
|
+
const severity = errorTypes.syntaxError > 0 ? 'SYNTAX_ERROR' :
|
|
159
|
+
lines.length > 5 ? 'LINT_ISSUES' : 'TYPE_WARNING';
|
|
116
160
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
161
|
+
results.typescript = {
|
|
162
|
+
passed: false,
|
|
163
|
+
errorCount: lines.length,
|
|
164
|
+
errorTypes,
|
|
165
|
+
errors: lines.slice(0, 5),
|
|
166
|
+
severity
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
log(severity, `TypeScript errors detected: ${lines.length}`, {
|
|
170
|
+
errorCount: lines.length,
|
|
171
|
+
errorTypes,
|
|
172
|
+
errors: lines.slice(0, 5)
|
|
121
173
|
});
|
|
122
174
|
|
|
123
|
-
//
|
|
124
|
-
|
|
175
|
+
// Add recommendations based on error types
|
|
176
|
+
if (errorTypes.syntaxError > 0) {
|
|
125
177
|
results.recommendations.push({
|
|
126
|
-
type: '
|
|
178
|
+
type: 'typescript',
|
|
127
179
|
priority: 'critical',
|
|
128
|
-
message:
|
|
129
|
-
action:
|
|
180
|
+
message: 'Fix syntax errors before proceeding',
|
|
181
|
+
action: 'Review and fix TypeScript syntax issues'
|
|
130
182
|
});
|
|
131
|
-
})
|
|
183
|
+
} else if (errorTypes.implicitAny > 0) {
|
|
184
|
+
results.recommendations.push({
|
|
185
|
+
type: 'typescript',
|
|
186
|
+
priority: 'high',
|
|
187
|
+
message: 'Add explicit type annotations',
|
|
188
|
+
action: 'Add type annotations for parameters and return values'
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
log('SKIPPED', 'TypeScript validation skipped for non-TypeScript file');
|
|
195
|
+
}
|
|
132
196
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
197
|
+
// ============================================================================
|
|
198
|
+
// PHASE 1.5: Rust Validation (via cargo check)
|
|
199
|
+
// ============================================================================
|
|
200
|
+
|
|
201
|
+
if (ext === '.rs') {
|
|
202
|
+
try {
|
|
203
|
+
log('VALIDATING', 'Running Rust validation via cargo check');
|
|
204
|
+
|
|
205
|
+
// Find the Cargo.toml directory (walk up from file)
|
|
206
|
+
let cargoDir = dirname(resolve(filePath));
|
|
207
|
+
let foundCargo = false;
|
|
208
|
+
for (let i = 0; i < 10; i++) {
|
|
209
|
+
if (existsSync(`${cargoDir}/Cargo.toml`)) {
|
|
210
|
+
foundCargo = true;
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
const parent = dirname(cargoDir);
|
|
214
|
+
if (parent === cargoDir) break;
|
|
215
|
+
cargoDir = parent;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (!foundCargo) {
|
|
219
|
+
log('SKIPPED', 'No Cargo.toml found, skipping Rust validation');
|
|
220
|
+
results.rust = { passed: true, skipped: true, reason: 'No Cargo.toml found' };
|
|
140
221
|
} else {
|
|
141
|
-
|
|
222
|
+
// Run cargo check in the crate directory
|
|
223
|
+
const cmd = `cd "${cargoDir}" && cargo check --message-format=short 2>&1`;
|
|
224
|
+
execSync(cmd, { stdio: 'pipe', encoding: 'utf-8', timeout: 60000 });
|
|
225
|
+
|
|
226
|
+
results.rust = {
|
|
227
|
+
passed: true,
|
|
228
|
+
errors: []
|
|
229
|
+
};
|
|
230
|
+
log('SUCCESS', 'Rust validation passed');
|
|
142
231
|
}
|
|
143
|
-
} catch (parseError) {
|
|
144
|
-
log('ERROR', 'Failed to parse security scanner output', {
|
|
145
|
-
parseError: parseError.message,
|
|
146
|
-
output: securityScanOutput
|
|
147
|
-
});
|
|
148
232
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
233
|
+
} catch (error) {
|
|
234
|
+
// Parse Rust errors
|
|
235
|
+
const output = error.stdout || error.stderr || '';
|
|
236
|
+
const lines = output.split('\n').filter(line => line.includes('error[E') || line.includes('error:'));
|
|
237
|
+
|
|
238
|
+
if (lines.length === 0) {
|
|
239
|
+
results.rust = {
|
|
240
|
+
passed: true,
|
|
241
|
+
errors: []
|
|
242
|
+
};
|
|
243
|
+
log('SUCCESS', 'No Rust errors detected');
|
|
244
|
+
} else {
|
|
245
|
+
// Categorize Rust errors
|
|
246
|
+
const errorTypes = {
|
|
247
|
+
borrowCheck: lines.filter(l => l.includes('E0502') || l.includes('E0499') || l.includes('E0503')).length,
|
|
248
|
+
typeError: lines.filter(l => l.includes('E0308') || l.includes('E0277')).length,
|
|
249
|
+
missingImport: lines.filter(l => l.includes('E0432') || l.includes('E0433')).length,
|
|
250
|
+
lifetime: lines.filter(l => l.includes('E0106') || l.includes('E0495')).length,
|
|
251
|
+
syntaxError: lines.filter(l => l.includes('error:') && !l.includes('error[E')).length,
|
|
252
|
+
other: 0
|
|
253
|
+
};
|
|
254
|
+
errorTypes.other = lines.length - Object.values(errorTypes).reduce((a, b) => a + b, 0);
|
|
255
|
+
|
|
256
|
+
const severity = errorTypes.syntaxError > 0 ? 'SYNTAX_ERROR' :
|
|
257
|
+
errorTypes.borrowCheck > 0 ? 'BORROW_ERROR' :
|
|
258
|
+
lines.length > 5 ? 'RUST_ERRORS' : 'RUST_WARNING';
|
|
259
|
+
|
|
260
|
+
results.rust = {
|
|
261
|
+
passed: false,
|
|
262
|
+
errorCount: lines.length,
|
|
263
|
+
errorTypes,
|
|
264
|
+
errors: lines.slice(0, 5),
|
|
265
|
+
severity
|
|
266
|
+
};
|
|
181
267
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
268
|
+
log(severity, `Rust errors detected: ${lines.length}`, {
|
|
269
|
+
errorCount: lines.length,
|
|
270
|
+
errorTypes,
|
|
271
|
+
errors: lines.slice(0, 5)
|
|
185
272
|
});
|
|
186
273
|
|
|
187
|
-
|
|
274
|
+
// Add recommendations based on error types
|
|
275
|
+
if (errorTypes.borrowCheck > 0) {
|
|
276
|
+
results.recommendations.push({
|
|
277
|
+
type: 'rust',
|
|
278
|
+
priority: 'critical',
|
|
279
|
+
message: 'Fix borrow checker errors',
|
|
280
|
+
action: 'Review ownership and borrowing rules'
|
|
281
|
+
});
|
|
282
|
+
} else if (errorTypes.lifetime > 0) {
|
|
188
283
|
results.recommendations.push({
|
|
189
|
-
type: '
|
|
190
|
-
priority:
|
|
191
|
-
message:
|
|
192
|
-
action:
|
|
284
|
+
type: 'rust',
|
|
285
|
+
priority: 'high',
|
|
286
|
+
message: 'Fix lifetime annotations',
|
|
287
|
+
action: 'Add or correct lifetime parameters'
|
|
193
288
|
});
|
|
194
|
-
})
|
|
289
|
+
} else if (errorTypes.typeError > 0) {
|
|
290
|
+
results.recommendations.push({
|
|
291
|
+
type: 'rust',
|
|
292
|
+
priority: 'high',
|
|
293
|
+
message: 'Fix type mismatches',
|
|
294
|
+
action: 'Check expected vs actual types'
|
|
295
|
+
});
|
|
296
|
+
}
|
|
195
297
|
}
|
|
196
298
|
}
|
|
197
|
-
}
|
|
198
|
-
log('
|
|
199
|
-
error: error.message,
|
|
200
|
-
stack: error.stack
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
results.security = {
|
|
204
|
-
passed: false,
|
|
205
|
-
confidence: 0,
|
|
206
|
-
issues: [],
|
|
207
|
-
details: 'Complete security scanning failure'
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
results.recommendations.push({
|
|
211
|
-
type: 'security',
|
|
212
|
-
priority: 'critical',
|
|
213
|
-
message: 'Security scanning infrastructure failure',
|
|
214
|
-
action: 'Verify security scanning script and dependencies'
|
|
215
|
-
});
|
|
299
|
+
} else {
|
|
300
|
+
log('SKIPPED', 'Rust validation skipped for non-Rust file');
|
|
216
301
|
}
|
|
217
302
|
|
|
218
303
|
// ============================================================================
|
|
219
|
-
// PHASE
|
|
304
|
+
// PHASE 1: ESLint Integration
|
|
220
305
|
// ============================================================================
|
|
221
306
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
'bash-dependency-checker.sh',
|
|
234
|
-
'enforce-lf.sh'
|
|
235
|
-
],
|
|
236
|
-
'.py': [
|
|
237
|
-
'python-subprocess-safety.py',
|
|
238
|
-
'python-async-safety.py',
|
|
239
|
-
'python-import-checker.py',
|
|
240
|
-
'enforce-lf.sh'
|
|
241
|
-
],
|
|
242
|
-
'.js': [
|
|
243
|
-
'js-promise-safety.sh',
|
|
244
|
-
'enforce-lf.sh'
|
|
245
|
-
],
|
|
246
|
-
'.ts': [
|
|
247
|
-
'js-promise-safety.sh',
|
|
248
|
-
'enforce-lf.sh'
|
|
249
|
-
],
|
|
250
|
-
'.jsx': [
|
|
251
|
-
'js-promise-safety.sh',
|
|
252
|
-
'enforce-lf.sh'
|
|
253
|
-
],
|
|
254
|
-
'.tsx': [
|
|
255
|
-
'js-promise-safety.sh',
|
|
256
|
-
'enforce-lf.sh'
|
|
257
|
-
],
|
|
258
|
-
'.rs': [
|
|
259
|
-
'rust-command-safety.sh',
|
|
260
|
-
'rust-future-safety.sh',
|
|
261
|
-
'rust-dependency-checker.sh',
|
|
262
|
-
'enforce-lf.sh'
|
|
263
|
-
]
|
|
264
|
-
};
|
|
307
|
+
if (['.js', '.jsx', '.ts', '.tsx'].includes(ext)) {
|
|
308
|
+
try {
|
|
309
|
+
log('VALIDATING', 'Running ESLint validation');
|
|
310
|
+
|
|
311
|
+
// Check if ESLint is available
|
|
312
|
+
try {
|
|
313
|
+
execSync('npx eslint --version', { stdio: 'ignore', timeout: 5000 });
|
|
314
|
+
} catch {
|
|
315
|
+
log('SKIPPED', 'ESLint not available');
|
|
316
|
+
results.eslint = { available: false };
|
|
317
|
+
}
|
|
265
318
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
319
|
+
if (results.eslint === null) {
|
|
320
|
+
// Run ESLint
|
|
321
|
+
const eslintCmd = `npx eslint "${filePath}" --format json`;
|
|
322
|
+
const eslintOutput = execSync(eslintCmd, {
|
|
323
|
+
stdio: 'pipe',
|
|
324
|
+
encoding: 'utf-8',
|
|
325
|
+
timeout: 10000
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
const eslintResults = JSON.parse(eslintOutput);
|
|
329
|
+
const fileResults = eslintResults[0] || { messages: [] };
|
|
330
|
+
|
|
331
|
+
results.eslint = {
|
|
332
|
+
available: true,
|
|
333
|
+
passed: fileResults.errorCount === 0,
|
|
334
|
+
errorCount: fileResults.errorCount || 0,
|
|
335
|
+
warningCount: fileResults.warningCount || 0,
|
|
336
|
+
messages: fileResults.messages.slice(0, 5)
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
if (fileResults.errorCount > 0) {
|
|
340
|
+
log('LINT_ISSUES', `ESLint found ${fileResults.errorCount} errors`, {
|
|
341
|
+
errorCount: fileResults.errorCount,
|
|
342
|
+
warningCount: fileResults.warningCount
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
results.recommendations.push({
|
|
346
|
+
type: 'eslint',
|
|
347
|
+
priority: 'high',
|
|
348
|
+
message: `Fix ${fileResults.errorCount} ESLint errors`,
|
|
349
|
+
action: `Run: npx eslint "${filePath}" --fix`
|
|
350
|
+
});
|
|
351
|
+
} else {
|
|
352
|
+
log('SUCCESS', 'ESLint validation passed');
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
} catch (error) {
|
|
356
|
+
// ESLint errors are expected, parse them
|
|
357
|
+
if (error.stdout) {
|
|
358
|
+
try {
|
|
359
|
+
const eslintResults = JSON.parse(error.stdout);
|
|
360
|
+
const fileResults = eslintResults[0] || { messages: [] };
|
|
361
|
+
|
|
362
|
+
results.eslint = {
|
|
363
|
+
available: true,
|
|
364
|
+
passed: fileResults.errorCount === 0,
|
|
365
|
+
errorCount: fileResults.errorCount || 0,
|
|
366
|
+
warningCount: fileResults.warningCount || 0,
|
|
367
|
+
messages: fileResults.messages.slice(0, 5)
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
if (fileResults.errorCount > 0) {
|
|
371
|
+
log('LINT_ISSUES', `ESLint found ${fileResults.errorCount} errors`, {
|
|
372
|
+
errorCount: fileResults.errorCount,
|
|
373
|
+
warningCount: fileResults.warningCount
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
results.recommendations.push({
|
|
377
|
+
type: 'eslint',
|
|
378
|
+
priority: 'high',
|
|
379
|
+
message: `Fix ${fileResults.errorCount} ESLint errors`,
|
|
380
|
+
action: `Run: npx eslint "${filePath}" --fix`
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
} catch {
|
|
384
|
+
log('ERROR', 'ESLint execution failed', { error: error.message });
|
|
385
|
+
results.eslint = { available: false, error: error.message };
|
|
386
|
+
}
|
|
387
|
+
} else {
|
|
388
|
+
log('ERROR', 'ESLint execution failed', { error: error.message });
|
|
389
|
+
results.eslint = { available: false, error: error.message };
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
269
393
|
|
|
270
|
-
|
|
394
|
+
// ============================================================================
|
|
395
|
+
// PHASE 1: Prettier Integration
|
|
396
|
+
// ============================================================================
|
|
271
397
|
|
|
398
|
+
if (['.js', '.jsx', '.ts', '.tsx', '.json', '.css', '.html'].includes(ext)) {
|
|
272
399
|
try {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
400
|
+
log('VALIDATING', 'Running Prettier formatting check');
|
|
401
|
+
|
|
402
|
+
// Check if Prettier is available
|
|
403
|
+
try {
|
|
404
|
+
execSync('npx prettier --version', { stdio: 'ignore', timeout: 5000 });
|
|
405
|
+
} catch {
|
|
406
|
+
log('SKIPPED', 'Prettier not available');
|
|
407
|
+
results.prettier = { available: false };
|
|
408
|
+
}
|
|
282
409
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
410
|
+
if (results.prettier === null) {
|
|
411
|
+
// Run Prettier check
|
|
412
|
+
try {
|
|
413
|
+
const prettierCmd = `npx prettier --check "${filePath}"`;
|
|
414
|
+
execSync(prettierCmd, {
|
|
415
|
+
stdio: 'pipe',
|
|
416
|
+
encoding: 'utf-8',
|
|
417
|
+
timeout: 10000
|
|
418
|
+
});
|
|
286
419
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
420
|
+
results.prettier = {
|
|
421
|
+
available: true,
|
|
422
|
+
passed: true,
|
|
423
|
+
formatted: true
|
|
424
|
+
};
|
|
425
|
+
log('SUCCESS', 'Prettier formatting check passed');
|
|
426
|
+
|
|
427
|
+
} catch (error) {
|
|
428
|
+
results.prettier = {
|
|
429
|
+
available: true,
|
|
430
|
+
passed: false,
|
|
431
|
+
formatted: false,
|
|
432
|
+
needsFormatting: true
|
|
433
|
+
};
|
|
434
|
+
log('LINT_ISSUES', 'File needs Prettier formatting');
|
|
292
435
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
isBlocking: exitCode === 1,
|
|
302
|
-
isWarning: exitCode === 2,
|
|
303
|
-
message: stderr || stdout || 'Validator passed',
|
|
304
|
-
stdout,
|
|
305
|
-
stderr
|
|
306
|
-
};
|
|
436
|
+
results.recommendations.push({
|
|
437
|
+
type: 'prettier',
|
|
438
|
+
priority: 'medium',
|
|
439
|
+
message: 'File needs formatting',
|
|
440
|
+
action: `Run: npx prettier --write "${filePath}"`
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
}
|
|
307
444
|
} catch (error) {
|
|
308
|
-
log('ERROR',
|
|
309
|
-
|
|
310
|
-
stack: error.stack
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
return {
|
|
314
|
-
validator: validatorName,
|
|
315
|
-
exitCode: -1,
|
|
316
|
-
passed: false,
|
|
317
|
-
isBlocking: false,
|
|
318
|
-
isWarning: true,
|
|
319
|
-
message: `Validator execution failed: ${error.message}`,
|
|
320
|
-
error: error.message
|
|
321
|
-
};
|
|
445
|
+
log('ERROR', 'Prettier execution failed', { error: error.message });
|
|
446
|
+
results.prettier = { available: false, error: error.message };
|
|
322
447
|
}
|
|
323
448
|
}
|
|
324
449
|
|
|
325
|
-
//
|
|
326
|
-
|
|
450
|
+
// ============================================================================
|
|
451
|
+
// PHASE 2: Security Analysis
|
|
452
|
+
// ============================================================================
|
|
327
453
|
|
|
328
|
-
|
|
329
|
-
log('INFO', `Running ${applicableValidators.length} bash validators for ${ext} file`);
|
|
454
|
+
log('VALIDATING', 'Running security analysis');
|
|
330
455
|
|
|
331
|
-
|
|
332
|
-
const validatorResults = applicableValidators.map(validator =>
|
|
333
|
-
runValidator(validator, filePath)
|
|
334
|
-
);
|
|
456
|
+
const securityIssues = [];
|
|
335
457
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
458
|
+
// Check for eval() usage
|
|
459
|
+
if (fileContent.includes('eval(')) {
|
|
460
|
+
securityIssues.push({
|
|
461
|
+
type: 'security',
|
|
462
|
+
severity: 'critical',
|
|
463
|
+
message: 'Use of eval() function detected - security risk',
|
|
464
|
+
suggestion: 'Replace eval() with safer alternatives like JSON.parse() or Function constructor with proper validation'
|
|
465
|
+
});
|
|
466
|
+
}
|
|
343
467
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
message: result.message
|
|
354
|
-
});
|
|
468
|
+
// Check for new Function() usage
|
|
469
|
+
if (fileContent.includes('new Function(')) {
|
|
470
|
+
securityIssues.push({
|
|
471
|
+
type: 'security',
|
|
472
|
+
severity: 'critical',
|
|
473
|
+
message: 'Use of new Function() detected - security risk',
|
|
474
|
+
suggestion: 'Avoid dynamic code execution; use safer alternatives'
|
|
475
|
+
});
|
|
476
|
+
}
|
|
355
477
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
478
|
+
// Check for password logging
|
|
479
|
+
if (fileContent.includes('password') && (fileContent.includes('console.log') || fileContent.includes('console.debug'))) {
|
|
480
|
+
securityIssues.push({
|
|
481
|
+
type: 'security',
|
|
482
|
+
severity: 'critical',
|
|
483
|
+
message: 'Potential password logging detected',
|
|
484
|
+
suggestion: 'Remove password logging from code immediately'
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Check for XSS vulnerabilities (innerHTML with concatenation)
|
|
489
|
+
if (fileContent.includes('innerHTML') && fileContent.match(/innerHTML\s*[+]=|innerHTML\s*=\s*.*\+/)) {
|
|
490
|
+
securityIssues.push({
|
|
491
|
+
type: 'security',
|
|
492
|
+
severity: 'high',
|
|
493
|
+
message: 'Potential XSS vulnerability with innerHTML concatenation',
|
|
494
|
+
suggestion: 'Use textContent, createElement, or proper sanitization libraries'
|
|
366
495
|
});
|
|
496
|
+
}
|
|
367
497
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
498
|
+
// Check for hardcoded secrets (basic patterns)
|
|
499
|
+
const secretPatterns = [
|
|
500
|
+
/api[_-]?key\s*=\s*['"][^'"]{20,}['"]/i,
|
|
501
|
+
/secret\s*=\s*['"][^'"]{20,}['"]/i,
|
|
502
|
+
/token\s*=\s*['"][^'"]{20,}['"]/i,
|
|
503
|
+
/password\s*=\s*['"][^'"]+['"]/i
|
|
504
|
+
];
|
|
505
|
+
|
|
506
|
+
for (const pattern of secretPatterns) {
|
|
507
|
+
if (pattern.test(fileContent)) {
|
|
508
|
+
securityIssues.push({
|
|
509
|
+
type: 'security',
|
|
510
|
+
severity: 'critical',
|
|
511
|
+
message: 'Potential hardcoded secret detected',
|
|
512
|
+
suggestion: 'Move secrets to environment variables or secure configuration'
|
|
513
|
+
});
|
|
514
|
+
break; // Only report once
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
results.security = {
|
|
519
|
+
passed: securityIssues.length === 0,
|
|
520
|
+
issueCount: securityIssues.length,
|
|
521
|
+
issues: securityIssues
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
if (securityIssues.length > 0) {
|
|
525
|
+
log('SECURITY_ISSUES', `Security analysis found ${securityIssues.length} issues`, {
|
|
526
|
+
issueCount: securityIssues.length,
|
|
527
|
+
issues: securityIssues
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
results.recommendations.push({
|
|
531
|
+
type: 'security',
|
|
532
|
+
priority: 'critical',
|
|
533
|
+
message: `Address ${securityIssues.length} security ${securityIssues.length === 1 ? 'issue' : 'issues'} immediately`,
|
|
534
|
+
action: 'Review security recommendations and apply fixes'
|
|
382
535
|
});
|
|
383
536
|
} else {
|
|
384
|
-
log('
|
|
537
|
+
log('SUCCESS', 'No security issues detected');
|
|
385
538
|
}
|
|
386
539
|
|
|
387
540
|
// ============================================================================
|
|
388
|
-
// PHASE
|
|
541
|
+
// PHASE 3: Code Metrics
|
|
389
542
|
// ============================================================================
|
|
390
543
|
|
|
391
|
-
|
|
392
|
-
log('VALIDATING', 'Running SQL injection detection');
|
|
544
|
+
log('VALIDATING', 'Calculating code metrics');
|
|
393
545
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
546
|
+
const lines = fileContent.split('\n');
|
|
547
|
+
const lineCount = lines.length;
|
|
548
|
+
const functionCount = (fileContent.match(/function\s+\w+|const\s+\w+\s*=\s*\([^)]*\)\s*=>/g) || []).length;
|
|
549
|
+
const classCount = (fileContent.match(/class\s+\w+/g) || []).length;
|
|
550
|
+
const todoCount = (fileContent.match(/\/\/\s*TODO|\/\*\s*TODO/gi) || []).length;
|
|
551
|
+
const fixmeCount = (fileContent.match(/\/\/\s*FIXME|\/\*\s*FIXME/gi) || []).length;
|
|
552
|
+
|
|
553
|
+
// Calculate cyclomatic complexity (simplified)
|
|
554
|
+
let complexity = 'low';
|
|
555
|
+
if (lineCount > 300 || functionCount > 10) {
|
|
556
|
+
complexity = 'high';
|
|
557
|
+
} else if (lineCount > 150 || functionCount > 5) {
|
|
558
|
+
complexity = 'medium';
|
|
559
|
+
}
|
|
398
560
|
|
|
399
|
-
|
|
400
|
-
|
|
561
|
+
results.metrics = {
|
|
562
|
+
lines: lineCount,
|
|
563
|
+
functions: functionCount,
|
|
564
|
+
classes: classCount,
|
|
565
|
+
todos: todoCount,
|
|
566
|
+
fixmes: fixmeCount,
|
|
567
|
+
complexity
|
|
568
|
+
};
|
|
401
569
|
|
|
402
|
-
|
|
403
|
-
|
|
570
|
+
log('SUCCESS', 'Code metrics calculated', {
|
|
571
|
+
lines: lineCount,
|
|
572
|
+
functions: functionCount,
|
|
573
|
+
classes: classCount,
|
|
574
|
+
complexity
|
|
575
|
+
});
|
|
404
576
|
|
|
405
|
-
|
|
406
|
-
|
|
577
|
+
// ============================================================================
|
|
578
|
+
// PHASE 3: Recommendations Engine
|
|
579
|
+
// ============================================================================
|
|
407
580
|
|
|
408
|
-
|
|
409
|
-
{ pattern: /(?:req\.body|req\.query|req\.params|request\.form|request\.args)\.[^\s]+.*(?:SELECT|INSERT|UPDATE|DELETE)/i, risk: 'UNSANITIZED_INPUT', severity: 'critical' },
|
|
581
|
+
log('VALIDATING', 'Generating recommendations');
|
|
410
582
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
583
|
+
// Maintainability recommendations
|
|
584
|
+
if (lineCount > 200) {
|
|
585
|
+
results.recommendations.push({
|
|
586
|
+
type: 'maintainability',
|
|
587
|
+
priority: 'medium',
|
|
588
|
+
message: `File has ${lineCount} lines - consider breaking it down`,
|
|
589
|
+
action: 'Split into smaller, focused modules (150-200 lines per file)'
|
|
590
|
+
});
|
|
591
|
+
}
|
|
415
592
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
593
|
+
// Code quality recommendations
|
|
594
|
+
if (fileContent.includes('var ')) {
|
|
595
|
+
results.recommendations.push({
|
|
596
|
+
type: 'code-quality',
|
|
597
|
+
priority: 'low',
|
|
598
|
+
message: 'Use const or let instead of var',
|
|
599
|
+
action: 'Replace var declarations with const or let for better scoping'
|
|
600
|
+
});
|
|
601
|
+
}
|
|
419
602
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
603
|
+
if (fileContent.includes('==') && !fileContent.includes('===') && fileContent.includes('==') > fileContent.includes('===')) {
|
|
604
|
+
results.recommendations.push({
|
|
605
|
+
type: 'code-quality',
|
|
606
|
+
priority: 'medium',
|
|
607
|
+
message: 'Prefer strict equality (===) over loose equality (==)',
|
|
608
|
+
action: 'Replace == with === for type-safe comparisons'
|
|
609
|
+
});
|
|
610
|
+
}
|
|
424
611
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
612
|
+
// TypeScript-specific recommendations
|
|
613
|
+
if (['.ts', '.tsx'].includes(ext)) {
|
|
614
|
+
if (fileContent.includes(': any')) {
|
|
615
|
+
results.recommendations.push({
|
|
616
|
+
type: 'typescript',
|
|
617
|
+
priority: 'medium',
|
|
618
|
+
message: 'Avoid using "any" type when possible',
|
|
619
|
+
action: 'Use specific types or unknown for better type safety'
|
|
620
|
+
});
|
|
621
|
+
}
|
|
429
622
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
});
|
|
623
|
+
if (!fileContent.includes('interface') && !fileContent.includes('type ') && lineCount > 100) {
|
|
624
|
+
results.recommendations.push({
|
|
625
|
+
type: 'typescript',
|
|
626
|
+
priority: 'low',
|
|
627
|
+
message: 'Consider defining interfaces or types',
|
|
628
|
+
action: 'Add type definitions for better code structure and maintainability'
|
|
437
629
|
});
|
|
438
|
-
} else {
|
|
439
|
-
results.sqlInjection = { passed: true, issues: [] };
|
|
440
|
-
log('SUCCESS', 'No SQL injection risks detected');
|
|
441
630
|
}
|
|
442
631
|
}
|
|
443
632
|
|
|
444
|
-
//
|
|
445
|
-
|
|
446
|
-
|
|
633
|
+
// Documentation recommendations
|
|
634
|
+
if (fileContent.includes('export ') && !fileContent.includes('/**') && functionCount > 0) {
|
|
635
|
+
results.recommendations.push({
|
|
636
|
+
type: 'documentation',
|
|
637
|
+
priority: 'low',
|
|
638
|
+
message: 'Public exports could benefit from JSDoc comments',
|
|
639
|
+
action: 'Add JSDoc documentation for exported functions/classes'
|
|
640
|
+
});
|
|
641
|
+
}
|
|
447
642
|
|
|
448
|
-
|
|
643
|
+
// Testing recommendations
|
|
644
|
+
if (!filePath.includes('test') && !filePath.includes('spec') && (functionCount > 0 || classCount > 0)) {
|
|
645
|
+
results.recommendations.push({
|
|
646
|
+
type: 'testing',
|
|
647
|
+
priority: 'medium',
|
|
648
|
+
message: 'Consider writing tests for this module',
|
|
649
|
+
action: 'Create corresponding test file to ensure code reliability'
|
|
650
|
+
});
|
|
651
|
+
}
|
|
449
652
|
|
|
450
|
-
|
|
451
|
-
if (
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
}
|
|
460
|
-
if (ext === '.md' && !filePath.match(/^(README|CLAUDE)\.md$/)) {
|
|
461
|
-
suggestions.push({ location: `docs/${filePath}`, reason: 'Documentation belongs in docs/' });
|
|
462
|
-
}
|
|
463
|
-
if (ext === '.json' && !filePath.match(/^package\.json$/)) {
|
|
464
|
-
suggestions.push({ location: `config/${filePath}`, reason: 'Config files belong in config/' });
|
|
465
|
-
}
|
|
466
|
-
if (ext === '.sh') {
|
|
467
|
-
suggestions.push({ location: `scripts/${filePath}`, reason: 'Scripts belong in scripts/' });
|
|
468
|
-
}
|
|
653
|
+
// TODO/FIXME recommendations
|
|
654
|
+
if (todoCount > 0 || fixmeCount > 0) {
|
|
655
|
+
results.recommendations.push({
|
|
656
|
+
type: 'maintenance',
|
|
657
|
+
priority: 'low',
|
|
658
|
+
message: `Found ${todoCount} TODOs and ${fixmeCount} FIXMEs`,
|
|
659
|
+
action: 'Address pending tasks and technical debt'
|
|
660
|
+
});
|
|
661
|
+
}
|
|
469
662
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
suggestions
|
|
474
|
-
});
|
|
663
|
+
// Sort recommendations by priority
|
|
664
|
+
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
665
|
+
results.recommendations.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
|
|
475
666
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
priority: 'high',
|
|
479
|
-
message: `File "${filePath}" should not be in root directory`,
|
|
480
|
-
action: `Move to: ${suggestions[0].location}`,
|
|
481
|
-
suggestions
|
|
482
|
-
});
|
|
667
|
+
// Limit to top 10 recommendations
|
|
668
|
+
results.recommendations = results.recommendations.slice(0, 10);
|
|
483
669
|
|
|
484
|
-
|
|
485
|
-
results.rootWarning = { suggestions };
|
|
486
|
-
}
|
|
487
|
-
}
|
|
670
|
+
log('SUCCESS', `Generated ${results.recommendations.length} recommendations`);
|
|
488
671
|
|
|
489
672
|
// ============================================================================
|
|
490
|
-
//
|
|
673
|
+
// MULTI-LANGUAGE VALIDATION
|
|
491
674
|
// ============================================================================
|
|
492
675
|
|
|
493
|
-
|
|
494
|
-
|
|
676
|
+
// JavaScript Type Checking (JSDoc via TypeScript)
|
|
677
|
+
if (['.js', '.jsx', '.mjs', '.cjs'].includes(ext)) {
|
|
678
|
+
try {
|
|
679
|
+
log('VALIDATING', 'Running JavaScript type checking (JSDoc via TypeScript)');
|
|
680
|
+
|
|
681
|
+
// Check if TypeScript is available
|
|
682
|
+
try {
|
|
683
|
+
execSync('npx tsc --version', { stdio: 'ignore', timeout: 5000 });
|
|
684
|
+
} catch {
|
|
685
|
+
log('WARNING', 'TypeScript not available for JavaScript type checking');
|
|
686
|
+
results.javascript = { available: false };
|
|
687
|
+
results.recommendations.push({
|
|
688
|
+
type: 'dependency',
|
|
689
|
+
priority: 'medium',
|
|
690
|
+
message: 'TypeScript not installed - JavaScript type checking unavailable',
|
|
691
|
+
action: 'Install TypeScript: npm install -g typescript',
|
|
692
|
+
line: null
|
|
693
|
+
});
|
|
694
|
+
throw new Error('TypeScript not available');
|
|
695
|
+
}
|
|
495
696
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
697
|
+
// Use TypeScript with allowJs and checkJs to validate JSDoc
|
|
698
|
+
const tempConfig = JSON.stringify({
|
|
699
|
+
compilerOptions: {
|
|
700
|
+
allowJs: true,
|
|
701
|
+
checkJs: true,
|
|
702
|
+
noEmit: true,
|
|
703
|
+
skipLibCheck: true,
|
|
704
|
+
strict: false,
|
|
705
|
+
target: 'ES2020',
|
|
706
|
+
module: 'commonjs'
|
|
707
|
+
}
|
|
708
|
+
});
|
|
503
709
|
|
|
504
|
-
|
|
505
|
-
|
|
710
|
+
const configPath = '/tmp/tsconfig.jsdoc.json';
|
|
711
|
+
require('fs').writeFileSync(configPath, tempConfig);
|
|
506
712
|
|
|
507
|
-
|
|
508
|
-
|
|
713
|
+
const cmd = `npx tsc --noEmit --project ${configPath} ${filePath}`;
|
|
714
|
+
execSync(cmd, { stdio: 'pipe', encoding: 'utf-8', timeout: 10000 });
|
|
509
715
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
716
|
+
results.javascript = {
|
|
717
|
+
available: true,
|
|
718
|
+
passed: true,
|
|
719
|
+
errors: [],
|
|
720
|
+
hasJSDoc: fileContent.includes('/**') || fileContent.includes('@param') || fileContent.includes('@returns')
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
log('SUCCESS', 'JavaScript type checking passed');
|
|
515
724
|
|
|
725
|
+
if (!results.javascript.hasJSDoc) {
|
|
516
726
|
results.recommendations.push({
|
|
517
|
-
type: '
|
|
518
|
-
priority: '
|
|
519
|
-
message: 'No
|
|
520
|
-
action: '
|
|
727
|
+
type: 'javascript',
|
|
728
|
+
priority: 'low',
|
|
729
|
+
message: 'No JSDoc comments found in JavaScript file',
|
|
730
|
+
action: 'Add JSDoc type annotations for better type safety',
|
|
731
|
+
line: null
|
|
521
732
|
});
|
|
733
|
+
}
|
|
522
734
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
735
|
+
} catch (error) {
|
|
736
|
+
if (!results.javascript || results.javascript.available !== false) {
|
|
737
|
+
// Parse TypeScript/JSDoc errors
|
|
738
|
+
const output = error.stdout || error.stderr || '';
|
|
739
|
+
const lines = output.split('\n').filter(line => line.includes('error TS'));
|
|
740
|
+
|
|
741
|
+
const errors = lines.map(line => {
|
|
742
|
+
const match = line.match(/\((\d+),(\d+)\): error TS(\d+): (.+)/);
|
|
743
|
+
if (match) {
|
|
744
|
+
return {
|
|
745
|
+
line: parseInt(match[1]),
|
|
746
|
+
column: parseInt(match[2]),
|
|
747
|
+
code: match[3],
|
|
748
|
+
message: match[4]
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
return null;
|
|
752
|
+
}).filter(Boolean);
|
|
753
|
+
|
|
754
|
+
results.javascript = {
|
|
755
|
+
available: true,
|
|
756
|
+
passed: false,
|
|
757
|
+
errorCount: errors.length,
|
|
758
|
+
errors
|
|
527
759
|
};
|
|
760
|
+
|
|
761
|
+
log('FAILED', `JavaScript type checking failed (${errors.length} errors)`, { errors });
|
|
762
|
+
|
|
763
|
+
// Add recommendations
|
|
764
|
+
errors.slice(0, 5).forEach(err => {
|
|
765
|
+
results.recommendations.push({
|
|
766
|
+
type: 'javascript',
|
|
767
|
+
priority: 'medium',
|
|
768
|
+
message: `JSDoc type error: ${err.message}`,
|
|
769
|
+
action: `Fix type annotation at line ${err.line}`,
|
|
770
|
+
line: err.line
|
|
771
|
+
});
|
|
772
|
+
});
|
|
528
773
|
}
|
|
529
774
|
}
|
|
530
775
|
}
|
|
531
776
|
|
|
532
|
-
//
|
|
533
|
-
|
|
534
|
-
|
|
777
|
+
// Python Validation (pylint + black)
|
|
778
|
+
if (ext === '.py') {
|
|
779
|
+
try {
|
|
780
|
+
log('VALIDATING', 'Running Python validation (pylint + black)');
|
|
781
|
+
|
|
782
|
+
// Check for pylint
|
|
783
|
+
let pylintAvailable = false;
|
|
784
|
+
try {
|
|
785
|
+
execSync('pylint --version', { stdio: 'ignore', timeout: 5000 });
|
|
786
|
+
pylintAvailable = true;
|
|
787
|
+
} catch {
|
|
788
|
+
log('WARNING', 'pylint not available for Python validation');
|
|
789
|
+
}
|
|
535
790
|
|
|
536
|
-
|
|
791
|
+
// Check for black
|
|
792
|
+
let blackAvailable = false;
|
|
793
|
+
try {
|
|
794
|
+
execSync('black --version', { stdio: 'ignore', timeout: 5000 });
|
|
795
|
+
blackAvailable = true;
|
|
796
|
+
} catch {
|
|
797
|
+
log('WARNING', 'black not available for Python formatting check');
|
|
798
|
+
}
|
|
537
799
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
800
|
+
results.python = {
|
|
801
|
+
available: pylintAvailable || blackAvailable,
|
|
802
|
+
pylint: null,
|
|
803
|
+
black: null
|
|
804
|
+
};
|
|
543
805
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
806
|
+
// Add warning if no Python tools available
|
|
807
|
+
if (!pylintAvailable && !blackAvailable) {
|
|
808
|
+
results.recommendations.push({
|
|
809
|
+
type: 'dependency',
|
|
810
|
+
priority: 'medium',
|
|
811
|
+
message: 'Python validation tools not installed (pylint, black)',
|
|
812
|
+
action: 'Install Python tools: pip install pylint black',
|
|
813
|
+
line: null
|
|
814
|
+
});
|
|
815
|
+
}
|
|
552
816
|
|
|
553
|
-
|
|
817
|
+
// Run pylint if available
|
|
818
|
+
if (pylintAvailable) {
|
|
819
|
+
try {
|
|
820
|
+
const pylintCmd = `pylint "${filePath}" --output-format=json`;
|
|
821
|
+
const pylintOutput = execSync(pylintCmd, { encoding: 'utf-8', timeout: 10000 });
|
|
822
|
+
const pylintResults = JSON.parse(pylintOutput || '[]');
|
|
823
|
+
|
|
824
|
+
const errors = pylintResults.filter(r => r.type === 'error');
|
|
825
|
+
const warnings = pylintResults.filter(r => r.type === 'warning');
|
|
826
|
+
|
|
827
|
+
results.python.pylint = {
|
|
828
|
+
passed: errors.length === 0,
|
|
829
|
+
errorCount: errors.length,
|
|
830
|
+
warningCount: warnings.length,
|
|
831
|
+
issues: pylintResults
|
|
832
|
+
};
|
|
833
|
+
|
|
834
|
+
log('SUCCESS', `pylint validation complete (${errors.length} errors, ${warnings.length} warnings)`);
|
|
835
|
+
|
|
836
|
+
// Add critical error recommendations
|
|
837
|
+
errors.slice(0, 3).forEach(err => {
|
|
838
|
+
results.recommendations.push({
|
|
839
|
+
type: 'python',
|
|
840
|
+
priority: 'high',
|
|
841
|
+
message: `pylint error: ${err.message}`,
|
|
842
|
+
action: `Fix ${err.symbol} at line ${err.line}`,
|
|
843
|
+
line: err.line
|
|
844
|
+
});
|
|
845
|
+
});
|
|
554
846
|
|
|
555
|
-
|
|
556
|
-
//
|
|
557
|
-
|
|
847
|
+
} catch (error) {
|
|
848
|
+
// pylint may exit with non-zero for warnings
|
|
849
|
+
try {
|
|
850
|
+
const output = error.stdout || '[]';
|
|
851
|
+
const pylintResults = JSON.parse(output);
|
|
852
|
+
const errors = pylintResults.filter(r => r.type === 'error');
|
|
853
|
+
const warnings = pylintResults.filter(r => r.type === 'warning');
|
|
854
|
+
|
|
855
|
+
results.python.pylint = {
|
|
856
|
+
passed: errors.length === 0,
|
|
857
|
+
errorCount: errors.length,
|
|
858
|
+
warningCount: warnings.length,
|
|
859
|
+
issues: pylintResults
|
|
860
|
+
};
|
|
861
|
+
} catch {
|
|
862
|
+
results.python.pylint = { passed: false, error: 'Failed to parse pylint output' };
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// Run black if available
|
|
868
|
+
if (blackAvailable) {
|
|
869
|
+
try {
|
|
870
|
+
const blackCmd = `black --check "${filePath}"`;
|
|
871
|
+
execSync(blackCmd, { encoding: 'utf-8', timeout: 10000 });
|
|
872
|
+
|
|
873
|
+
results.python.black = {
|
|
874
|
+
passed: true,
|
|
875
|
+
formatted: true
|
|
876
|
+
};
|
|
558
877
|
|
|
559
|
-
log('
|
|
878
|
+
log('SUCCESS', 'black formatting check passed');
|
|
560
879
|
|
|
561
|
-
|
|
562
|
-
|
|
880
|
+
} catch (error) {
|
|
881
|
+
results.python.black = {
|
|
882
|
+
passed: false,
|
|
883
|
+
formatted: false
|
|
884
|
+
};
|
|
885
|
+
|
|
886
|
+
log('FAILED', 'File needs black formatting');
|
|
887
|
+
|
|
888
|
+
results.recommendations.push({
|
|
889
|
+
type: 'python',
|
|
890
|
+
priority: 'medium',
|
|
891
|
+
message: 'Python file needs formatting',
|
|
892
|
+
action: `Run: black "${filePath}"`,
|
|
893
|
+
line: null
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
} catch (error) {
|
|
899
|
+
log('ERROR', 'Python validation error', { error: error.message });
|
|
900
|
+
results.python = { available: false, error: error.message };
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// Rust Validation (cargo clippy + rustfmt)
|
|
905
|
+
if (ext === '.rs') {
|
|
563
906
|
try {
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
907
|
+
log('VALIDATING', 'Running Rust validation (cargo clippy + rustfmt)');
|
|
908
|
+
|
|
909
|
+
// Check for cargo
|
|
910
|
+
let cargoAvailable = false;
|
|
911
|
+
try {
|
|
912
|
+
execSync('cargo --version', { stdio: 'ignore', timeout: 5000 });
|
|
913
|
+
cargoAvailable = true;
|
|
914
|
+
} catch {
|
|
915
|
+
log('WARNING', 'cargo not available for Rust validation');
|
|
916
|
+
results.recommendations.push({
|
|
917
|
+
type: 'dependency',
|
|
918
|
+
priority: 'medium',
|
|
919
|
+
message: 'Rust toolchain not installed - cargo validation unavailable',
|
|
920
|
+
action: 'Install Rust: curl --proto \'=https\' --tlsv1.2 -sSf https://sh.rustup.rs | sh',
|
|
921
|
+
line: null
|
|
572
922
|
});
|
|
923
|
+
}
|
|
573
924
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
925
|
+
results.rust = {
|
|
926
|
+
available: cargoAvailable,
|
|
927
|
+
clippy: null,
|
|
928
|
+
rustfmt: null
|
|
929
|
+
};
|
|
577
930
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
931
|
+
if (cargoAvailable) {
|
|
932
|
+
// Check for clippy
|
|
933
|
+
let clippyAvailable = false;
|
|
934
|
+
try {
|
|
935
|
+
execSync('cargo clippy --version', { stdio: 'ignore', timeout: 5000 });
|
|
936
|
+
clippyAvailable = true;
|
|
937
|
+
} catch {
|
|
938
|
+
log('WARNING', 'cargo clippy not available');
|
|
939
|
+
}
|
|
581
940
|
|
|
582
|
-
|
|
941
|
+
// Check for rustfmt
|
|
942
|
+
let rustfmtAvailable = false;
|
|
943
|
+
try {
|
|
944
|
+
execSync('rustfmt --version', { stdio: 'ignore', timeout: 5000 });
|
|
945
|
+
rustfmtAvailable = true;
|
|
946
|
+
} catch {
|
|
947
|
+
log('WARNING', 'rustfmt not available');
|
|
948
|
+
}
|
|
583
949
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
950
|
+
// Warn if Rust components missing
|
|
951
|
+
if (!clippyAvailable || !rustfmtAvailable) {
|
|
952
|
+
const missing = [];
|
|
953
|
+
if (!clippyAvailable) missing.push('clippy');
|
|
954
|
+
if (!rustfmtAvailable) missing.push('rustfmt');
|
|
955
|
+
results.recommendations.push({
|
|
956
|
+
type: 'dependency',
|
|
957
|
+
priority: 'low',
|
|
958
|
+
message: `Rust components missing: ${missing.join(', ')}`,
|
|
959
|
+
action: `Install: rustup component add ${missing.join(' ')}`,
|
|
960
|
+
line: null
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// Run clippy if available (requires being in a Cargo project)
|
|
965
|
+
if (clippyAvailable) {
|
|
966
|
+
try {
|
|
967
|
+
const projectDir = dirname(filePath);
|
|
968
|
+
const clippyCmd = `cd "${projectDir}" && cargo clippy --message-format=json -- -W clippy::all 2>&1`;
|
|
969
|
+
const clippyOutput = execSync(clippyCmd, { encoding: 'utf-8', timeout: 30000 });
|
|
970
|
+
|
|
971
|
+
// Parse JSON messages
|
|
972
|
+
const messages = clippyOutput.split('\n')
|
|
973
|
+
.filter(line => line.trim().startsWith('{'))
|
|
974
|
+
.map(line => {
|
|
975
|
+
try {
|
|
976
|
+
return JSON.parse(line);
|
|
977
|
+
} catch {
|
|
978
|
+
return null;
|
|
979
|
+
}
|
|
980
|
+
})
|
|
981
|
+
.filter(msg => msg && msg.message && msg.message.level === 'warning');
|
|
590
982
|
|
|
983
|
+
results.rust.clippy = {
|
|
984
|
+
passed: messages.length === 0,
|
|
985
|
+
warningCount: messages.length,
|
|
986
|
+
warnings: messages
|
|
987
|
+
};
|
|
988
|
+
|
|
989
|
+
log('SUCCESS', `cargo clippy complete (${messages.length} warnings)`);
|
|
990
|
+
|
|
991
|
+
// Add recommendations for clippy warnings
|
|
992
|
+
messages.slice(0, 3).forEach(msg => {
|
|
591
993
|
results.recommendations.push({
|
|
592
|
-
type: '
|
|
994
|
+
type: 'rust',
|
|
593
995
|
priority: 'medium',
|
|
594
|
-
message: `
|
|
595
|
-
action:
|
|
596
|
-
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
// Critical threshold: 40 - invoke lizard for detailed analysis
|
|
600
|
-
if (complexity >= 40) {
|
|
601
|
-
log('COMPLEXITY_CRITICAL', `High complexity detected: ${complexity}, invoking lizard`, {
|
|
602
|
-
threshold: 40,
|
|
603
|
-
complexity
|
|
996
|
+
message: `clippy: ${msg.message.message}`,
|
|
997
|
+
action: msg.message.rendered || 'Review clippy suggestion',
|
|
998
|
+
line: msg.message.spans?.[0]?.line_start || null
|
|
604
999
|
});
|
|
1000
|
+
});
|
|
605
1001
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
if (lizardCheck.status === 0) {
|
|
610
|
-
// Run lizard for detailed analysis
|
|
611
|
-
const lizardResult = spawnSync('lizard', [
|
|
612
|
-
filePath,
|
|
613
|
-
'-C', '15' // Show functions with complexity >15
|
|
614
|
-
], {
|
|
615
|
-
encoding: 'utf-8',
|
|
616
|
-
timeout: 10000
|
|
617
|
-
});
|
|
618
|
-
|
|
619
|
-
if (lizardResult.status === 0) {
|
|
620
|
-
const lizardOutput = lizardResult.stdout;
|
|
621
|
-
|
|
622
|
-
log('LIZARD_ANALYSIS', 'Detailed complexity analysis', {
|
|
623
|
-
output: lizardOutput
|
|
624
|
-
});
|
|
625
|
-
|
|
626
|
-
results.complexityAnalysis = {
|
|
627
|
-
tool: 'lizard',
|
|
628
|
-
complexity,
|
|
629
|
-
detailedReport: lizardOutput
|
|
630
|
-
};
|
|
631
|
-
|
|
632
|
-
results.recommendations.push({
|
|
633
|
-
type: 'complexity',
|
|
634
|
-
priority: 'critical',
|
|
635
|
-
message: `Critical complexity level: ${complexity} (threshold: 40)`,
|
|
636
|
-
action: 'Refactor immediately. Run cyclomatic-complexity-reducer agent',
|
|
637
|
-
details: lizardOutput
|
|
638
|
-
});
|
|
639
|
-
} else {
|
|
640
|
-
log('WARN', 'Lizard analysis failed', {
|
|
641
|
-
stderr: lizardResult.stderr
|
|
642
|
-
});
|
|
643
|
-
}
|
|
644
|
-
} else {
|
|
645
|
-
log('WARN', 'Lizard not installed, skipping detailed analysis');
|
|
646
|
-
|
|
647
|
-
results.recommendations.push({
|
|
648
|
-
type: 'complexity',
|
|
649
|
-
priority: 'critical',
|
|
650
|
-
message: `Critical complexity level: ${complexity} (threshold: 40)`,
|
|
651
|
-
action: 'Refactor immediately. Install lizard: ./scripts/install-lizard.sh'
|
|
652
|
-
});
|
|
653
|
-
}
|
|
654
|
-
}
|
|
1002
|
+
} catch (error) {
|
|
1003
|
+
log('SKIP', 'cargo clippy requires Cargo project context');
|
|
1004
|
+
results.rust.clippy = { passed: true, note: 'Requires Cargo project' };
|
|
655
1005
|
}
|
|
656
|
-
} else {
|
|
657
|
-
log('WARN', 'Complexity analysis failed', {
|
|
658
|
-
stderr: complexityResult.stderr
|
|
659
|
-
});
|
|
660
1006
|
}
|
|
661
|
-
}
|
|
662
|
-
// For TypeScript/JavaScript, use lizard directly if available
|
|
663
|
-
else if (ext.match(/\.(js|ts|jsx|tsx)$/)) {
|
|
664
|
-
const lizardCheck = spawnSync('which', ['lizard'], { encoding: 'utf-8' });
|
|
665
|
-
|
|
666
|
-
if (lizardCheck.status === 0) {
|
|
667
|
-
const lizardResult = spawnSync('lizard', [
|
|
668
|
-
filePath,
|
|
669
|
-
'--json'
|
|
670
|
-
], {
|
|
671
|
-
encoding: 'utf-8',
|
|
672
|
-
timeout: 10000
|
|
673
|
-
});
|
|
674
1007
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
action: avgComplexity >= 40
|
|
704
|
-
? 'Critical: Refactor high-complexity functions immediately'
|
|
705
|
-
: 'Consider refactoring complex functions'
|
|
706
|
-
});
|
|
707
|
-
}
|
|
708
|
-
} catch (parseError) {
|
|
709
|
-
log('WARN', 'Failed to parse lizard JSON output', {
|
|
710
|
-
error: parseError.message
|
|
711
|
-
});
|
|
712
|
-
}
|
|
1008
|
+
// Run rustfmt if available
|
|
1009
|
+
if (rustfmtAvailable) {
|
|
1010
|
+
try {
|
|
1011
|
+
const rustfmtCmd = `rustfmt --check "${filePath}"`;
|
|
1012
|
+
execSync(rustfmtCmd, { encoding: 'utf-8', timeout: 10000 });
|
|
1013
|
+
|
|
1014
|
+
results.rust.rustfmt = {
|
|
1015
|
+
passed: true,
|
|
1016
|
+
formatted: true
|
|
1017
|
+
};
|
|
1018
|
+
|
|
1019
|
+
log('SUCCESS', 'rustfmt check passed');
|
|
1020
|
+
|
|
1021
|
+
} catch (error) {
|
|
1022
|
+
results.rust.rustfmt = {
|
|
1023
|
+
passed: false,
|
|
1024
|
+
formatted: false
|
|
1025
|
+
};
|
|
1026
|
+
|
|
1027
|
+
log('FAILED', 'Rust file needs formatting');
|
|
1028
|
+
|
|
1029
|
+
results.recommendations.push({
|
|
1030
|
+
type: 'rust',
|
|
1031
|
+
priority: 'low',
|
|
1032
|
+
message: 'Rust file needs formatting',
|
|
1033
|
+
action: `Run: rustfmt "${filePath}"`,
|
|
1034
|
+
line: null
|
|
1035
|
+
});
|
|
713
1036
|
}
|
|
714
1037
|
}
|
|
715
1038
|
}
|
|
1039
|
+
|
|
716
1040
|
} catch (error) {
|
|
717
|
-
log('
|
|
718
|
-
|
|
719
|
-
});
|
|
1041
|
+
log('ERROR', 'Rust validation error', { error: error.message });
|
|
1042
|
+
results.rust = { available: false, error: error.message };
|
|
720
1043
|
}
|
|
721
1044
|
}
|
|
722
1045
|
|
|
723
|
-
//
|
|
724
|
-
if (ext === '.
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
1046
|
+
// Go Validation (gofmt + go vet)
|
|
1047
|
+
if (ext === '.go') {
|
|
1048
|
+
try {
|
|
1049
|
+
log('VALIDATING', 'Running Go validation (gofmt + go vet)');
|
|
1050
|
+
|
|
1051
|
+
// Check for go
|
|
1052
|
+
let goAvailable = false;
|
|
1053
|
+
try {
|
|
1054
|
+
execSync('go version', { stdio: 'ignore', timeout: 5000 });
|
|
1055
|
+
goAvailable = true;
|
|
1056
|
+
} catch {
|
|
1057
|
+
log('WARNING', 'go not available for Go validation');
|
|
1058
|
+
results.recommendations.push({
|
|
1059
|
+
type: 'dependency',
|
|
1060
|
+
priority: 'medium',
|
|
1061
|
+
message: 'Go toolchain not installed - Go validation unavailable',
|
|
1062
|
+
action: 'Install Go: https://golang.org/doc/install or brew install go',
|
|
1063
|
+
line: null
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
734
1066
|
|
|
735
|
-
results.
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
});
|
|
1067
|
+
results.go = {
|
|
1068
|
+
available: goAvailable,
|
|
1069
|
+
gofmt: null,
|
|
1070
|
+
govet: null
|
|
1071
|
+
};
|
|
741
1072
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
1073
|
+
if (goAvailable) {
|
|
1074
|
+
// Run gofmt
|
|
1075
|
+
try {
|
|
1076
|
+
const gofmtCmd = `gofmt -l "${filePath}"`;
|
|
1077
|
+
const gofmtOutput = execSync(gofmtCmd, { encoding: 'utf-8', timeout: 10000 });
|
|
1078
|
+
|
|
1079
|
+
const needsFormatting = gofmtOutput.trim().length > 0;
|
|
1080
|
+
|
|
1081
|
+
results.go.gofmt = {
|
|
1082
|
+
passed: !needsFormatting,
|
|
1083
|
+
formatted: !needsFormatting
|
|
1084
|
+
};
|
|
1085
|
+
|
|
1086
|
+
if (needsFormatting) {
|
|
1087
|
+
log('FAILED', 'Go file needs formatting');
|
|
1088
|
+
results.recommendations.push({
|
|
1089
|
+
type: 'go',
|
|
1090
|
+
priority: 'medium',
|
|
1091
|
+
message: 'Go file needs formatting',
|
|
1092
|
+
action: `Run: gofmt -w "${filePath}"`,
|
|
1093
|
+
line: null
|
|
1094
|
+
});
|
|
1095
|
+
} else {
|
|
1096
|
+
log('SUCCESS', 'gofmt check passed');
|
|
1097
|
+
}
|
|
745
1098
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
1099
|
+
} catch (error) {
|
|
1100
|
+
results.go.gofmt = { passed: false, error: error.message };
|
|
1101
|
+
}
|
|
749
1102
|
|
|
750
|
-
|
|
1103
|
+
// Run go vet
|
|
1104
|
+
try {
|
|
1105
|
+
const govetCmd = `go vet "${filePath}"`;
|
|
1106
|
+
execSync(govetCmd, { encoding: 'utf-8', timeout: 10000 });
|
|
1107
|
+
|
|
1108
|
+
results.go.govet = {
|
|
1109
|
+
passed: true,
|
|
1110
|
+
issues: []
|
|
1111
|
+
};
|
|
1112
|
+
|
|
1113
|
+
log('SUCCESS', 'go vet passed');
|
|
1114
|
+
|
|
1115
|
+
} catch (error) {
|
|
1116
|
+
const output = error.stderr || error.stdout || '';
|
|
1117
|
+
const issues = output.split('\n').filter(line => line.trim().length > 0);
|
|
1118
|
+
|
|
1119
|
+
results.go.govet = {
|
|
1120
|
+
passed: false,
|
|
1121
|
+
issueCount: issues.length,
|
|
1122
|
+
issues
|
|
1123
|
+
};
|
|
1124
|
+
|
|
1125
|
+
log('FAILED', `go vet found ${issues.length} issues`);
|
|
1126
|
+
|
|
1127
|
+
// Add recommendations
|
|
1128
|
+
issues.slice(0, 3).forEach(issue => {
|
|
1129
|
+
results.recommendations.push({
|
|
1130
|
+
type: 'go',
|
|
1131
|
+
priority: 'high',
|
|
1132
|
+
message: `go vet: ${issue}`,
|
|
1133
|
+
action: 'Fix static analysis issue',
|
|
1134
|
+
line: null
|
|
1135
|
+
});
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
751
1139
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
priority: 'medium',
|
|
757
|
-
message: 'Avoid using "any" type when possible',
|
|
758
|
-
action: 'Use specific types or unknown for better type safety'
|
|
759
|
-
});
|
|
1140
|
+
} catch (error) {
|
|
1141
|
+
log('ERROR', 'Go validation error', { error: error.message });
|
|
1142
|
+
results.go = { available: false, error: error.message };
|
|
1143
|
+
}
|
|
760
1144
|
}
|
|
761
1145
|
|
|
762
|
-
//
|
|
763
|
-
if (
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
1146
|
+
// Java Validation (google-java-format)
|
|
1147
|
+
if (ext === '.java') {
|
|
1148
|
+
try {
|
|
1149
|
+
log('VALIDATING', 'Running Java validation (google-java-format)');
|
|
1150
|
+
let javaFormatterAvailable = false;
|
|
1151
|
+
try {
|
|
1152
|
+
execSync('google-java-format --version', { stdio: 'ignore', timeout: 5000 });
|
|
1153
|
+
javaFormatterAvailable = true;
|
|
1154
|
+
} catch {
|
|
1155
|
+
log('WARNING', 'google-java-format not available for Java validation');
|
|
1156
|
+
results.recommendations.push({
|
|
1157
|
+
type: 'dependency',
|
|
1158
|
+
priority: 'medium',
|
|
1159
|
+
message: 'Java formatter not installed - google-java-format unavailable',
|
|
1160
|
+
action: 'Install: brew install google-java-format or download from https://github.com/google/google-java-format',
|
|
1161
|
+
line: null
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
results.java = {
|
|
1165
|
+
available: javaFormatterAvailable,
|
|
1166
|
+
format: null
|
|
1167
|
+
};
|
|
1168
|
+
if (javaFormatterAvailable) {
|
|
1169
|
+
try {
|
|
1170
|
+
const javaCmd = `google-java-format --dry-run --set-exit-if-changed "${filePath}"`;
|
|
1171
|
+
execSync(javaCmd, { encoding: 'utf-8', timeout: 10000 });
|
|
1172
|
+
results.java.format = { passed: true, formatted: true };
|
|
1173
|
+
log('SUCCESS', 'google-java-format check passed');
|
|
1174
|
+
} catch (error) {
|
|
1175
|
+
results.java.format = { passed: false, formatted: false };
|
|
1176
|
+
log('FAILED', 'Java file needs formatting');
|
|
1177
|
+
results.recommendations.push({
|
|
1178
|
+
type: 'java',
|
|
1179
|
+
priority: 'medium',
|
|
1180
|
+
message: 'Java file needs formatting',
|
|
1181
|
+
action: `Run: google-java-format -i "${filePath}"`,
|
|
1182
|
+
line: null
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
} catch (error) {
|
|
1187
|
+
log('ERROR', 'Java validation error', { error: error.message });
|
|
1188
|
+
results.java = { available: false, error: error.message };
|
|
1189
|
+
}
|
|
770
1190
|
}
|
|
771
1191
|
|
|
772
|
-
|
|
1192
|
+
// C/C++ Validation (clang-format + cppcheck)
|
|
1193
|
+
if (['.c', '.cpp', '.cc', '.cxx', '.h', '.hpp', '.hxx'].includes(ext)) {
|
|
1194
|
+
try {
|
|
1195
|
+
log('VALIDATING', 'Running C/C++ validation (clang-format + cppcheck)');
|
|
1196
|
+
let clangFormatAvailable = false;
|
|
1197
|
+
try {
|
|
1198
|
+
execSync('clang-format --version', { stdio: 'ignore', timeout: 5000 });
|
|
1199
|
+
clangFormatAvailable = true;
|
|
1200
|
+
} catch {
|
|
1201
|
+
log('WARNING', 'clang-format not available');
|
|
1202
|
+
}
|
|
1203
|
+
let cppcheckAvailable = false;
|
|
1204
|
+
try {
|
|
1205
|
+
execSync('cppcheck --version', { stdio: 'ignore', timeout: 5000 });
|
|
1206
|
+
cppcheckAvailable = true;
|
|
1207
|
+
} catch {
|
|
1208
|
+
log('WARNING', 'cppcheck not available');
|
|
1209
|
+
}
|
|
1210
|
+
results.cpp = {
|
|
1211
|
+
available: clangFormatAvailable || cppcheckAvailable,
|
|
1212
|
+
clangFormat: null,
|
|
1213
|
+
cppcheck: null
|
|
1214
|
+
};
|
|
773
1215
|
|
|
774
|
-
//
|
|
775
|
-
|
|
1216
|
+
// Add warning if no C/C++ tools available
|
|
1217
|
+
if (!clangFormatAvailable && !cppcheckAvailable) {
|
|
1218
|
+
results.recommendations.push({
|
|
1219
|
+
type: 'dependency',
|
|
1220
|
+
priority: 'medium',
|
|
1221
|
+
message: 'C/C++ validation tools not installed (clang-format, cppcheck)',
|
|
1222
|
+
action: 'Install: brew install clang-format cppcheck or apt-get install clang-format cppcheck',
|
|
1223
|
+
line: null
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
if (clangFormatAvailable) {
|
|
1227
|
+
try {
|
|
1228
|
+
const clangCmd = `clang-format --dry-run -Werror "${filePath}"`;
|
|
1229
|
+
execSync(clangCmd, { encoding: 'utf-8', timeout: 10000 });
|
|
1230
|
+
results.cpp.clangFormat = { passed: true, formatted: true };
|
|
1231
|
+
log('SUCCESS', 'clang-format check passed');
|
|
1232
|
+
} catch (error) {
|
|
1233
|
+
results.cpp.clangFormat = { passed: false, formatted: false };
|
|
1234
|
+
log('FAILED', 'C/C++ file needs formatting');
|
|
1235
|
+
results.recommendations.push({
|
|
1236
|
+
type: 'cpp',
|
|
1237
|
+
priority: 'low',
|
|
1238
|
+
message: 'C/C++ file needs formatting',
|
|
1239
|
+
action: `Run: clang-format -i "${filePath}"`,
|
|
1240
|
+
line: null
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
if (cppcheckAvailable) {
|
|
1245
|
+
try {
|
|
1246
|
+
const cppcheckCmd = `cppcheck --enable=all --suppress=missingIncludeSystem "${filePath}" 2>&1`;
|
|
1247
|
+
const cppcheckOutput = execSync(cppcheckCmd, { encoding: 'utf-8', timeout: 10000 });
|
|
1248
|
+
const lines = cppcheckOutput.split('\n').filter(line => line.includes(':'));
|
|
1249
|
+
const errors = lines.filter(l => l.includes(':error:'));
|
|
1250
|
+
const warnings = lines.filter(l => l.includes(':warning:') || l.includes(':style:'));
|
|
1251
|
+
results.cpp.cppcheck = {
|
|
1252
|
+
passed: errors.length === 0,
|
|
1253
|
+
errorCount: errors.length,
|
|
1254
|
+
warningCount: warnings.length,
|
|
1255
|
+
issues: lines
|
|
1256
|
+
};
|
|
1257
|
+
log('SUCCESS', `cppcheck complete (${errors.length} errors, ${warnings.length} warnings)`);
|
|
1258
|
+
errors.slice(0, 3).forEach(err => {
|
|
1259
|
+
results.recommendations.push({
|
|
1260
|
+
type: 'cpp',
|
|
1261
|
+
priority: 'critical',
|
|
1262
|
+
message: `cppcheck error: ${err}`,
|
|
1263
|
+
action: 'Fix static analysis error',
|
|
1264
|
+
line: null
|
|
1265
|
+
});
|
|
1266
|
+
});
|
|
1267
|
+
} catch (error) {
|
|
1268
|
+
const output = error.stdout || error.stderr || '';
|
|
1269
|
+
const lines = output.split('\n').filter(line => line.includes(':'));
|
|
1270
|
+
const errors = lines.filter(l => l.includes(':error:'));
|
|
1271
|
+
results.cpp.cppcheck = {
|
|
1272
|
+
passed: errors.length === 0,
|
|
1273
|
+
errorCount: errors.length,
|
|
1274
|
+
issues: lines
|
|
1275
|
+
};
|
|
1276
|
+
if (errors.length > 0) {
|
|
1277
|
+
log('FAILED', `cppcheck found ${errors.length} errors`);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
} catch (error) {
|
|
1282
|
+
log('ERROR', 'C/C++ validation error', { error: error.message });
|
|
1283
|
+
results.cpp = { available: false, error: error.message };
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
// Summary and Exit
|
|
776
1287
|
// ============================================================================
|
|
777
1288
|
|
|
1289
|
+
// Calculate overall status
|
|
1290
|
+
const hasCriticalIssues = results.recommendations.some(r => r.priority === 'critical');
|
|
1291
|
+
const hasHighIssues = results.recommendations.some(r => r.priority === 'high');
|
|
1292
|
+
const hasTypeScriptErrors = results.typescript && !results.typescript.passed;
|
|
1293
|
+
const hasRustErrors = results.rust && !results.rust.passed && !results.rust.skipped;
|
|
1294
|
+
const hasESLintErrors = results.eslint && results.eslint.errorCount > 0;
|
|
1295
|
+
|
|
1296
|
+
let overallStatus = 'SUCCESS';
|
|
778
1297
|
let exitCode = 0;
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
const hasBashValidatorError = results.bashValidators && results.bashValidators.errors > 0;
|
|
786
|
-
const hasBashValidatorWarning = results.bashValidators && results.bashValidators.warnings > 0;
|
|
787
|
-
|
|
788
|
-
// Check for SQL injection issues
|
|
789
|
-
const hasSqlInjectionCritical = results.sqlInjection && !results.sqlInjection.passed &&
|
|
790
|
-
results.sqlInjection.issues.some(issue => issue.severity === 'critical');
|
|
791
|
-
|
|
792
|
-
if (hasSqlInjectionCritical) {
|
|
793
|
-
exitCode = 12;
|
|
794
|
-
finalStatus = 'SQL_INJECTION_CRITICAL';
|
|
795
|
-
} else if (hasBashValidatorError) {
|
|
796
|
-
exitCode = 9;
|
|
797
|
-
finalStatus = 'BASH_VALIDATOR_ERROR';
|
|
798
|
-
} else if (results.rootWarning) {
|
|
1298
|
+
|
|
1299
|
+
if (hasCriticalIssues) {
|
|
1300
|
+
overallStatus = 'CRITICAL_ISSUES';
|
|
1301
|
+
exitCode = 2;
|
|
1302
|
+
} else if (hasTypeScriptErrors && results.typescript.severity === 'SYNTAX_ERROR') {
|
|
1303
|
+
overallStatus = 'SYNTAX_ERROR';
|
|
799
1304
|
exitCode = 2;
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
exitCode =
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
exitCode =
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
exitCode =
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
exitCode =
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
exitCode =
|
|
815
|
-
finalStatus = 'RUST_QUALITY';
|
|
816
|
-
} else if (results.prettier && !results.prettier.passed) {
|
|
817
|
-
exitCode = 6;
|
|
818
|
-
finalStatus = 'LINT_ISSUES';
|
|
819
|
-
} else if (results.typescript && !results.typescript.passed) {
|
|
820
|
-
exitCode = 1;
|
|
821
|
-
finalStatus = 'TYPE_WARNING';
|
|
1305
|
+
} else if (hasTypeScriptErrors) {
|
|
1306
|
+
overallStatus = 'TYPE_ERRORS';
|
|
1307
|
+
exitCode = 2; // Now blocking for all TS errors
|
|
1308
|
+
} else if (hasRustErrors && results.rust.severity === 'SYNTAX_ERROR') {
|
|
1309
|
+
overallStatus = 'RUST_SYNTAX_ERROR';
|
|
1310
|
+
exitCode = 2;
|
|
1311
|
+
} else if (hasRustErrors && results.rust.severity === 'BORROW_ERROR') {
|
|
1312
|
+
overallStatus = 'RUST_BORROW_ERROR';
|
|
1313
|
+
exitCode = 2; // Borrow checker errors are critical
|
|
1314
|
+
} else if (hasRustErrors) {
|
|
1315
|
+
overallStatus = 'RUST_ERRORS';
|
|
1316
|
+
exitCode = 2; // Now blocking for all Rust errors
|
|
1317
|
+
} else if (hasHighIssues || hasESLintErrors) {
|
|
1318
|
+
overallStatus = 'LINT_ISSUES';
|
|
1319
|
+
exitCode = 0; // Non-blocking
|
|
822
1320
|
} else if (results.recommendations.length > 0) {
|
|
823
|
-
|
|
1321
|
+
overallStatus = 'IMPROVEMENTS_SUGGESTED';
|
|
1322
|
+
exitCode = 0;
|
|
824
1323
|
}
|
|
825
1324
|
|
|
826
|
-
|
|
1325
|
+
// Log final summary
|
|
1326
|
+
log(overallStatus, 'Pipeline validation complete', {
|
|
827
1327
|
typescript: results.typescript,
|
|
1328
|
+
rust: results.rust,
|
|
828
1329
|
eslint: results.eslint,
|
|
829
1330
|
prettier: results.prettier,
|
|
830
1331
|
security: results.security,
|
|
831
1332
|
metrics: results.metrics,
|
|
832
1333
|
recommendationCount: results.recommendations.length,
|
|
833
1334
|
topRecommendations: results.recommendations.slice(0, 3)
|
|
834
|
-
};
|
|
1335
|
+
});
|
|
1336
|
+
|
|
1337
|
+
// Print user-friendly summary
|
|
1338
|
+
console.error('\n' + '='.repeat(80));
|
|
1339
|
+
console.error('Enhanced Post-Edit Pipeline Summary');
|
|
1340
|
+
console.error('='.repeat(80));
|
|
835
1341
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
1342
|
+
if (results.typescript) {
|
|
1343
|
+
console.error(`\nTypeScript: ${results.typescript.passed ? '✅ PASSED' : '❌ FAILED'}`);
|
|
1344
|
+
if (!results.typescript.passed) {
|
|
1345
|
+
console.error(` Errors: ${results.typescript.errorCount}`);
|
|
1346
|
+
}
|
|
839
1347
|
}
|
|
840
|
-
|
|
841
|
-
|
|
1348
|
+
|
|
1349
|
+
if (results.rust) {
|
|
1350
|
+
if (results.rust.skipped) {
|
|
1351
|
+
console.error(`\nRust: ⏭️ SKIPPED (${results.rust.reason})`);
|
|
1352
|
+
} else {
|
|
1353
|
+
console.error(`\nRust: ${results.rust.passed ? '✅ PASSED' : '❌ FAILED'}`);
|
|
1354
|
+
if (!results.rust.passed) {
|
|
1355
|
+
console.error(` Errors: ${results.rust.errorCount}`);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
842
1358
|
}
|
|
843
|
-
|
|
844
|
-
|
|
1359
|
+
|
|
1360
|
+
if (results.eslint && results.eslint.available) {
|
|
1361
|
+
console.error(`ESLint: ${results.eslint.passed ? '✅ PASSED' : '❌ FAILED'}`);
|
|
1362
|
+
if (!results.eslint.passed) {
|
|
1363
|
+
console.error(` Errors: ${results.eslint.errorCount}, Warnings: ${results.eslint.warningCount}`);
|
|
1364
|
+
}
|
|
845
1365
|
}
|
|
846
|
-
|
|
847
|
-
|
|
1366
|
+
|
|
1367
|
+
if (results.prettier && results.prettier.available) {
|
|
1368
|
+
console.error(`Prettier: ${results.prettier.passed ? '✅ PASSED' : '⚠️ NEEDS FORMATTING'}`);
|
|
848
1369
|
}
|
|
849
|
-
|
|
850
|
-
|
|
1370
|
+
|
|
1371
|
+
console.error(`\nSecurity: ${results.security.passed ? '✅ NO ISSUES' : '❌ ISSUES FOUND'}`);
|
|
1372
|
+
if (!results.security.passed) {
|
|
1373
|
+
console.error(` Issues: ${results.security.issueCount}`);
|
|
851
1374
|
}
|
|
852
|
-
|
|
853
|
-
|
|
1375
|
+
|
|
1376
|
+
console.error(`\nCode Metrics:`);
|
|
1377
|
+
console.error(` Lines: ${results.metrics.lines}`);
|
|
1378
|
+
console.error(` Functions: ${results.metrics.functions}`);
|
|
1379
|
+
console.error(` Classes: ${results.metrics.classes}`);
|
|
1380
|
+
console.error(` Complexity: ${results.metrics.complexity.toUpperCase()}`);
|
|
1381
|
+
|
|
1382
|
+
if (results.recommendations.length > 0) {
|
|
1383
|
+
console.error(`\nTop Recommendations:`);
|
|
1384
|
+
results.recommendations.slice(0, 3).forEach((rec, i) => {
|
|
1385
|
+
const icon = rec.priority === 'critical' ? '🔴' : rec.priority === 'high' ? '🟠' : rec.priority === 'medium' ? '🟡' : '🔵';
|
|
1386
|
+
console.error(` ${icon} [${rec.type.toUpperCase()}] ${rec.message}`);
|
|
1387
|
+
console.error(` Action: ${rec.action}`);
|
|
1388
|
+
});
|
|
1389
|
+
|
|
1390
|
+
if (results.recommendations.length > 3) {
|
|
1391
|
+
console.error(` ... and ${results.recommendations.length - 3} more recommendations`);
|
|
1392
|
+
}
|
|
854
1393
|
}
|
|
855
1394
|
|
|
856
|
-
|
|
1395
|
+
console.error('='.repeat(80) + '\n');
|
|
857
1396
|
|
|
858
|
-
process.exit(exitCode);
|
|
1397
|
+
process.exit(exitCode);
|