claude-flow-novice 2.18.24 → 2.18.25

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.
@@ -0,0 +1,858 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Enhanced Post-Edit Pipeline - Comprehensive Validation Hook
4
+ * Validates edited files with TypeScript, ESLint, Prettier, Security Analysis, and Code Metrics
5
+ *
6
+ * Features:
7
+ * - TypeScript validation with error categorization
8
+ * - ESLint integration for code quality
9
+ * - Prettier formatting checks
10
+ * - Security analysis (integrated security scanner)
11
+ * - Code metrics (lines, functions, classes, complexity)
12
+ * - Actionable recommendations engine
13
+ *
14
+ * Usage: node config/hooks/post-edit-pipeline.js <file_path> [--memory-key <key>] [--agent-id <id>]
15
+ */
16
+
17
+ import { spawnSync } from 'child_process';
18
+ import { existsSync, readFileSync, appendFileSync, mkdirSync } from 'fs';
19
+ import { dirname, extname, resolve } from 'path';
20
+
21
+ // Parse arguments
22
+ const args = process.argv.slice(2);
23
+ const filePath = args[0];
24
+ const memoryKeyIndex = args.indexOf('--memory-key');
25
+ const memoryKey = memoryKeyIndex >= 0 ? args[memoryKeyIndex + 1] : null;
26
+ const agentIdIndex = args.indexOf('--agent-id');
27
+ const agentId = agentIdIndex >= 0 ? args[agentIdIndex + 1] : null;
28
+
29
+ if (!filePath) {
30
+ console.error('Error: File path required');
31
+ console.error('Usage: node config/hooks/post-edit-pipeline.js <file_path> [--memory-key <key>] [--agent-id <id>]');
32
+ process.exit(1);
33
+ }
34
+
35
+ // Ensure log directory exists
36
+ const logDir = '.artifacts/logs';
37
+ if (!existsSync(logDir)) {
38
+ mkdirSync(logDir, { recursive: true });
39
+ }
40
+
41
+ const logFile = `${logDir}/post-edit-pipeline.log`;
42
+
43
+ function log(status, message, metadata = {}) {
44
+ const entry = JSON.stringify({
45
+ timestamp: new Date().toISOString(),
46
+ file: filePath,
47
+ status,
48
+ message,
49
+ memoryKey,
50
+ agentId,
51
+ ...metadata
52
+ });
53
+ console.log(entry);
54
+ appendFileSync(logFile, entry + '\n');
55
+ }
56
+
57
+ // Check if file exists
58
+ if (!existsSync(filePath)) {
59
+ log('ERROR', 'File not found', { path: filePath });
60
+ process.exit(1);
61
+ }
62
+
63
+ // Read file content for analysis
64
+ const fileContent = readFileSync(filePath, 'utf-8');
65
+ const ext = extname(filePath);
66
+ const baseName = filePath.replace(ext, '').split('/').pop();
67
+
68
+ // Initialize results object
69
+ const results = {
70
+ typescript: null,
71
+ eslint: null,
72
+ prettier: null,
73
+ security: null,
74
+ metrics: null,
75
+ recommendations: []
76
+ };
77
+
78
+ // [Remaining TypeScript, ESLint, and Prettier validation code remains the same]
79
+
80
+ // ============================================================================
81
+ // PHASE 2: Security Analysis
82
+ // ============================================================================
83
+
84
+ log('VALIDATING', 'Running security analysis');
85
+
86
+ try {
87
+ // Primary scanner method: security scanner script
88
+ const securityScanProcess = spawnSync('bash', [
89
+ '.claude/skills/hook-pipeline/security-scanner.sh',
90
+ filePath
91
+ ], {
92
+ encoding: 'utf-8',
93
+ timeout: 10000
94
+ });
95
+
96
+ const securityScanOutput = securityScanProcess.stdout || '{}';
97
+ const exitCode = securityScanProcess.status;
98
+
99
+ log('DEBUG', 'Security scanner output', {
100
+ stdout: securityScanOutput,
101
+ stderr: securityScanProcess.stderr,
102
+ exitCode: exitCode
103
+ });
104
+
105
+ try {
106
+ const securityScanResults = JSON.parse(securityScanOutput);
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
+ };
116
+
117
+ if (results.security.issues.length > 0) {
118
+ log('SECURITY_WARNING', `Security scanner detected ${results.security.issues.length} vulnerabilities`, {
119
+ confidence: results.security.confidence,
120
+ issueTypes: results.security.issues
121
+ });
122
+
123
+ // Transform scanner issues into recommendations
124
+ results.security.issues.slice(0, 3).forEach(vuln => {
125
+ results.recommendations.push({
126
+ type: 'security',
127
+ priority: 'critical',
128
+ message: `Security vulnerability: ${vuln}`,
129
+ action: `Review and remediate ${vuln} vulnerability`
130
+ });
131
+ });
132
+
133
+ // Add general security warning
134
+ results.recommendations.push({
135
+ type: 'security',
136
+ priority: 'critical',
137
+ message: 'Security vulnerabilities detected by security scanner',
138
+ action: 'Conduct thorough security review and address all vulnerabilities'
139
+ });
140
+ } else {
141
+ log('SUCCESS', 'No security vulnerabilities detected');
142
+ }
143
+ } catch (parseError) {
144
+ log('ERROR', 'Failed to parse security scanner output', {
145
+ parseError: parseError.message,
146
+ output: securityScanOutput
147
+ });
148
+
149
+ // Fallback vulnerability detection (minimal built-in checks)
150
+ const builtinChecks = [
151
+ {
152
+ pattern: /eval\(/,
153
+ vulnerability: 'POTENTIAL_RCE',
154
+ severity: 'critical'
155
+ },
156
+ {
157
+ pattern: /innerHTML\s*=/,
158
+ vulnerability: 'XSS_POTENTIAL',
159
+ severity: 'high'
160
+ },
161
+ {
162
+ pattern: /(password|secret|token|api[-_]?key|anthropic|openai|openrouter|kimi|npm[-_]?token|zai|z[-_]ai).*=.*['"]?[^'"\s]{20,}['"]?/i,
163
+ vulnerability: 'HARDCODED_SECRET',
164
+ severity: 'critical'
165
+ }
166
+ ];
167
+
168
+ const foundVulnerabilities = builtinChecks
169
+ .filter(check => check.pattern.test(fileContent))
170
+ .map(check => ({
171
+ type: check.vulnerability,
172
+ severity: check.severity
173
+ }));
174
+
175
+ results.security = {
176
+ passed: foundVulnerabilities.length === 0,
177
+ confidence: 50,
178
+ issues: foundVulnerabilities,
179
+ details: 'Fallback vulnerability detection'
180
+ };
181
+
182
+ if (foundVulnerabilities.length > 0) {
183
+ log('SECURITY_WARNING', 'Vulnerabilities detected by fallback method', {
184
+ vulnerabilities: foundVulnerabilities
185
+ });
186
+
187
+ foundVulnerabilities.forEach(vuln => {
188
+ results.recommendations.push({
189
+ type: 'security',
190
+ priority: vuln.severity === 'critical' ? 'critical' : 'high',
191
+ message: `Potential ${vuln.type} vulnerability detected`,
192
+ action: `Manually review code for ${vuln.type} vulnerability`
193
+ });
194
+ });
195
+ }
196
+ }
197
+ } catch (error) {
198
+ log('CRITICAL_ERROR', 'Unexpected security scanning failure', {
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
+ });
216
+ }
217
+
218
+ // ============================================================================
219
+ // PHASE 2.5: Bash Validator Integration
220
+ // ============================================================================
221
+
222
+ log('VALIDATING', 'Running bash validators');
223
+
224
+ // Validator mapping by file extension
225
+ const validatorsByExtension = {
226
+ '.sh': [
227
+ 'bash-pipe-safety.sh',
228
+ 'bash-dependency-checker.sh',
229
+ 'enforce-lf.sh'
230
+ ],
231
+ '.bash': [
232
+ 'bash-pipe-safety.sh',
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
+ };
265
+
266
+ // Helper function to run a single validator
267
+ function runValidator(validatorName, targetFile) {
268
+ const validatorPath = `.claude/skills/hook-pipeline/${validatorName}`;
269
+
270
+ log('DEBUG', `Executing validator: ${validatorName}`, { targetFile });
271
+
272
+ try {
273
+ // Determine interpreter based on file extension
274
+ const isPython = validatorName.endsWith('.py');
275
+ const interpreter = isPython ? 'python3' : 'bash';
276
+
277
+ const result = spawnSync(interpreter, [validatorPath, targetFile], {
278
+ encoding: 'utf-8',
279
+ timeout: 5000,
280
+ cwd: process.cwd()
281
+ });
282
+
283
+ const exitCode = result.status;
284
+ const stdout = (result.stdout || '').trim();
285
+ const stderr = (result.stderr || '').trim();
286
+
287
+ log('DEBUG', `Validator ${validatorName} completed`, {
288
+ exitCode,
289
+ stdout: stdout.substring(0, 200), // Truncate for logging
290
+ stderr: stderr.substring(0, 200)
291
+ });
292
+
293
+ // Exit code convention:
294
+ // 0 = pass (no issues)
295
+ // 1 = error (blocking issue)
296
+ // 2 = warning (non-blocking issue)
297
+ return {
298
+ validator: validatorName,
299
+ exitCode,
300
+ passed: exitCode === 0,
301
+ isBlocking: exitCode === 1,
302
+ isWarning: exitCode === 2,
303
+ message: stderr || stdout || 'Validator passed',
304
+ stdout,
305
+ stderr
306
+ };
307
+ } catch (error) {
308
+ log('ERROR', `Validator ${validatorName} execution failed`, {
309
+ error: error.message,
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
+ };
322
+ }
323
+ }
324
+
325
+ // Run validators for applicable file types
326
+ const applicableValidators = validatorsByExtension[ext] || [];
327
+
328
+ if (applicableValidators.length > 0) {
329
+ log('INFO', `Running ${applicableValidators.length} bash validators for ${ext} file`);
330
+
331
+ // Sequential execution of validators
332
+ const validatorResults = applicableValidators.map(validator =>
333
+ runValidator(validator, filePath)
334
+ );
335
+
336
+ // Process validator results
337
+ validatorResults.forEach(result => {
338
+ if (result.isBlocking) {
339
+ // Blocking error (exit code 1)
340
+ log('VALIDATOR_ERROR', `Blocking issue detected by ${result.validator}`, {
341
+ message: result.message
342
+ });
343
+
344
+ results.recommendations.push({
345
+ type: 'bash-validator',
346
+ priority: 'critical',
347
+ message: `${result.validator}: ${result.message}`,
348
+ action: 'Fix blocking issue before proceeding'
349
+ });
350
+ } else if (result.isWarning) {
351
+ // Warning (exit code 2)
352
+ log('VALIDATOR_WARNING', `Warning from ${result.validator}`, {
353
+ message: result.message
354
+ });
355
+
356
+ results.recommendations.push({
357
+ type: 'bash-safety',
358
+ priority: 'medium',
359
+ message: `${result.validator}: ${result.message}`,
360
+ action: 'Review recommendations and consider fixing'
361
+ });
362
+ } else if (result.passed) {
363
+ // Pass (exit code 0)
364
+ log('SUCCESS', `Validator ${result.validator} passed`);
365
+ }
366
+ });
367
+
368
+ // Store validator results for exit code determination
369
+ results.bashValidators = {
370
+ executed: validatorResults.length,
371
+ passed: validatorResults.filter(r => r.passed).length,
372
+ warnings: validatorResults.filter(r => r.isWarning).length,
373
+ errors: validatorResults.filter(r => r.isBlocking).length,
374
+ results: validatorResults
375
+ };
376
+
377
+ log('SUCCESS', `Bash validators completed`, {
378
+ executed: results.bashValidators.executed,
379
+ passed: results.bashValidators.passed,
380
+ warnings: results.bashValidators.warnings,
381
+ errors: results.bashValidators.errors
382
+ });
383
+ } else {
384
+ log('DEBUG', `No bash validators configured for ${ext} files`);
385
+ }
386
+
387
+ // ============================================================================
388
+ // PHASE 2.6: SQL Injection Detection
389
+ // ============================================================================
390
+
391
+ if (ext.match(/\.(sql|ts|js|py)$/)) {
392
+ log('VALIDATING', 'Running SQL injection detection');
393
+
394
+ const sqlInjectionPatterns = [
395
+ // String concatenation in queries
396
+ { pattern: /['"`]\s*\+\s*\w+\s*\+\s*['"`].*(?:SELECT|INSERT|UPDATE|DELETE|WHERE)/i, risk: 'STRING_CONCATENATION', severity: 'critical' },
397
+ { pattern: /(?:SELECT|INSERT|UPDATE|DELETE|WHERE).*['"`]\s*\+\s*\w+/i, risk: 'STRING_CONCATENATION', severity: 'critical' },
398
+
399
+ // Template literals with variables in SQL
400
+ { pattern: /\$\{[^}]+\}.*(?:SELECT|INSERT|UPDATE|DELETE|WHERE)/i, risk: 'TEMPLATE_INJECTION', severity: 'high' },
401
+
402
+ // f-strings in Python SQL
403
+ { pattern: /f['"].*(?:SELECT|INSERT|UPDATE|DELETE|WHERE).*\{/i, risk: 'FSTRING_SQL_INJECTION', severity: 'critical' },
404
+
405
+ // .format() in Python SQL
406
+ { pattern: /['"].*(?:SELECT|INSERT|UPDATE|DELETE).*['"]\.format\(/i, risk: 'FORMAT_SQL_INJECTION', severity: 'critical' },
407
+
408
+ // Raw user input in query
409
+ { pattern: /(?:req\.body|req\.query|req\.params|request\.form|request\.args)\.[^\s]+.*(?:SELECT|INSERT|UPDATE|DELETE)/i, risk: 'UNSANITIZED_INPUT', severity: 'critical' },
410
+
411
+ // execute() with string concatenation
412
+ { pattern: /\.execute\s*\(\s*['"`].*\+/i, risk: 'EXECUTE_CONCATENATION', severity: 'critical' },
413
+ { pattern: /\.execute\s*\(\s*f['"`]/i, risk: 'EXECUTE_FSTRING', severity: 'critical' },
414
+ ];
415
+
416
+ const sqlInjectionIssues = sqlInjectionPatterns
417
+ .filter(check => check.pattern.test(fileContent))
418
+ .map(check => ({ type: check.risk, severity: check.severity }));
419
+
420
+ if (sqlInjectionIssues.length > 0) {
421
+ log('SQL_INJECTION_WARNING', `Found ${sqlInjectionIssues.length} potential SQL injection risks`, {
422
+ issues: sqlInjectionIssues
423
+ });
424
+
425
+ results.sqlInjection = {
426
+ passed: false,
427
+ issues: sqlInjectionIssues
428
+ };
429
+
430
+ sqlInjectionIssues.forEach(issue => {
431
+ results.recommendations.push({
432
+ type: 'sql-injection',
433
+ priority: issue.severity,
434
+ message: `Potential SQL injection: ${issue.type}`,
435
+ action: 'Use parameterized queries ($1, $2) or prepared statements instead of string concatenation'
436
+ });
437
+ });
438
+ } else {
439
+ results.sqlInjection = { passed: true, issues: [] };
440
+ log('SUCCESS', 'No SQL injection risks detected');
441
+ }
442
+ }
443
+
444
+ // ============================================================================
445
+ // PHASE 3: Root Directory Detection
446
+ // ============================================================================
447
+
448
+ log('VALIDATING', 'Checking file location (root directory warning)');
449
+
450
+ const isRootFile = dirname(resolve(filePath)) === resolve('.');
451
+ if (isRootFile && !filePath.match(/^(package\.json|tsconfig\.json|\.gitignore|\.env.*|README\.md|LICENSE|CLAUDE\.md)$/)) {
452
+ // Suggest appropriate location based on file type
453
+ const suggestions = [];
454
+ if (ext.match(/\.(js|ts|jsx|tsx)$/)) {
455
+ suggestions.push({ location: `src/${filePath}`, reason: 'Source files belong in src/' });
456
+ }
457
+ if (ext.match(/\.(test|spec)\.(js|ts|jsx|tsx)$/)) {
458
+ suggestions.push({ location: `tests/${filePath}`, reason: 'Test files belong in tests/' });
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
+ }
469
+
470
+ if (suggestions.length > 0) {
471
+ log('ROOT_WARNING', 'File in root directory - should be organized', {
472
+ file: filePath,
473
+ suggestions
474
+ });
475
+
476
+ results.recommendations.push({
477
+ type: 'organization',
478
+ priority: 'high',
479
+ message: `File "${filePath}" should not be in root directory`,
480
+ action: `Move to: ${suggestions[0].location}`,
481
+ suggestions
482
+ });
483
+
484
+ // Store for handler processing
485
+ results.rootWarning = { suggestions };
486
+ }
487
+ }
488
+
489
+ // ============================================================================
490
+ // PHASE 4: TDD Violation Detection
491
+ // ============================================================================
492
+
493
+ if (ext.match(/\.(js|ts|jsx|tsx|py|go|rs)$/) && !filePath.match(/\.(test|spec)\./)) {
494
+ log('VALIDATING', 'Checking TDD compliance');
495
+
496
+ const testPatterns = {
497
+ js: [`${dirname(filePath)}/${baseName}.test.js`, `tests/${baseName}.test.js`],
498
+ ts: [`${dirname(filePath)}/${baseName}.test.ts`, `tests/${baseName}.test.ts`],
499
+ py: [`${dirname(filePath)}/test_${baseName}.py`, `tests/test_${baseName}.py`],
500
+ go: [`${dirname(filePath)}/${baseName}_test.go`],
501
+ rs: null // Rust uses inline tests
502
+ };
503
+
504
+ const langKey = ext.replace('.', '');
505
+ const patterns = testPatterns[langKey];
506
+
507
+ if (patterns) {
508
+ const hasTest = patterns.some(p => existsSync(p));
509
+
510
+ if (!hasTest) {
511
+ log('TDD_VIOLATION', 'No test file found', {
512
+ file: filePath,
513
+ expectedLocations: patterns
514
+ });
515
+
516
+ results.recommendations.push({
517
+ type: 'testing',
518
+ priority: 'high',
519
+ message: 'No test file found for this module',
520
+ action: 'Create test file or run feedback-resolver.sh --type TDD_VIOLATION'
521
+ });
522
+
523
+ results.tddViolation = {
524
+ hasTests: false,
525
+ testFile: patterns[0],
526
+ recommendations: [`Create ${patterns[0]}`]
527
+ };
528
+ }
529
+ }
530
+ }
531
+
532
+ // ============================================================================
533
+ // PHASE 5: Code Metrics and Complexity Analysis
534
+ // ============================================================================
535
+
536
+ log('VALIDATING', 'Calculating code metrics');
537
+
538
+ const lines = fileContent.split('\n').length;
539
+ const functions = (fileContent.match(/function\s+\w+|const\s+\w+\s*=\s*\(/g) || []).length;
540
+ const classes = (fileContent.match(/class\s+\w+/g) || []).length;
541
+ const todos = (fileContent.match(/\/\/\s*TODO/gi) || []).length;
542
+ const fixmes = (fileContent.match(/\/\/\s*FIXME/gi) || []).length;
543
+
544
+ results.metrics = {
545
+ lines,
546
+ functions,
547
+ classes,
548
+ todos,
549
+ fixmes,
550
+ complexity: lines > 300 ? 'high' : lines > 100 ? 'medium' : 'low'
551
+ };
552
+
553
+ log('SUCCESS', 'Code metrics calculated', results.metrics);
554
+
555
+ // ============================================================================
556
+ // PHASE 5.1: Cyclomatic Complexity Analysis
557
+ // ============================================================================
558
+
559
+ log('VALIDATING', 'Analyzing cyclomatic complexity');
560
+
561
+ // Only analyze files >200 lines to reduce overhead
562
+ if (lines > 200 && ext.match(/\.(sh|js|ts|jsx|tsx|py)$/)) {
563
+ try {
564
+ // Use simple-complexity.sh for bash scripts
565
+ if (ext === '.sh') {
566
+ const complexityResult = spawnSync('bash', [
567
+ 'scripts/simple-complexity.sh',
568
+ filePath
569
+ ], {
570
+ encoding: 'utf-8',
571
+ timeout: 5000
572
+ });
573
+
574
+ if (complexityResult.status === 0) {
575
+ const output = complexityResult.stdout;
576
+ const complexityMatch = output.match(/Total Complexity:\s*(\d+)/);
577
+
578
+ if (complexityMatch) {
579
+ const complexity = parseInt(complexityMatch[1], 10);
580
+ results.metrics.cyclomaticComplexity = complexity;
581
+
582
+ log('SUCCESS', `Cyclomatic complexity: ${complexity}`, { complexity });
583
+
584
+ // Warning threshold: 30
585
+ if (complexity >= 30 && complexity < 40) {
586
+ log('COMPLEXITY_WARNING', `Moderate complexity detected: ${complexity}`, {
587
+ threshold: 30,
588
+ complexity
589
+ });
590
+
591
+ results.recommendations.push({
592
+ type: 'complexity',
593
+ priority: 'medium',
594
+ message: `Cyclomatic complexity is ${complexity} (threshold: 30)`,
595
+ action: 'Consider refactoring to reduce complexity'
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
604
+ });
605
+
606
+ // Check if lizard is available
607
+ const lizardCheck = spawnSync('which', ['lizard'], { encoding: 'utf-8' });
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
+ }
655
+ }
656
+ } else {
657
+ log('WARN', 'Complexity analysis failed', {
658
+ stderr: complexityResult.stderr
659
+ });
660
+ }
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
+
675
+ if (lizardResult.status === 0) {
676
+ try {
677
+ const lizardData = JSON.parse(lizardResult.stdout);
678
+
679
+ // Calculate average complexity
680
+ let totalComplexity = 0;
681
+ let functionCount = 0;
682
+
683
+ if (lizardData.function_list) {
684
+ lizardData.function_list.forEach(func => {
685
+ totalComplexity += func.cyclomatic_complexity || 0;
686
+ functionCount++;
687
+ });
688
+ }
689
+
690
+ const avgComplexity = functionCount > 0 ? Math.round(totalComplexity / functionCount) : 0;
691
+ results.metrics.cyclomaticComplexity = avgComplexity;
692
+
693
+ if (avgComplexity >= 30) {
694
+ log('COMPLEXITY_WARNING', `Average complexity: ${avgComplexity}`, {
695
+ avgComplexity,
696
+ functionCount
697
+ });
698
+
699
+ results.recommendations.push({
700
+ type: 'complexity',
701
+ priority: avgComplexity >= 40 ? 'critical' : 'medium',
702
+ message: `Average cyclomatic complexity: ${avgComplexity}`,
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
+ }
713
+ }
714
+ }
715
+ }
716
+ } catch (error) {
717
+ log('WARN', 'Complexity analysis error', {
718
+ error: error.message
719
+ });
720
+ }
721
+ }
722
+
723
+ // Check for Rust-specific quality issues
724
+ if (ext === '.rs') {
725
+ log('VALIDATING', 'Running Rust quality checks');
726
+
727
+ const rustIssues = [];
728
+ if (fileContent.match(/println!\(/)) rustIssues.push('debug_println');
729
+ if (fileContent.match(/unwrap\(\)/)) rustIssues.push('unwrap_usage');
730
+ if (fileContent.match(/panic!\(/)) rustIssues.push('panic_usage');
731
+
732
+ if (rustIssues.length > 0) {
733
+ log('RUST_QUALITY', 'Rust quality issues detected', { issues: rustIssues });
734
+
735
+ results.recommendations.push({
736
+ type: 'rust',
737
+ priority: 'medium',
738
+ message: 'Rust quality issues detected',
739
+ action: 'Run: cargo fmt && cargo clippy --fix --allow-dirty'
740
+ });
741
+
742
+ results.rustQuality = { issues: rustIssues };
743
+ }
744
+ }
745
+
746
+ // ============================================================================
747
+ // PHASE 6: Final Recommendations
748
+ // ============================================================================
749
+
750
+ log('VALIDATING', 'Generating recommendations');
751
+
752
+ // Type safety recommendations
753
+ if (ext.match(/\.(ts|tsx)$/) && fileContent.match(/:\s*any\b/)) {
754
+ results.recommendations.push({
755
+ type: 'typescript',
756
+ priority: 'medium',
757
+ message: 'Avoid using "any" type when possible',
758
+ action: 'Use specific types or unknown for better type safety'
759
+ });
760
+ }
761
+
762
+ // Testing recommendations
763
+ if (!filePath.match(/\.(test|spec)\./)) {
764
+ results.recommendations.push({
765
+ type: 'testing',
766
+ priority: 'medium',
767
+ message: 'Consider writing tests for this module',
768
+ action: 'Create corresponding test file to ensure code reliability'
769
+ });
770
+ }
771
+
772
+ log('SUCCESS', `Generated ${results.recommendations.length} recommendations`);
773
+
774
+ // ============================================================================
775
+ // PHASE 7: Exit Code Determination
776
+ // ============================================================================
777
+
778
+ let exitCode = 0;
779
+ let finalStatus = 'SUCCESS';
780
+
781
+ // Check for critical complexity issues
782
+ const hasComplexityIssue = results.recommendations.find(r => r.type === 'complexity');
783
+
784
+ // Check for bash validator issues
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) {
799
+ exitCode = 2;
800
+ finalStatus = 'ROOT_WARNING';
801
+ } else if (results.tddViolation) {
802
+ exitCode = 3;
803
+ finalStatus = 'TDD_VIOLATION';
804
+ } else if (hasBashValidatorWarning) {
805
+ exitCode = 10;
806
+ finalStatus = 'BASH_VALIDATOR_WARNING';
807
+ } else if (hasComplexityIssue && hasComplexityIssue.priority === 'critical') {
808
+ exitCode = 7;
809
+ finalStatus = 'COMPLEXITY_CRITICAL';
810
+ } else if (hasComplexityIssue && hasComplexityIssue.priority === 'medium') {
811
+ exitCode = 8;
812
+ finalStatus = 'COMPLEXITY_WARNING';
813
+ } else if (results.rustQuality) {
814
+ exitCode = 5;
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';
822
+ } else if (results.recommendations.length > 0) {
823
+ finalStatus = 'IMPROVEMENTS_SUGGESTED';
824
+ }
825
+
826
+ const finalResult = {
827
+ typescript: results.typescript,
828
+ eslint: results.eslint,
829
+ prettier: results.prettier,
830
+ security: results.security,
831
+ metrics: results.metrics,
832
+ recommendationCount: results.recommendations.length,
833
+ topRecommendations: results.recommendations.slice(0, 3)
834
+ };
835
+
836
+ // Include structured data for feedback handlers
837
+ if (results.sqlInjection) {
838
+ finalResult.sqlInjection = results.sqlInjection;
839
+ }
840
+ if (results.rootWarning) {
841
+ finalResult.rootWarning = results.rootWarning;
842
+ }
843
+ if (results.tddViolation) {
844
+ finalResult.tddViolation = results.tddViolation;
845
+ }
846
+ if (results.bashValidators) {
847
+ finalResult.bashValidators = results.bashValidators;
848
+ }
849
+ if (results.rustQuality) {
850
+ finalResult.rustQuality = results.rustQuality;
851
+ }
852
+ if (results.complexityAnalysis) {
853
+ finalResult.complexityAnalysis = results.complexityAnalysis;
854
+ }
855
+
856
+ log(finalStatus, 'Pipeline validation complete', finalResult);
857
+
858
+ process.exit(exitCode);