claude-flow-novice 2.18.24 → 2.18.26

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,897 @@
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
+ * TYPESCRIPT ERROR DETECTION - WHAT'S CAUGHT vs NOT CAUGHT
18
+ * ============================================================================
19
+ *
20
+ * This pipeline runs: `npx tsc --noEmit --skipLibCheck ${filePath}`
21
+ * It checks the EDITED FILE + its IMPORTS (transitively), but NOT files that
22
+ * import the edited file.
23
+ *
24
+ * ✅ CAUGHT (Blocks Edit):
25
+ * ┌────────────────────┬─────────────┬─────────────────────────────────────────┐
26
+ * │ Error Type │ Code │ Example │
27
+ * ├────────────────────┼─────────────┼─────────────────────────────────────────┤
28
+ * │ Missing module │ TS2307 │ import { x } from './nonexistent' │
29
+ * │ Missing export │ TS2305 │ import { nonExistent } from './exists' │
30
+ * │ Typo in export │ TS2724 │ import { Uzer } from './user' │
31
+ * │ Syntax errors │ TS1005,1128 │ Missing ), unexpected token │
32
+ * │ Unused imports │ TS6133,6196 │ import { unused } from... │
33
+ * │ Type mismatches │ TS2322,2345 │ Wrong type assignment │
34
+ * │ Missing properties │ TS2339,2353 │ obj.nonExistentProp │
35
+ * └────────────────────┴─────────────┴─────────────────────────────────────────┘
36
+ *
37
+ * ❌ NOT CAUGHT (Single-File Limitation):
38
+ * ┌─────────────────────────────────┬────────────────────────────────────────────┐
39
+ * │ Scenario │ Why │
40
+ * ├─────────────────────────────────┼────────────────────────────────────────────┤
41
+ * │ Cross-file type changes │ Editing types.ts won't catch breaks in │
42
+ * │ │ other.ts that imports it │
43
+ * │ Deleted exports used elsewhere │ Consumers aren't checked until edited │
44
+ * │ Renamed functions/types │ Same - callers won't be validated │
45
+ * └─────────────────────────────────┴────────────────────────────────────────────┘
46
+ *
47
+ * Example of NOT CAUGHT:
48
+ * You edit: src/types.ts (remove UserType export)
49
+ * Pipeline: ✅ passes (types.ts is valid)
50
+ * Reality: src/user.ts imports UserType → BROKEN but not checked
51
+ *
52
+ * To catch everything: Run `npx tsc --noEmit` project-wide periodically.
53
+ * ============================================================================
54
+ */
55
+
56
+ import { spawnSync } from 'child_process';
57
+ import { existsSync, readFileSync, appendFileSync, mkdirSync } from 'fs';
58
+ import { dirname, extname, resolve } from 'path';
59
+
60
+ // Parse arguments
61
+ const args = process.argv.slice(2);
62
+ const filePath = args[0];
63
+ const memoryKeyIndex = args.indexOf('--memory-key');
64
+ const memoryKey = memoryKeyIndex >= 0 ? args[memoryKeyIndex + 1] : null;
65
+ const agentIdIndex = args.indexOf('--agent-id');
66
+ const agentId = agentIdIndex >= 0 ? args[agentIdIndex + 1] : null;
67
+
68
+ if (!filePath) {
69
+ console.error('Error: File path required');
70
+ console.error('Usage: node config/hooks/post-edit-pipeline.js <file_path> [--memory-key <key>] [--agent-id <id>]');
71
+ process.exit(1);
72
+ }
73
+
74
+ // Ensure log directory exists
75
+ const logDir = '.artifacts/logs';
76
+ if (!existsSync(logDir)) {
77
+ mkdirSync(logDir, { recursive: true });
78
+ }
79
+
80
+ const logFile = `${logDir}/post-edit-pipeline.log`;
81
+
82
+ function log(status, message, metadata = {}) {
83
+ const entry = JSON.stringify({
84
+ timestamp: new Date().toISOString(),
85
+ file: filePath,
86
+ status,
87
+ message,
88
+ memoryKey,
89
+ agentId,
90
+ ...metadata
91
+ });
92
+ console.log(entry);
93
+ appendFileSync(logFile, entry + '\n');
94
+ }
95
+
96
+ // Check if file exists
97
+ if (!existsSync(filePath)) {
98
+ log('ERROR', 'File not found', { path: filePath });
99
+ process.exit(1);
100
+ }
101
+
102
+ // Read file content for analysis
103
+ const fileContent = readFileSync(filePath, 'utf-8');
104
+ const ext = extname(filePath);
105
+ const baseName = filePath.replace(ext, '').split('/').pop();
106
+
107
+ // Initialize results object
108
+ const results = {
109
+ typescript: null,
110
+ eslint: null,
111
+ prettier: null,
112
+ security: null,
113
+ metrics: null,
114
+ recommendations: []
115
+ };
116
+
117
+ // [Remaining TypeScript, ESLint, and Prettier validation code remains the same]
118
+
119
+ // ============================================================================
120
+ // PHASE 2: Security Analysis
121
+ // ============================================================================
122
+
123
+ log('VALIDATING', 'Running security analysis');
124
+
125
+ try {
126
+ // Primary scanner method: security scanner script
127
+ const securityScanProcess = spawnSync('bash', [
128
+ '.claude/skills/hook-pipeline/security-scanner.sh',
129
+ filePath
130
+ ], {
131
+ encoding: 'utf-8',
132
+ timeout: 10000
133
+ });
134
+
135
+ const securityScanOutput = securityScanProcess.stdout || '{}';
136
+ const exitCode = securityScanProcess.status;
137
+
138
+ log('DEBUG', 'Security scanner output', {
139
+ stdout: securityScanOutput,
140
+ stderr: securityScanProcess.stderr,
141
+ exitCode: exitCode
142
+ });
143
+
144
+ try {
145
+ const securityScanResults = JSON.parse(securityScanOutput);
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
+ };
155
+
156
+ if (results.security.issues.length > 0) {
157
+ log('SECURITY_WARNING', `Security scanner detected ${results.security.issues.length} vulnerabilities`, {
158
+ confidence: results.security.confidence,
159
+ issueTypes: results.security.issues
160
+ });
161
+
162
+ // Transform scanner issues into recommendations
163
+ results.security.issues.slice(0, 3).forEach(vuln => {
164
+ results.recommendations.push({
165
+ type: 'security',
166
+ priority: 'critical',
167
+ message: `Security vulnerability: ${vuln}`,
168
+ action: `Review and remediate ${vuln} vulnerability`
169
+ });
170
+ });
171
+
172
+ // Add general security warning
173
+ results.recommendations.push({
174
+ type: 'security',
175
+ priority: 'critical',
176
+ message: 'Security vulnerabilities detected by security scanner',
177
+ action: 'Conduct thorough security review and address all vulnerabilities'
178
+ });
179
+ } else {
180
+ log('SUCCESS', 'No security vulnerabilities detected');
181
+ }
182
+ } catch (parseError) {
183
+ log('ERROR', 'Failed to parse security scanner output', {
184
+ parseError: parseError.message,
185
+ output: securityScanOutput
186
+ });
187
+
188
+ // Fallback vulnerability detection (minimal built-in checks)
189
+ const builtinChecks = [
190
+ {
191
+ pattern: /eval\(/,
192
+ vulnerability: 'POTENTIAL_RCE',
193
+ severity: 'critical'
194
+ },
195
+ {
196
+ pattern: /innerHTML\s*=/,
197
+ vulnerability: 'XSS_POTENTIAL',
198
+ severity: 'high'
199
+ },
200
+ {
201
+ pattern: /(password|secret|token|api[-_]?key|anthropic|openai|openrouter|kimi|npm[-_]?token|zai|z[-_]ai).*=.*['"]?[^'"\s]{20,}['"]?/i,
202
+ vulnerability: 'HARDCODED_SECRET',
203
+ severity: 'critical'
204
+ }
205
+ ];
206
+
207
+ const foundVulnerabilities = builtinChecks
208
+ .filter(check => check.pattern.test(fileContent))
209
+ .map(check => ({
210
+ type: check.vulnerability,
211
+ severity: check.severity
212
+ }));
213
+
214
+ results.security = {
215
+ passed: foundVulnerabilities.length === 0,
216
+ confidence: 50,
217
+ issues: foundVulnerabilities,
218
+ details: 'Fallback vulnerability detection'
219
+ };
220
+
221
+ if (foundVulnerabilities.length > 0) {
222
+ log('SECURITY_WARNING', 'Vulnerabilities detected by fallback method', {
223
+ vulnerabilities: foundVulnerabilities
224
+ });
225
+
226
+ foundVulnerabilities.forEach(vuln => {
227
+ results.recommendations.push({
228
+ type: 'security',
229
+ priority: vuln.severity === 'critical' ? 'critical' : 'high',
230
+ message: `Potential ${vuln.type} vulnerability detected`,
231
+ action: `Manually review code for ${vuln.type} vulnerability`
232
+ });
233
+ });
234
+ }
235
+ }
236
+ } catch (error) {
237
+ log('CRITICAL_ERROR', 'Unexpected security scanning failure', {
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
+ });
255
+ }
256
+
257
+ // ============================================================================
258
+ // PHASE 2.5: Bash Validator Integration
259
+ // ============================================================================
260
+
261
+ log('VALIDATING', 'Running bash validators');
262
+
263
+ // Validator mapping by file extension
264
+ const validatorsByExtension = {
265
+ '.sh': [
266
+ 'bash-pipe-safety.sh',
267
+ 'bash-dependency-checker.sh',
268
+ 'enforce-lf.sh'
269
+ ],
270
+ '.bash': [
271
+ 'bash-pipe-safety.sh',
272
+ 'bash-dependency-checker.sh',
273
+ 'enforce-lf.sh'
274
+ ],
275
+ '.py': [
276
+ 'python-subprocess-safety.py',
277
+ 'python-async-safety.py',
278
+ 'python-import-checker.py',
279
+ 'enforce-lf.sh'
280
+ ],
281
+ '.js': [
282
+ 'js-promise-safety.sh',
283
+ 'enforce-lf.sh'
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
+ };
304
+
305
+ // Helper function to run a single validator
306
+ function runValidator(validatorName, targetFile) {
307
+ const validatorPath = `.claude/skills/hook-pipeline/${validatorName}`;
308
+
309
+ log('DEBUG', `Executing validator: ${validatorName}`, { targetFile });
310
+
311
+ try {
312
+ // Determine interpreter based on file extension
313
+ const isPython = validatorName.endsWith('.py');
314
+ const interpreter = isPython ? 'python3' : 'bash';
315
+
316
+ const result = spawnSync(interpreter, [validatorPath, targetFile], {
317
+ encoding: 'utf-8',
318
+ timeout: 5000,
319
+ cwd: process.cwd()
320
+ });
321
+
322
+ const exitCode = result.status;
323
+ const stdout = (result.stdout || '').trim();
324
+ const stderr = (result.stderr || '').trim();
325
+
326
+ log('DEBUG', `Validator ${validatorName} completed`, {
327
+ exitCode,
328
+ stdout: stdout.substring(0, 200), // Truncate for logging
329
+ stderr: stderr.substring(0, 200)
330
+ });
331
+
332
+ // Exit code convention:
333
+ // 0 = pass (no issues)
334
+ // 1 = error (blocking issue)
335
+ // 2 = warning (non-blocking issue)
336
+ return {
337
+ validator: validatorName,
338
+ exitCode,
339
+ passed: exitCode === 0,
340
+ isBlocking: exitCode === 1,
341
+ isWarning: exitCode === 2,
342
+ message: stderr || stdout || 'Validator passed',
343
+ stdout,
344
+ stderr
345
+ };
346
+ } catch (error) {
347
+ log('ERROR', `Validator ${validatorName} execution failed`, {
348
+ error: error.message,
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
+ };
361
+ }
362
+ }
363
+
364
+ // Run validators for applicable file types
365
+ const applicableValidators = validatorsByExtension[ext] || [];
366
+
367
+ if (applicableValidators.length > 0) {
368
+ log('INFO', `Running ${applicableValidators.length} bash validators for ${ext} file`);
369
+
370
+ // Sequential execution of validators
371
+ const validatorResults = applicableValidators.map(validator =>
372
+ runValidator(validator, filePath)
373
+ );
374
+
375
+ // Process validator results
376
+ validatorResults.forEach(result => {
377
+ if (result.isBlocking) {
378
+ // Blocking error (exit code 1)
379
+ log('VALIDATOR_ERROR', `Blocking issue detected by ${result.validator}`, {
380
+ message: result.message
381
+ });
382
+
383
+ results.recommendations.push({
384
+ type: 'bash-validator',
385
+ priority: 'critical',
386
+ message: `${result.validator}: ${result.message}`,
387
+ action: 'Fix blocking issue before proceeding'
388
+ });
389
+ } else if (result.isWarning) {
390
+ // Warning (exit code 2)
391
+ log('VALIDATOR_WARNING', `Warning from ${result.validator}`, {
392
+ message: result.message
393
+ });
394
+
395
+ results.recommendations.push({
396
+ type: 'bash-safety',
397
+ priority: 'medium',
398
+ message: `${result.validator}: ${result.message}`,
399
+ action: 'Review recommendations and consider fixing'
400
+ });
401
+ } else if (result.passed) {
402
+ // Pass (exit code 0)
403
+ log('SUCCESS', `Validator ${result.validator} passed`);
404
+ }
405
+ });
406
+
407
+ // Store validator results for exit code determination
408
+ results.bashValidators = {
409
+ executed: validatorResults.length,
410
+ passed: validatorResults.filter(r => r.passed).length,
411
+ warnings: validatorResults.filter(r => r.isWarning).length,
412
+ errors: validatorResults.filter(r => r.isBlocking).length,
413
+ results: validatorResults
414
+ };
415
+
416
+ log('SUCCESS', `Bash validators completed`, {
417
+ executed: results.bashValidators.executed,
418
+ passed: results.bashValidators.passed,
419
+ warnings: results.bashValidators.warnings,
420
+ errors: results.bashValidators.errors
421
+ });
422
+ } else {
423
+ log('DEBUG', `No bash validators configured for ${ext} files`);
424
+ }
425
+
426
+ // ============================================================================
427
+ // PHASE 2.6: SQL Injection Detection
428
+ // ============================================================================
429
+
430
+ if (ext.match(/\.(sql|ts|js|py)$/)) {
431
+ log('VALIDATING', 'Running SQL injection detection');
432
+
433
+ const sqlInjectionPatterns = [
434
+ // String concatenation in queries
435
+ { pattern: /['"`]\s*\+\s*\w+\s*\+\s*['"`].*(?:SELECT|INSERT|UPDATE|DELETE|WHERE)/i, risk: 'STRING_CONCATENATION', severity: 'critical' },
436
+ { pattern: /(?:SELECT|INSERT|UPDATE|DELETE|WHERE).*['"`]\s*\+\s*\w+/i, risk: 'STRING_CONCATENATION', severity: 'critical' },
437
+
438
+ // Template literals with variables in SQL
439
+ { pattern: /\$\{[^}]+\}.*(?:SELECT|INSERT|UPDATE|DELETE|WHERE)/i, risk: 'TEMPLATE_INJECTION', severity: 'high' },
440
+
441
+ // f-strings in Python SQL
442
+ { pattern: /f['"].*(?:SELECT|INSERT|UPDATE|DELETE|WHERE).*\{/i, risk: 'FSTRING_SQL_INJECTION', severity: 'critical' },
443
+
444
+ // .format() in Python SQL
445
+ { pattern: /['"].*(?:SELECT|INSERT|UPDATE|DELETE).*['"]\.format\(/i, risk: 'FORMAT_SQL_INJECTION', severity: 'critical' },
446
+
447
+ // Raw user input in query
448
+ { pattern: /(?:req\.body|req\.query|req\.params|request\.form|request\.args)\.[^\s]+.*(?:SELECT|INSERT|UPDATE|DELETE)/i, risk: 'UNSANITIZED_INPUT', severity: 'critical' },
449
+
450
+ // execute() with string concatenation
451
+ { pattern: /\.execute\s*\(\s*['"`].*\+/i, risk: 'EXECUTE_CONCATENATION', severity: 'critical' },
452
+ { pattern: /\.execute\s*\(\s*f['"`]/i, risk: 'EXECUTE_FSTRING', severity: 'critical' },
453
+ ];
454
+
455
+ const sqlInjectionIssues = sqlInjectionPatterns
456
+ .filter(check => check.pattern.test(fileContent))
457
+ .map(check => ({ type: check.risk, severity: check.severity }));
458
+
459
+ if (sqlInjectionIssues.length > 0) {
460
+ log('SQL_INJECTION_WARNING', `Found ${sqlInjectionIssues.length} potential SQL injection risks`, {
461
+ issues: sqlInjectionIssues
462
+ });
463
+
464
+ results.sqlInjection = {
465
+ passed: false,
466
+ issues: sqlInjectionIssues
467
+ };
468
+
469
+ sqlInjectionIssues.forEach(issue => {
470
+ results.recommendations.push({
471
+ type: 'sql-injection',
472
+ priority: issue.severity,
473
+ message: `Potential SQL injection: ${issue.type}`,
474
+ action: 'Use parameterized queries ($1, $2) or prepared statements instead of string concatenation'
475
+ });
476
+ });
477
+ } else {
478
+ results.sqlInjection = { passed: true, issues: [] };
479
+ log('SUCCESS', 'No SQL injection risks detected');
480
+ }
481
+ }
482
+
483
+ // ============================================================================
484
+ // PHASE 3: Root Directory Detection
485
+ // ============================================================================
486
+
487
+ log('VALIDATING', 'Checking file location (root directory warning)');
488
+
489
+ const isRootFile = dirname(resolve(filePath)) === resolve('.');
490
+ if (isRootFile && !filePath.match(/^(package\.json|tsconfig\.json|\.gitignore|\.env.*|README\.md|LICENSE|CLAUDE\.md)$/)) {
491
+ // Suggest appropriate location based on file type
492
+ const suggestions = [];
493
+ if (ext.match(/\.(js|ts|jsx|tsx)$/)) {
494
+ suggestions.push({ location: `src/${filePath}`, reason: 'Source files belong in src/' });
495
+ }
496
+ if (ext.match(/\.(test|spec)\.(js|ts|jsx|tsx)$/)) {
497
+ suggestions.push({ location: `tests/${filePath}`, reason: 'Test files belong in tests/' });
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
+ }
508
+
509
+ if (suggestions.length > 0) {
510
+ log('ROOT_WARNING', 'File in root directory - should be organized', {
511
+ file: filePath,
512
+ suggestions
513
+ });
514
+
515
+ results.recommendations.push({
516
+ type: 'organization',
517
+ priority: 'high',
518
+ message: `File "${filePath}" should not be in root directory`,
519
+ action: `Move to: ${suggestions[0].location}`,
520
+ suggestions
521
+ });
522
+
523
+ // Store for handler processing
524
+ results.rootWarning = { suggestions };
525
+ }
526
+ }
527
+
528
+ // ============================================================================
529
+ // PHASE 4: TDD Violation Detection
530
+ // ============================================================================
531
+
532
+ if (ext.match(/\.(js|ts|jsx|tsx|py|go|rs)$/) && !filePath.match(/\.(test|spec)\./)) {
533
+ log('VALIDATING', 'Checking TDD compliance');
534
+
535
+ const testPatterns = {
536
+ js: [`${dirname(filePath)}/${baseName}.test.js`, `tests/${baseName}.test.js`],
537
+ ts: [`${dirname(filePath)}/${baseName}.test.ts`, `tests/${baseName}.test.ts`],
538
+ py: [`${dirname(filePath)}/test_${baseName}.py`, `tests/test_${baseName}.py`],
539
+ go: [`${dirname(filePath)}/${baseName}_test.go`],
540
+ rs: null // Rust uses inline tests
541
+ };
542
+
543
+ const langKey = ext.replace('.', '');
544
+ const patterns = testPatterns[langKey];
545
+
546
+ if (patterns) {
547
+ const hasTest = patterns.some(p => existsSync(p));
548
+
549
+ if (!hasTest) {
550
+ log('TDD_VIOLATION', 'No test file found', {
551
+ file: filePath,
552
+ expectedLocations: patterns
553
+ });
554
+
555
+ results.recommendations.push({
556
+ type: 'testing',
557
+ priority: 'high',
558
+ message: 'No test file found for this module',
559
+ action: 'Create test file or run feedback-resolver.sh --type TDD_VIOLATION'
560
+ });
561
+
562
+ results.tddViolation = {
563
+ hasTests: false,
564
+ testFile: patterns[0],
565
+ recommendations: [`Create ${patterns[0]}`]
566
+ };
567
+ }
568
+ }
569
+ }
570
+
571
+ // ============================================================================
572
+ // PHASE 5: Code Metrics and Complexity Analysis
573
+ // ============================================================================
574
+
575
+ log('VALIDATING', 'Calculating code metrics');
576
+
577
+ const lines = fileContent.split('\n').length;
578
+ const functions = (fileContent.match(/function\s+\w+|const\s+\w+\s*=\s*\(/g) || []).length;
579
+ const classes = (fileContent.match(/class\s+\w+/g) || []).length;
580
+ const todos = (fileContent.match(/\/\/\s*TODO/gi) || []).length;
581
+ const fixmes = (fileContent.match(/\/\/\s*FIXME/gi) || []).length;
582
+
583
+ results.metrics = {
584
+ lines,
585
+ functions,
586
+ classes,
587
+ todos,
588
+ fixmes,
589
+ complexity: lines > 300 ? 'high' : lines > 100 ? 'medium' : 'low'
590
+ };
591
+
592
+ log('SUCCESS', 'Code metrics calculated', results.metrics);
593
+
594
+ // ============================================================================
595
+ // PHASE 5.1: Cyclomatic Complexity Analysis
596
+ // ============================================================================
597
+
598
+ log('VALIDATING', 'Analyzing cyclomatic complexity');
599
+
600
+ // Only analyze files >200 lines to reduce overhead
601
+ if (lines > 200 && ext.match(/\.(sh|js|ts|jsx|tsx|py)$/)) {
602
+ try {
603
+ // Use simple-complexity.sh for bash scripts
604
+ if (ext === '.sh') {
605
+ const complexityResult = spawnSync('bash', [
606
+ 'scripts/simple-complexity.sh',
607
+ filePath
608
+ ], {
609
+ encoding: 'utf-8',
610
+ timeout: 5000
611
+ });
612
+
613
+ if (complexityResult.status === 0) {
614
+ const output = complexityResult.stdout;
615
+ const complexityMatch = output.match(/Total Complexity:\s*(\d+)/);
616
+
617
+ if (complexityMatch) {
618
+ const complexity = parseInt(complexityMatch[1], 10);
619
+ results.metrics.cyclomaticComplexity = complexity;
620
+
621
+ log('SUCCESS', `Cyclomatic complexity: ${complexity}`, { complexity });
622
+
623
+ // Warning threshold: 30
624
+ if (complexity >= 30 && complexity < 40) {
625
+ log('COMPLEXITY_WARNING', `Moderate complexity detected: ${complexity}`, {
626
+ threshold: 30,
627
+ complexity
628
+ });
629
+
630
+ results.recommendations.push({
631
+ type: 'complexity',
632
+ priority: 'medium',
633
+ message: `Cyclomatic complexity is ${complexity} (threshold: 30)`,
634
+ action: 'Consider refactoring to reduce complexity'
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
643
+ });
644
+
645
+ // Check if lizard is available
646
+ const lizardCheck = spawnSync('which', ['lizard'], { encoding: 'utf-8' });
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
+ }
694
+ }
695
+ } else {
696
+ log('WARN', 'Complexity analysis failed', {
697
+ stderr: complexityResult.stderr
698
+ });
699
+ }
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
+
714
+ if (lizardResult.status === 0) {
715
+ try {
716
+ const lizardData = JSON.parse(lizardResult.stdout);
717
+
718
+ // Calculate average complexity
719
+ let totalComplexity = 0;
720
+ let functionCount = 0;
721
+
722
+ if (lizardData.function_list) {
723
+ lizardData.function_list.forEach(func => {
724
+ totalComplexity += func.cyclomatic_complexity || 0;
725
+ functionCount++;
726
+ });
727
+ }
728
+
729
+ const avgComplexity = functionCount > 0 ? Math.round(totalComplexity / functionCount) : 0;
730
+ results.metrics.cyclomaticComplexity = avgComplexity;
731
+
732
+ if (avgComplexity >= 30) {
733
+ log('COMPLEXITY_WARNING', `Average complexity: ${avgComplexity}`, {
734
+ avgComplexity,
735
+ functionCount
736
+ });
737
+
738
+ results.recommendations.push({
739
+ type: 'complexity',
740
+ priority: avgComplexity >= 40 ? 'critical' : 'medium',
741
+ message: `Average cyclomatic complexity: ${avgComplexity}`,
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
+ }
752
+ }
753
+ }
754
+ }
755
+ } catch (error) {
756
+ log('WARN', 'Complexity analysis error', {
757
+ error: error.message
758
+ });
759
+ }
760
+ }
761
+
762
+ // Check for Rust-specific quality issues
763
+ if (ext === '.rs') {
764
+ log('VALIDATING', 'Running Rust quality checks');
765
+
766
+ const rustIssues = [];
767
+ if (fileContent.match(/println!\(/)) rustIssues.push('debug_println');
768
+ if (fileContent.match(/unwrap\(\)/)) rustIssues.push('unwrap_usage');
769
+ if (fileContent.match(/panic!\(/)) rustIssues.push('panic_usage');
770
+
771
+ if (rustIssues.length > 0) {
772
+ log('RUST_QUALITY', 'Rust quality issues detected', { issues: rustIssues });
773
+
774
+ results.recommendations.push({
775
+ type: 'rust',
776
+ priority: 'medium',
777
+ message: 'Rust quality issues detected',
778
+ action: 'Run: cargo fmt && cargo clippy --fix --allow-dirty'
779
+ });
780
+
781
+ results.rustQuality = { issues: rustIssues };
782
+ }
783
+ }
784
+
785
+ // ============================================================================
786
+ // PHASE 6: Final Recommendations
787
+ // ============================================================================
788
+
789
+ log('VALIDATING', 'Generating recommendations');
790
+
791
+ // Type safety recommendations
792
+ if (ext.match(/\.(ts|tsx)$/) && fileContent.match(/:\s*any\b/)) {
793
+ results.recommendations.push({
794
+ type: 'typescript',
795
+ priority: 'medium',
796
+ message: 'Avoid using "any" type when possible',
797
+ action: 'Use specific types or unknown for better type safety'
798
+ });
799
+ }
800
+
801
+ // Testing recommendations
802
+ if (!filePath.match(/\.(test|spec)\./)) {
803
+ results.recommendations.push({
804
+ type: 'testing',
805
+ priority: 'medium',
806
+ message: 'Consider writing tests for this module',
807
+ action: 'Create corresponding test file to ensure code reliability'
808
+ });
809
+ }
810
+
811
+ log('SUCCESS', `Generated ${results.recommendations.length} recommendations`);
812
+
813
+ // ============================================================================
814
+ // PHASE 7: Exit Code Determination
815
+ // ============================================================================
816
+
817
+ let exitCode = 0;
818
+ let finalStatus = 'SUCCESS';
819
+
820
+ // Check for critical complexity issues
821
+ const hasComplexityIssue = results.recommendations.find(r => r.type === 'complexity');
822
+
823
+ // Check for bash validator issues
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) {
838
+ exitCode = 2;
839
+ finalStatus = 'ROOT_WARNING';
840
+ } else if (results.tddViolation) {
841
+ exitCode = 3;
842
+ finalStatus = 'TDD_VIOLATION';
843
+ } else if (hasBashValidatorWarning) {
844
+ exitCode = 10;
845
+ finalStatus = 'BASH_VALIDATOR_WARNING';
846
+ } else if (hasComplexityIssue && hasComplexityIssue.priority === 'critical') {
847
+ exitCode = 7;
848
+ finalStatus = 'COMPLEXITY_CRITICAL';
849
+ } else if (hasComplexityIssue && hasComplexityIssue.priority === 'medium') {
850
+ exitCode = 8;
851
+ finalStatus = 'COMPLEXITY_WARNING';
852
+ } else if (results.rustQuality) {
853
+ exitCode = 5;
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';
861
+ } else if (results.recommendations.length > 0) {
862
+ finalStatus = 'IMPROVEMENTS_SUGGESTED';
863
+ }
864
+
865
+ const finalResult = {
866
+ typescript: results.typescript,
867
+ eslint: results.eslint,
868
+ prettier: results.prettier,
869
+ security: results.security,
870
+ metrics: results.metrics,
871
+ recommendationCount: results.recommendations.length,
872
+ topRecommendations: results.recommendations.slice(0, 3)
873
+ };
874
+
875
+ // Include structured data for feedback handlers
876
+ if (results.sqlInjection) {
877
+ finalResult.sqlInjection = results.sqlInjection;
878
+ }
879
+ if (results.rootWarning) {
880
+ finalResult.rootWarning = results.rootWarning;
881
+ }
882
+ if (results.tddViolation) {
883
+ finalResult.tddViolation = results.tddViolation;
884
+ }
885
+ if (results.bashValidators) {
886
+ finalResult.bashValidators = results.bashValidators;
887
+ }
888
+ if (results.rustQuality) {
889
+ finalResult.rustQuality = results.rustQuality;
890
+ }
891
+ if (results.complexityAnalysis) {
892
+ finalResult.complexityAnalysis = results.complexityAnalysis;
893
+ }
894
+
895
+ log(finalStatus, 'Pipeline validation complete', finalResult);
896
+
897
+ process.exit(exitCode);