claude-flow-novice 2.18.26 → 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 +1157 -657
- package/package.json +1 -1
|
@@ -1,59 +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>]
|
|
15
16
|
*
|
|
16
17
|
* ============================================================================
|
|
17
|
-
*
|
|
18
|
+
* ERROR DETECTION - WHAT'S CAUGHT vs NOT CAUGHT
|
|
18
19
|
* ============================================================================
|
|
19
20
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
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).
|
|
23
26
|
*
|
|
24
27
|
* ✅ CAUGHT (Blocks Edit):
|
|
25
|
-
*
|
|
26
|
-
* │ Error Type │ Code
|
|
27
|
-
*
|
|
28
|
-
* │ Missing module │ TS2307
|
|
29
|
-
* │ Missing export │ TS2305
|
|
30
|
-
* │
|
|
31
|
-
* │
|
|
32
|
-
* │
|
|
33
|
-
* │
|
|
34
|
-
* │
|
|
35
|
-
*
|
|
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
|
+
* └────────────────────┴──────────────────┴─────────────────────────────────────┘
|
|
36
40
|
*
|
|
37
|
-
* ❌ NOT CAUGHT (Single-File Limitation):
|
|
41
|
+
* ❌ NOT CAUGHT (TypeScript Single-File Limitation):
|
|
38
42
|
* ┌─────────────────────────────────┬────────────────────────────────────────────┐
|
|
39
43
|
* │ Scenario │ Why │
|
|
40
44
|
* ├─────────────────────────────────┼────────────────────────────────────────────┤
|
|
41
|
-
* │ Cross-file type changes
|
|
45
|
+
* │ Cross-file type changes (TS) │ Editing types.ts won't catch breaks in │
|
|
42
46
|
* │ │ other.ts that imports it │
|
|
43
47
|
* │ Deleted exports used elsewhere │ Consumers aren't checked until edited │
|
|
44
|
-
* │ Renamed functions/types │ Same - callers won't be validated │
|
|
45
48
|
* └─────────────────────────────────┴────────────────────────────────────────────┘
|
|
46
49
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* Pipeline: ✅ passes (types.ts is valid)
|
|
50
|
-
* Reality: src/user.ts imports UserType → BROKEN but not checked
|
|
50
|
+
* Note: Rust DOES catch cross-file breaks because cargo check compiles the
|
|
51
|
+
* entire crate. TypeScript only checks the single file + its imports.
|
|
51
52
|
*
|
|
52
|
-
* To catch everything: Run `npx tsc --noEmit` project-wide periodically.
|
|
53
|
+
* To catch everything in TS: Run `npx tsc --noEmit` project-wide periodically.
|
|
53
54
|
* ============================================================================
|
|
54
55
|
*/
|
|
55
56
|
|
|
56
|
-
import {
|
|
57
|
+
import { execSync } from 'child_process';
|
|
57
58
|
import { existsSync, readFileSync, appendFileSync, mkdirSync } from 'fs';
|
|
58
59
|
import { dirname, extname, resolve } from 'path';
|
|
59
60
|
|
|
@@ -102,11 +103,11 @@ if (!existsSync(filePath)) {
|
|
|
102
103
|
// Read file content for analysis
|
|
103
104
|
const fileContent = readFileSync(filePath, 'utf-8');
|
|
104
105
|
const ext = extname(filePath);
|
|
105
|
-
const baseName = filePath.replace(ext, '').split('/').pop();
|
|
106
106
|
|
|
107
107
|
// Initialize results object
|
|
108
108
|
const results = {
|
|
109
109
|
typescript: null,
|
|
110
|
+
rust: null,
|
|
110
111
|
eslint: null,
|
|
111
112
|
prettier: null,
|
|
112
113
|
security: null,
|
|
@@ -114,784 +115,1283 @@ const results = {
|
|
|
114
115
|
recommendations: []
|
|
115
116
|
};
|
|
116
117
|
|
|
117
|
-
// [Remaining TypeScript, ESLint, and Prettier validation code remains the same]
|
|
118
|
-
|
|
119
118
|
// ============================================================================
|
|
120
|
-
// PHASE
|
|
119
|
+
// PHASE 1: TypeScript Validation
|
|
121
120
|
// ============================================================================
|
|
122
121
|
|
|
123
|
-
|
|
122
|
+
if (['.ts', '.tsx'].includes(ext)) {
|
|
123
|
+
try {
|
|
124
|
+
log('VALIDATING', 'Running TypeScript validation');
|
|
124
125
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
'.claude/skills/hook-pipeline/security-scanner.sh',
|
|
129
|
-
filePath
|
|
130
|
-
], {
|
|
131
|
-
encoding: 'utf-8',
|
|
132
|
-
timeout: 10000
|
|
133
|
-
});
|
|
126
|
+
// Use tsc to check only this file
|
|
127
|
+
const cmd = `npx tsc --noEmit --skipLibCheck ${filePath}`;
|
|
128
|
+
execSync(cmd, { stdio: 'pipe', encoding: 'utf-8' });
|
|
134
129
|
|
|
135
|
-
|
|
136
|
-
|
|
130
|
+
results.typescript = {
|
|
131
|
+
passed: true,
|
|
132
|
+
errors: []
|
|
133
|
+
};
|
|
134
|
+
log('SUCCESS', 'TypeScript validation passed');
|
|
137
135
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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);
|
|
143
157
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
results.security = {
|
|
148
|
-
passed: securityScanResults.passed,
|
|
149
|
-
confidence: securityScanResults.confidence || 0,
|
|
150
|
-
issues: Array.isArray(securityScanResults.vulnerabilities)
|
|
151
|
-
? securityScanResults.vulnerabilities
|
|
152
|
-
: JSON.parse(securityScanResults.vulnerabilities || '[]'),
|
|
153
|
-
details: securityScanOutput
|
|
154
|
-
};
|
|
158
|
+
const severity = errorTypes.syntaxError > 0 ? 'SYNTAX_ERROR' :
|
|
159
|
+
lines.length > 5 ? 'LINT_ISSUES' : 'TYPE_WARNING';
|
|
155
160
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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)
|
|
160
173
|
});
|
|
161
174
|
|
|
162
|
-
//
|
|
163
|
-
|
|
175
|
+
// Add recommendations based on error types
|
|
176
|
+
if (errorTypes.syntaxError > 0) {
|
|
164
177
|
results.recommendations.push({
|
|
165
|
-
type: '
|
|
178
|
+
type: 'typescript',
|
|
166
179
|
priority: 'critical',
|
|
167
|
-
message:
|
|
168
|
-
action:
|
|
180
|
+
message: 'Fix syntax errors before proceeding',
|
|
181
|
+
action: 'Review and fix TypeScript syntax issues'
|
|
169
182
|
});
|
|
170
|
-
})
|
|
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
|
+
}
|
|
171
196
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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' };
|
|
179
221
|
} else {
|
|
180
|
-
|
|
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');
|
|
181
231
|
}
|
|
182
|
-
} catch (parseError) {
|
|
183
|
-
log('ERROR', 'Failed to parse security scanner output', {
|
|
184
|
-
parseError: parseError.message,
|
|
185
|
-
output: securityScanOutput
|
|
186
|
-
});
|
|
187
232
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
.
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
+
};
|
|
220
267
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
268
|
+
log(severity, `Rust errors detected: ${lines.length}`, {
|
|
269
|
+
errorCount: lines.length,
|
|
270
|
+
errorTypes,
|
|
271
|
+
errors: lines.slice(0, 5)
|
|
224
272
|
});
|
|
225
273
|
|
|
226
|
-
|
|
274
|
+
// Add recommendations based on error types
|
|
275
|
+
if (errorTypes.borrowCheck > 0) {
|
|
227
276
|
results.recommendations.push({
|
|
228
|
-
type: '
|
|
229
|
-
priority:
|
|
230
|
-
message:
|
|
231
|
-
action:
|
|
277
|
+
type: 'rust',
|
|
278
|
+
priority: 'critical',
|
|
279
|
+
message: 'Fix borrow checker errors',
|
|
280
|
+
action: 'Review ownership and borrowing rules'
|
|
232
281
|
});
|
|
233
|
-
})
|
|
282
|
+
} else if (errorTypes.lifetime > 0) {
|
|
283
|
+
results.recommendations.push({
|
|
284
|
+
type: 'rust',
|
|
285
|
+
priority: 'high',
|
|
286
|
+
message: 'Fix lifetime annotations',
|
|
287
|
+
action: 'Add or correct lifetime parameters'
|
|
288
|
+
});
|
|
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
|
+
}
|
|
234
297
|
}
|
|
235
298
|
}
|
|
236
|
-
}
|
|
237
|
-
log('
|
|
238
|
-
error: error.message,
|
|
239
|
-
stack: error.stack
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
results.security = {
|
|
243
|
-
passed: false,
|
|
244
|
-
confidence: 0,
|
|
245
|
-
issues: [],
|
|
246
|
-
details: 'Complete security scanning failure'
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
results.recommendations.push({
|
|
250
|
-
type: 'security',
|
|
251
|
-
priority: 'critical',
|
|
252
|
-
message: 'Security scanning infrastructure failure',
|
|
253
|
-
action: 'Verify security scanning script and dependencies'
|
|
254
|
-
});
|
|
299
|
+
} else {
|
|
300
|
+
log('SKIPPED', 'Rust validation skipped for non-Rust file');
|
|
255
301
|
}
|
|
256
302
|
|
|
257
303
|
// ============================================================================
|
|
258
|
-
// PHASE
|
|
304
|
+
// PHASE 1: ESLint Integration
|
|
259
305
|
// ============================================================================
|
|
260
306
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
],
|
|
285
|
-
'.ts': [
|
|
286
|
-
'js-promise-safety.sh',
|
|
287
|
-
'enforce-lf.sh'
|
|
288
|
-
],
|
|
289
|
-
'.jsx': [
|
|
290
|
-
'js-promise-safety.sh',
|
|
291
|
-
'enforce-lf.sh'
|
|
292
|
-
],
|
|
293
|
-
'.tsx': [
|
|
294
|
-
'js-promise-safety.sh',
|
|
295
|
-
'enforce-lf.sh'
|
|
296
|
-
],
|
|
297
|
-
'.rs': [
|
|
298
|
-
'rust-command-safety.sh',
|
|
299
|
-
'rust-future-safety.sh',
|
|
300
|
-
'rust-dependency-checker.sh',
|
|
301
|
-
'enforce-lf.sh'
|
|
302
|
-
]
|
|
303
|
-
};
|
|
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
|
+
}
|
|
318
|
+
|
|
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: [] };
|
|
304
330
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
+
}
|
|
308
393
|
|
|
309
|
-
|
|
394
|
+
// ============================================================================
|
|
395
|
+
// PHASE 1: Prettier Integration
|
|
396
|
+
// ============================================================================
|
|
310
397
|
|
|
398
|
+
if (['.js', '.jsx', '.ts', '.tsx', '.json', '.css', '.html'].includes(ext)) {
|
|
311
399
|
try {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
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
|
+
}
|
|
321
409
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
+
});
|
|
325
419
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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');
|
|
331
435
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
isBlocking: exitCode === 1,
|
|
341
|
-
isWarning: exitCode === 2,
|
|
342
|
-
message: stderr || stdout || 'Validator passed',
|
|
343
|
-
stdout,
|
|
344
|
-
stderr
|
|
345
|
-
};
|
|
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
|
+
}
|
|
346
444
|
} catch (error) {
|
|
347
|
-
log('ERROR',
|
|
348
|
-
|
|
349
|
-
stack: error.stack
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
return {
|
|
353
|
-
validator: validatorName,
|
|
354
|
-
exitCode: -1,
|
|
355
|
-
passed: false,
|
|
356
|
-
isBlocking: false,
|
|
357
|
-
isWarning: true,
|
|
358
|
-
message: `Validator execution failed: ${error.message}`,
|
|
359
|
-
error: error.message
|
|
360
|
-
};
|
|
445
|
+
log('ERROR', 'Prettier execution failed', { error: error.message });
|
|
446
|
+
results.prettier = { available: false, error: error.message };
|
|
361
447
|
}
|
|
362
448
|
}
|
|
363
449
|
|
|
364
|
-
//
|
|
365
|
-
|
|
450
|
+
// ============================================================================
|
|
451
|
+
// PHASE 2: Security Analysis
|
|
452
|
+
// ============================================================================
|
|
366
453
|
|
|
367
|
-
|
|
368
|
-
log('INFO', `Running ${applicableValidators.length} bash validators for ${ext} file`);
|
|
454
|
+
log('VALIDATING', 'Running security analysis');
|
|
369
455
|
|
|
370
|
-
|
|
371
|
-
const validatorResults = applicableValidators.map(validator =>
|
|
372
|
-
runValidator(validator, filePath)
|
|
373
|
-
);
|
|
456
|
+
const securityIssues = [];
|
|
374
457
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
+
}
|
|
382
467
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
message: result.message
|
|
393
|
-
});
|
|
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
|
+
}
|
|
394
477
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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'
|
|
405
495
|
});
|
|
496
|
+
}
|
|
497
|
+
|
|
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
|
+
}
|
|
406
517
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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'
|
|
421
535
|
});
|
|
422
536
|
} else {
|
|
423
|
-
log('
|
|
537
|
+
log('SUCCESS', 'No security issues detected');
|
|
424
538
|
}
|
|
425
539
|
|
|
426
540
|
// ============================================================================
|
|
427
|
-
// PHASE
|
|
541
|
+
// PHASE 3: Code Metrics
|
|
428
542
|
// ============================================================================
|
|
429
543
|
|
|
430
|
-
|
|
431
|
-
log('VALIDATING', 'Running SQL injection detection');
|
|
544
|
+
log('VALIDATING', 'Calculating code metrics');
|
|
432
545
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
+
}
|
|
437
560
|
|
|
438
|
-
|
|
439
|
-
|
|
561
|
+
results.metrics = {
|
|
562
|
+
lines: lineCount,
|
|
563
|
+
functions: functionCount,
|
|
564
|
+
classes: classCount,
|
|
565
|
+
todos: todoCount,
|
|
566
|
+
fixmes: fixmeCount,
|
|
567
|
+
complexity
|
|
568
|
+
};
|
|
440
569
|
|
|
441
|
-
|
|
442
|
-
|
|
570
|
+
log('SUCCESS', 'Code metrics calculated', {
|
|
571
|
+
lines: lineCount,
|
|
572
|
+
functions: functionCount,
|
|
573
|
+
classes: classCount,
|
|
574
|
+
complexity
|
|
575
|
+
});
|
|
443
576
|
|
|
444
|
-
|
|
445
|
-
|
|
577
|
+
// ============================================================================
|
|
578
|
+
// PHASE 3: Recommendations Engine
|
|
579
|
+
// ============================================================================
|
|
446
580
|
|
|
447
|
-
|
|
448
|
-
{ 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');
|
|
449
582
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
+
}
|
|
454
592
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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
|
+
}
|
|
458
602
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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
|
+
}
|
|
463
611
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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
|
+
}
|
|
468
622
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
});
|
|
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'
|
|
476
629
|
});
|
|
477
|
-
} else {
|
|
478
|
-
results.sqlInjection = { passed: true, issues: [] };
|
|
479
|
-
log('SUCCESS', 'No SQL injection risks detected');
|
|
480
630
|
}
|
|
481
631
|
}
|
|
482
632
|
|
|
483
|
-
//
|
|
484
|
-
|
|
485
|
-
|
|
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
|
+
}
|
|
486
642
|
|
|
487
|
-
|
|
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
|
+
}
|
|
488
652
|
|
|
489
|
-
|
|
490
|
-
if (
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
}
|
|
499
|
-
if (ext === '.md' && !filePath.match(/^(README|CLAUDE)\.md$/)) {
|
|
500
|
-
suggestions.push({ location: `docs/${filePath}`, reason: 'Documentation belongs in docs/' });
|
|
501
|
-
}
|
|
502
|
-
if (ext === '.json' && !filePath.match(/^package\.json$/)) {
|
|
503
|
-
suggestions.push({ location: `config/${filePath}`, reason: 'Config files belong in config/' });
|
|
504
|
-
}
|
|
505
|
-
if (ext === '.sh') {
|
|
506
|
-
suggestions.push({ location: `scripts/${filePath}`, reason: 'Scripts belong in scripts/' });
|
|
507
|
-
}
|
|
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
|
+
}
|
|
508
662
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
suggestions
|
|
513
|
-
});
|
|
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]);
|
|
514
666
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
priority: 'high',
|
|
518
|
-
message: `File "${filePath}" should not be in root directory`,
|
|
519
|
-
action: `Move to: ${suggestions[0].location}`,
|
|
520
|
-
suggestions
|
|
521
|
-
});
|
|
667
|
+
// Limit to top 10 recommendations
|
|
668
|
+
results.recommendations = results.recommendations.slice(0, 10);
|
|
522
669
|
|
|
523
|
-
|
|
524
|
-
results.rootWarning = { suggestions };
|
|
525
|
-
}
|
|
526
|
-
}
|
|
670
|
+
log('SUCCESS', `Generated ${results.recommendations.length} recommendations`);
|
|
527
671
|
|
|
528
672
|
// ============================================================================
|
|
529
|
-
//
|
|
673
|
+
// MULTI-LANGUAGE VALIDATION
|
|
530
674
|
// ============================================================================
|
|
531
675
|
|
|
532
|
-
|
|
533
|
-
|
|
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
|
+
}
|
|
534
696
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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
|
+
});
|
|
542
709
|
|
|
543
|
-
|
|
544
|
-
|
|
710
|
+
const configPath = '/tmp/tsconfig.jsdoc.json';
|
|
711
|
+
require('fs').writeFileSync(configPath, tempConfig);
|
|
545
712
|
|
|
546
|
-
|
|
547
|
-
|
|
713
|
+
const cmd = `npx tsc --noEmit --project ${configPath} ${filePath}`;
|
|
714
|
+
execSync(cmd, { stdio: 'pipe', encoding: 'utf-8', timeout: 10000 });
|
|
548
715
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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');
|
|
554
724
|
|
|
725
|
+
if (!results.javascript.hasJSDoc) {
|
|
555
726
|
results.recommendations.push({
|
|
556
|
-
type: '
|
|
557
|
-
priority: '
|
|
558
|
-
message: 'No
|
|
559
|
-
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
|
|
560
732
|
});
|
|
733
|
+
}
|
|
561
734
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
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
|
|
566
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
|
+
});
|
|
567
773
|
}
|
|
568
774
|
}
|
|
569
775
|
}
|
|
570
776
|
|
|
571
|
-
//
|
|
572
|
-
|
|
573
|
-
|
|
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
|
+
}
|
|
574
790
|
|
|
575
|
-
|
|
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
|
+
}
|
|
576
799
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
800
|
+
results.python = {
|
|
801
|
+
available: pylintAvailable || blackAvailable,
|
|
802
|
+
pylint: null,
|
|
803
|
+
black: null
|
|
804
|
+
};
|
|
582
805
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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
|
+
}
|
|
816
|
+
|
|
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
|
+
});
|
|
591
846
|
|
|
592
|
-
|
|
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
|
+
}
|
|
593
866
|
|
|
594
|
-
//
|
|
595
|
-
|
|
596
|
-
|
|
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
|
+
};
|
|
877
|
+
|
|
878
|
+
log('SUCCESS', 'black formatting check passed');
|
|
879
|
+
|
|
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
|
+
}
|
|
597
897
|
|
|
598
|
-
|
|
898
|
+
} catch (error) {
|
|
899
|
+
log('ERROR', 'Python validation error', { error: error.message });
|
|
900
|
+
results.python = { available: false, error: error.message };
|
|
901
|
+
}
|
|
902
|
+
}
|
|
599
903
|
|
|
600
|
-
//
|
|
601
|
-
if (
|
|
904
|
+
// Rust Validation (cargo clippy + rustfmt)
|
|
905
|
+
if (ext === '.rs') {
|
|
602
906
|
try {
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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
|
|
611
922
|
});
|
|
923
|
+
}
|
|
612
924
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
925
|
+
results.rust = {
|
|
926
|
+
available: cargoAvailable,
|
|
927
|
+
clippy: null,
|
|
928
|
+
rustfmt: null
|
|
929
|
+
};
|
|
616
930
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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
|
+
}
|
|
620
940
|
|
|
621
|
-
|
|
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
|
+
}
|
|
622
949
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
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');
|
|
982
|
+
|
|
983
|
+
results.rust.clippy = {
|
|
984
|
+
passed: messages.length === 0,
|
|
985
|
+
warningCount: messages.length,
|
|
986
|
+
warnings: messages
|
|
987
|
+
};
|
|
629
988
|
|
|
989
|
+
log('SUCCESS', `cargo clippy complete (${messages.length} warnings)`);
|
|
990
|
+
|
|
991
|
+
// Add recommendations for clippy warnings
|
|
992
|
+
messages.slice(0, 3).forEach(msg => {
|
|
630
993
|
results.recommendations.push({
|
|
631
|
-
type: '
|
|
994
|
+
type: 'rust',
|
|
632
995
|
priority: 'medium',
|
|
633
|
-
message: `
|
|
634
|
-
action:
|
|
635
|
-
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
// Critical threshold: 40 - invoke lizard for detailed analysis
|
|
639
|
-
if (complexity >= 40) {
|
|
640
|
-
log('COMPLEXITY_CRITICAL', `High complexity detected: ${complexity}, invoking lizard`, {
|
|
641
|
-
threshold: 40,
|
|
642
|
-
complexity
|
|
996
|
+
message: `clippy: ${msg.message.message}`,
|
|
997
|
+
action: msg.message.rendered || 'Review clippy suggestion',
|
|
998
|
+
line: msg.message.spans?.[0]?.line_start || null
|
|
643
999
|
});
|
|
1000
|
+
});
|
|
644
1001
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
if (lizardCheck.status === 0) {
|
|
649
|
-
// Run lizard for detailed analysis
|
|
650
|
-
const lizardResult = spawnSync('lizard', [
|
|
651
|
-
filePath,
|
|
652
|
-
'-C', '15' // Show functions with complexity >15
|
|
653
|
-
], {
|
|
654
|
-
encoding: 'utf-8',
|
|
655
|
-
timeout: 10000
|
|
656
|
-
});
|
|
657
|
-
|
|
658
|
-
if (lizardResult.status === 0) {
|
|
659
|
-
const lizardOutput = lizardResult.stdout;
|
|
660
|
-
|
|
661
|
-
log('LIZARD_ANALYSIS', 'Detailed complexity analysis', {
|
|
662
|
-
output: lizardOutput
|
|
663
|
-
});
|
|
664
|
-
|
|
665
|
-
results.complexityAnalysis = {
|
|
666
|
-
tool: 'lizard',
|
|
667
|
-
complexity,
|
|
668
|
-
detailedReport: lizardOutput
|
|
669
|
-
};
|
|
670
|
-
|
|
671
|
-
results.recommendations.push({
|
|
672
|
-
type: 'complexity',
|
|
673
|
-
priority: 'critical',
|
|
674
|
-
message: `Critical complexity level: ${complexity} (threshold: 40)`,
|
|
675
|
-
action: 'Refactor immediately. Run cyclomatic-complexity-reducer agent',
|
|
676
|
-
details: lizardOutput
|
|
677
|
-
});
|
|
678
|
-
} else {
|
|
679
|
-
log('WARN', 'Lizard analysis failed', {
|
|
680
|
-
stderr: lizardResult.stderr
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
} else {
|
|
684
|
-
log('WARN', 'Lizard not installed, skipping detailed analysis');
|
|
685
|
-
|
|
686
|
-
results.recommendations.push({
|
|
687
|
-
type: 'complexity',
|
|
688
|
-
priority: 'critical',
|
|
689
|
-
message: `Critical complexity level: ${complexity} (threshold: 40)`,
|
|
690
|
-
action: 'Refactor immediately. Install lizard: ./scripts/install-lizard.sh'
|
|
691
|
-
});
|
|
692
|
-
}
|
|
693
|
-
}
|
|
1002
|
+
} catch (error) {
|
|
1003
|
+
log('SKIP', 'cargo clippy requires Cargo project context');
|
|
1004
|
+
results.rust.clippy = { passed: true, note: 'Requires Cargo project' };
|
|
694
1005
|
}
|
|
695
|
-
} else {
|
|
696
|
-
log('WARN', 'Complexity analysis failed', {
|
|
697
|
-
stderr: complexityResult.stderr
|
|
698
|
-
});
|
|
699
1006
|
}
|
|
700
|
-
}
|
|
701
|
-
// For TypeScript/JavaScript, use lizard directly if available
|
|
702
|
-
else if (ext.match(/\.(js|ts|jsx|tsx)$/)) {
|
|
703
|
-
const lizardCheck = spawnSync('which', ['lizard'], { encoding: 'utf-8' });
|
|
704
|
-
|
|
705
|
-
if (lizardCheck.status === 0) {
|
|
706
|
-
const lizardResult = spawnSync('lizard', [
|
|
707
|
-
filePath,
|
|
708
|
-
'--json'
|
|
709
|
-
], {
|
|
710
|
-
encoding: 'utf-8',
|
|
711
|
-
timeout: 10000
|
|
712
|
-
});
|
|
713
1007
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
action: avgComplexity >= 40
|
|
743
|
-
? 'Critical: Refactor high-complexity functions immediately'
|
|
744
|
-
: 'Consider refactoring complex functions'
|
|
745
|
-
});
|
|
746
|
-
}
|
|
747
|
-
} catch (parseError) {
|
|
748
|
-
log('WARN', 'Failed to parse lizard JSON output', {
|
|
749
|
-
error: parseError.message
|
|
750
|
-
});
|
|
751
|
-
}
|
|
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
|
+
});
|
|
752
1036
|
}
|
|
753
1037
|
}
|
|
754
1038
|
}
|
|
1039
|
+
|
|
755
1040
|
} catch (error) {
|
|
756
|
-
log('
|
|
757
|
-
|
|
758
|
-
});
|
|
1041
|
+
log('ERROR', 'Rust validation error', { error: error.message });
|
|
1042
|
+
results.rust = { available: false, error: error.message };
|
|
759
1043
|
}
|
|
760
1044
|
}
|
|
761
1045
|
|
|
762
|
-
//
|
|
763
|
-
if (ext === '.
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
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
|
+
}
|
|
773
1066
|
|
|
774
|
-
results.
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
});
|
|
1067
|
+
results.go = {
|
|
1068
|
+
available: goAvailable,
|
|
1069
|
+
gofmt: null,
|
|
1070
|
+
govet: null
|
|
1071
|
+
};
|
|
780
1072
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
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
|
+
}
|
|
784
1098
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
1099
|
+
} catch (error) {
|
|
1100
|
+
results.go.gofmt = { passed: false, error: error.message };
|
|
1101
|
+
}
|
|
788
1102
|
|
|
789
|
-
|
|
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
|
+
}
|
|
790
1139
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
priority: 'medium',
|
|
796
|
-
message: 'Avoid using "any" type when possible',
|
|
797
|
-
action: 'Use specific types or unknown for better type safety'
|
|
798
|
-
});
|
|
1140
|
+
} catch (error) {
|
|
1141
|
+
log('ERROR', 'Go validation error', { error: error.message });
|
|
1142
|
+
results.go = { available: false, error: error.message };
|
|
1143
|
+
}
|
|
799
1144
|
}
|
|
800
1145
|
|
|
801
|
-
//
|
|
802
|
-
if (
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
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
|
+
}
|
|
809
1190
|
}
|
|
810
1191
|
|
|
811
|
-
|
|
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
|
+
};
|
|
812
1215
|
|
|
813
|
-
//
|
|
814
|
-
|
|
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
|
|
815
1287
|
// ============================================================================
|
|
816
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';
|
|
817
1297
|
let exitCode = 0;
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
const hasBashValidatorError = results.bashValidators && results.bashValidators.errors > 0;
|
|
825
|
-
const hasBashValidatorWarning = results.bashValidators && results.bashValidators.warnings > 0;
|
|
826
|
-
|
|
827
|
-
// Check for SQL injection issues
|
|
828
|
-
const hasSqlInjectionCritical = results.sqlInjection && !results.sqlInjection.passed &&
|
|
829
|
-
results.sqlInjection.issues.some(issue => issue.severity === 'critical');
|
|
830
|
-
|
|
831
|
-
if (hasSqlInjectionCritical) {
|
|
832
|
-
exitCode = 12;
|
|
833
|
-
finalStatus = 'SQL_INJECTION_CRITICAL';
|
|
834
|
-
} else if (hasBashValidatorError) {
|
|
835
|
-
exitCode = 9;
|
|
836
|
-
finalStatus = 'BASH_VALIDATOR_ERROR';
|
|
837
|
-
} 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';
|
|
838
1304
|
exitCode = 2;
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
exitCode =
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
exitCode =
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
exitCode =
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
exitCode =
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
exitCode =
|
|
854
|
-
finalStatus = 'RUST_QUALITY';
|
|
855
|
-
} else if (results.prettier && !results.prettier.passed) {
|
|
856
|
-
exitCode = 6;
|
|
857
|
-
finalStatus = 'LINT_ISSUES';
|
|
858
|
-
} else if (results.typescript && !results.typescript.passed) {
|
|
859
|
-
exitCode = 1;
|
|
860
|
-
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
|
|
861
1320
|
} else if (results.recommendations.length > 0) {
|
|
862
|
-
|
|
1321
|
+
overallStatus = 'IMPROVEMENTS_SUGGESTED';
|
|
1322
|
+
exitCode = 0;
|
|
863
1323
|
}
|
|
864
1324
|
|
|
865
|
-
|
|
1325
|
+
// Log final summary
|
|
1326
|
+
log(overallStatus, 'Pipeline validation complete', {
|
|
866
1327
|
typescript: results.typescript,
|
|
1328
|
+
rust: results.rust,
|
|
867
1329
|
eslint: results.eslint,
|
|
868
1330
|
prettier: results.prettier,
|
|
869
1331
|
security: results.security,
|
|
870
1332
|
metrics: results.metrics,
|
|
871
1333
|
recommendationCount: results.recommendations.length,
|
|
872
1334
|
topRecommendations: results.recommendations.slice(0, 3)
|
|
873
|
-
};
|
|
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));
|
|
874
1341
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
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
|
+
}
|
|
878
1347
|
}
|
|
879
|
-
|
|
880
|
-
|
|
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
|
+
}
|
|
881
1358
|
}
|
|
882
|
-
|
|
883
|
-
|
|
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
|
+
}
|
|
884
1365
|
}
|
|
885
|
-
|
|
886
|
-
|
|
1366
|
+
|
|
1367
|
+
if (results.prettier && results.prettier.available) {
|
|
1368
|
+
console.error(`Prettier: ${results.prettier.passed ? '✅ PASSED' : '⚠️ NEEDS FORMATTING'}`);
|
|
887
1369
|
}
|
|
888
|
-
|
|
889
|
-
|
|
1370
|
+
|
|
1371
|
+
console.error(`\nSecurity: ${results.security.passed ? '✅ NO ISSUES' : '❌ ISSUES FOUND'}`);
|
|
1372
|
+
if (!results.security.passed) {
|
|
1373
|
+
console.error(` Issues: ${results.security.issueCount}`);
|
|
890
1374
|
}
|
|
891
|
-
|
|
892
|
-
|
|
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
|
+
}
|
|
893
1393
|
}
|
|
894
1394
|
|
|
895
|
-
|
|
1395
|
+
console.error('='.repeat(80) + '\n');
|
|
896
1396
|
|
|
897
|
-
process.exit(exitCode);
|
|
1397
|
+
process.exit(exitCode);
|