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.
- package/config/agent-whitelist.json +281 -0
- package/config/default.yml +180 -0
- package/config/feature-flags.json +315 -0
- package/config/fix-reports/config-manager-custom-keys.json +15 -0
- package/config/hooks/post-edit-pipeline.js +858 -0
- package/config/hooks/post-edit-pipeline.js.original +612 -0
- package/config/kong/grafana/datasources/prometheus.yml +24 -0
- package/config/kong/kong.yml +496 -0
- package/config/kong/prometheus.yml +49 -0
- package/config/logrotate.d/cfn-logs +221 -0
- package/config/loki/loki-config.yml +172 -0
- package/config/loki/retention.yml +107 -0
- package/config/mcp-servers.json +152 -0
- package/config/production.yml.example +72 -0
- package/config/prometheus.yml +85 -0
- package/config/promtail/promtail-config.yml +162 -0
- package/config/redis.conf +33 -0
- package/config/redis.config.js +115 -0
- package/config/skill-requirements.json +341 -0
- package/config/sla-definitions.test.yml +66 -0
- package/config/sla-definitions.yml +150 -0
- package/package.json +1 -1
|
@@ -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);
|