agentic-qe 3.6.10 → 3.6.12
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/.claude/helpers/learning-service.mjs +2 -0
- package/.claude/helpers/statusline-v3.cjs +2 -0
- package/.claude/skills/skills-manifest.json +1 -1
- package/package.json +1 -1
- package/scripts/migrate-v2-to-v3-memory.js +2 -0
- package/scripts/sync-claude-flow.cjs +1 -0
- package/v3/CHANGELOG.md +39 -0
- package/v3/dist/adapters/claude-flow/trajectory-bridge.js +2 -2
- package/v3/dist/adapters/claude-flow/trajectory-bridge.js.map +1 -1
- package/v3/dist/benchmarks/performance-benchmarks.js +1 -1
- package/v3/dist/cli/bundle.js +659 -427
- package/v3/dist/cli/commands/code.d.ts.map +1 -1
- package/v3/dist/cli/commands/code.js +9 -85
- package/v3/dist/cli/commands/code.js.map +1 -1
- package/v3/dist/cli/commands/coverage.d.ts.map +1 -1
- package/v3/dist/cli/commands/coverage.js +3 -28
- package/v3/dist/cli/commands/coverage.js.map +1 -1
- package/v3/dist/cli/commands/learning-helpers.d.ts.map +1 -1
- package/v3/dist/cli/commands/learning-helpers.js +3 -4
- package/v3/dist/cli/commands/learning-helpers.js.map +1 -1
- package/v3/dist/cli/commands/learning.d.ts.map +1 -1
- package/v3/dist/cli/commands/learning.js +5 -8
- package/v3/dist/cli/commands/learning.js.map +1 -1
- package/v3/dist/cli/commands/migrate.js +2 -2
- package/v3/dist/cli/commands/security.d.ts.map +1 -1
- package/v3/dist/cli/commands/security.js +3 -29
- package/v3/dist/cli/commands/security.js.map +1 -1
- package/v3/dist/cli/commands/test.d.ts.map +1 -1
- package/v3/dist/cli/commands/test.js +5 -58
- package/v3/dist/cli/commands/test.js.map +1 -1
- package/v3/dist/cli/utils/file-discovery.d.ts +27 -0
- package/v3/dist/cli/utils/file-discovery.d.ts.map +1 -0
- package/v3/dist/cli/utils/file-discovery.js +105 -0
- package/v3/dist/cli/utils/file-discovery.js.map +1 -0
- package/v3/dist/coordination/constants.d.ts +1 -1
- package/v3/dist/coordination/constants.js +1 -1
- package/v3/dist/coordination/task-executor.d.ts.map +1 -1
- package/v3/dist/coordination/task-executor.js +612 -104
- package/v3/dist/coordination/task-executor.js.map +1 -1
- package/v3/dist/domains/code-intelligence/coordinator-hypergraph.js +2 -2
- package/v3/dist/domains/code-intelligence/coordinator-hypergraph.js.map +1 -1
- package/v3/dist/domains/code-intelligence/coordinator.d.ts.map +1 -1
- package/v3/dist/domains/code-intelligence/coordinator.js +10 -3
- package/v3/dist/domains/code-intelligence/coordinator.js.map +1 -1
- package/v3/dist/domains/code-intelligence/services/metric-collector/index.d.ts.map +1 -1
- package/v3/dist/domains/code-intelligence/services/metric-collector/index.js +10 -0
- package/v3/dist/domains/code-intelligence/services/metric-collector/index.js.map +1 -1
- package/v3/dist/domains/code-intelligence/services/metric-collector/interfaces.d.ts +7 -1
- package/v3/dist/domains/code-intelligence/services/metric-collector/interfaces.d.ts.map +1 -1
- package/v3/dist/domains/code-intelligence/services/metric-collector/interfaces.js +10 -1
- package/v3/dist/domains/code-intelligence/services/metric-collector/interfaces.js.map +1 -1
- package/v3/dist/domains/code-intelligence/services/metric-collector/loc-counter.js +34 -10
- package/v3/dist/domains/code-intelligence/services/metric-collector/loc-counter.js.map +1 -1
- package/v3/dist/domains/coverage-analysis/coordinator.js +1 -1
- package/v3/dist/domains/coverage-analysis/services/coverage-analyzer.js +1 -1
- package/v3/dist/domains/coverage-analysis/services/coverage-embedder.d.ts +1 -1
- package/v3/dist/domains/coverage-analysis/services/coverage-embedder.js +1 -1
- package/v3/dist/domains/coverage-analysis/services/gap-detector.js +1 -1
- package/v3/dist/domains/coverage-analysis/services/ghost-coverage-analyzer.js +1 -1
- package/v3/dist/domains/coverage-analysis/services/hnsw-index.d.ts +10 -1
- package/v3/dist/domains/coverage-analysis/services/hnsw-index.d.ts.map +1 -1
- package/v3/dist/domains/coverage-analysis/services/hnsw-index.js +40 -5
- package/v3/dist/domains/coverage-analysis/services/hnsw-index.js.map +1 -1
- package/v3/dist/domains/coverage-analysis/services/sublinear-analyzer.d.ts +1 -1
- package/v3/dist/domains/coverage-analysis/services/sublinear-analyzer.js +1 -1
- package/v3/dist/init/fleet-integration.d.ts.map +1 -1
- package/v3/dist/init/fleet-integration.js +3 -4
- package/v3/dist/init/fleet-integration.js.map +1 -1
- package/v3/dist/init/init-wizard-hooks.d.ts +8 -1
- package/v3/dist/init/init-wizard-hooks.d.ts.map +1 -1
- package/v3/dist/init/init-wizard-hooks.js +47 -39
- package/v3/dist/init/init-wizard-hooks.js.map +1 -1
- package/v3/dist/init/init-wizard-migration.d.ts.map +1 -1
- package/v3/dist/init/init-wizard-migration.js +3 -7
- package/v3/dist/init/init-wizard-migration.js.map +1 -1
- package/v3/dist/init/init-wizard-steps.d.ts.map +1 -1
- package/v3/dist/init/init-wizard-steps.js +3 -4
- package/v3/dist/init/init-wizard-steps.js.map +1 -1
- package/v3/dist/init/migration/data-migrator.d.ts.map +1 -1
- package/v3/dist/init/migration/data-migrator.js +6 -10
- package/v3/dist/init/migration/data-migrator.js.map +1 -1
- package/v3/dist/init/migration/detector.d.ts.map +1 -1
- package/v3/dist/init/migration/detector.js +2 -4
- package/v3/dist/init/migration/detector.js.map +1 -1
- package/v3/dist/init/phases/01-detection.d.ts.map +1 -1
- package/v3/dist/init/phases/01-detection.js +2 -4
- package/v3/dist/init/phases/01-detection.js.map +1 -1
- package/v3/dist/init/phases/06-code-intelligence.d.ts.map +1 -1
- package/v3/dist/init/phases/06-code-intelligence.js +4 -6
- package/v3/dist/init/phases/06-code-intelligence.js.map +1 -1
- package/v3/dist/init/phases/07-hooks.d.ts +22 -1
- package/v3/dist/init/phases/07-hooks.d.ts.map +1 -1
- package/v3/dist/init/phases/07-hooks.js +137 -51
- package/v3/dist/init/phases/07-hooks.js.map +1 -1
- package/v3/dist/init/phases/12-verification.d.ts.map +1 -1
- package/v3/dist/init/phases/12-verification.js +2 -4
- package/v3/dist/init/phases/12-verification.js.map +1 -1
- package/v3/dist/init/settings-merge.d.ts +35 -0
- package/v3/dist/init/settings-merge.d.ts.map +1 -0
- package/v3/dist/init/settings-merge.js +140 -0
- package/v3/dist/init/settings-merge.js.map +1 -0
- package/v3/dist/integrations/agentic-flow/model-router/router.js +1 -1
- package/v3/dist/integrations/agentic-flow/model-router/router.js.map +1 -1
- package/v3/dist/integrations/agentic-flow/model-router/score-calculator.d.ts.map +1 -1
- package/v3/dist/integrations/agentic-flow/model-router/score-calculator.js +18 -3
- package/v3/dist/integrations/agentic-flow/model-router/score-calculator.js.map +1 -1
- package/v3/dist/integrations/agentic-flow/model-router/signal-collector.d.ts +3 -3
- package/v3/dist/integrations/agentic-flow/model-router/signal-collector.d.ts.map +1 -1
- package/v3/dist/integrations/agentic-flow/model-router/signal-collector.js +18 -0
- package/v3/dist/integrations/agentic-flow/model-router/signal-collector.js.map +1 -1
- package/v3/dist/integrations/embeddings/cache/EmbeddingCache.d.ts.map +1 -1
- package/v3/dist/integrations/embeddings/cache/EmbeddingCache.js +2 -2
- package/v3/dist/integrations/embeddings/cache/EmbeddingCache.js.map +1 -1
- package/v3/dist/kernel/constants.d.ts +1 -1
- package/v3/dist/kernel/constants.js +1 -1
- package/v3/dist/kernel/unified-memory-hnsw.d.ts.map +1 -1
- package/v3/dist/kernel/unified-memory-hnsw.js +25 -6
- package/v3/dist/kernel/unified-memory-hnsw.js.map +1 -1
- package/v3/dist/kernel/unified-memory-migration.d.ts.map +1 -1
- package/v3/dist/kernel/unified-memory-migration.js +3 -3
- package/v3/dist/kernel/unified-memory-migration.js.map +1 -1
- package/v3/dist/learning/metrics-tracker.d.ts.map +1 -1
- package/v3/dist/learning/metrics-tracker.js +2 -2
- package/v3/dist/learning/metrics-tracker.js.map +1 -1
- package/v3/dist/learning/sqlite-persistence.d.ts.map +1 -1
- package/v3/dist/learning/sqlite-persistence.js +3 -6
- package/v3/dist/learning/sqlite-persistence.js.map +1 -1
- package/v3/dist/learning/v2-to-v3-migration.d.ts.map +1 -1
- package/v3/dist/learning/v2-to-v3-migration.js +4 -5
- package/v3/dist/learning/v2-to-v3-migration.js.map +1 -1
- package/v3/dist/mcp/bundle.js +1266 -245
- package/v3/dist/mcp/handlers/domain-handler-configs.d.ts.map +1 -1
- package/v3/dist/mcp/handlers/domain-handler-configs.js +40 -31
- package/v3/dist/mcp/handlers/domain-handler-configs.js.map +1 -1
- package/v3/dist/mcp/handlers/task-handlers.d.ts.map +1 -1
- package/v3/dist/mcp/handlers/task-handlers.js +68 -5
- package/v3/dist/mcp/handlers/task-handlers.js.map +1 -1
- package/v3/dist/mcp/protocol-server.d.ts.map +1 -1
- package/v3/dist/mcp/protocol-server.js +16 -2
- package/v3/dist/mcp/protocol-server.js.map +1 -1
- package/v3/dist/mcp/tools/security-compliance/scan.d.ts +3 -1
- package/v3/dist/mcp/tools/security-compliance/scan.d.ts.map +1 -1
- package/v3/dist/mcp/tools/security-compliance/scan.js +417 -72
- package/v3/dist/mcp/tools/security-compliance/scan.js.map +1 -1
- package/v3/dist/planning/plan-executor.d.ts.map +1 -1
- package/v3/dist/planning/plan-executor.js +2 -2
- package/v3/dist/planning/plan-executor.js.map +1 -1
- package/v3/dist/shared/safe-db.d.ts +32 -0
- package/v3/dist/shared/safe-db.d.ts.map +1 -0
- package/v3/dist/shared/safe-db.js +41 -0
- package/v3/dist/shared/safe-db.js.map +1 -0
- package/v3/dist/sync/claude-flow-bridge.js +5 -5
- package/v3/dist/sync/claude-flow-bridge.js.map +1 -1
- package/v3/dist/sync/embeddings/sync-embedding-generator.js +3 -3
- package/v3/dist/sync/embeddings/sync-embedding-generator.js.map +1 -1
- package/v3/dist/sync/readers/sqlite-reader.d.ts.map +1 -1
- package/v3/dist/sync/readers/sqlite-reader.js +2 -2
- package/v3/dist/sync/readers/sqlite-reader.js.map +1 -1
- package/v3/package.json +1 -1
|
@@ -102,12 +102,18 @@ function detectTransformType(task) {
|
|
|
102
102
|
* Load coverage data from common coverage file formats
|
|
103
103
|
*/
|
|
104
104
|
async function loadCoverageData(targetPath) {
|
|
105
|
-
// Try various coverage file locations
|
|
105
|
+
// Try various coverage file locations (JS, Python, Java, etc.)
|
|
106
106
|
const coverageLocations = [
|
|
107
|
+
// JavaScript/TypeScript (Istanbul/nyc/vitest)
|
|
107
108
|
path.join(targetPath, 'coverage', 'coverage-final.json'),
|
|
108
109
|
path.join(targetPath, 'coverage', 'lcov.info'),
|
|
109
110
|
path.join(targetPath, '.nyc_output', 'coverage-final.json'),
|
|
110
111
|
path.join(targetPath, 'coverage-final.json'),
|
|
112
|
+
// Python (pytest-cov, coverage.py)
|
|
113
|
+
path.join(targetPath, 'coverage.xml'),
|
|
114
|
+
path.join(targetPath, 'htmlcov', 'status.json'),
|
|
115
|
+
// Cobertura (generic)
|
|
116
|
+
path.join(targetPath, 'cobertura-coverage.xml'),
|
|
111
117
|
];
|
|
112
118
|
for (const coveragePath of coverageLocations) {
|
|
113
119
|
try {
|
|
@@ -118,6 +124,9 @@ async function loadCoverageData(targetPath) {
|
|
|
118
124
|
else if (coveragePath.endsWith('.info')) {
|
|
119
125
|
return parseLcovInfo(content);
|
|
120
126
|
}
|
|
127
|
+
else if (coveragePath.endsWith('.xml')) {
|
|
128
|
+
return parseCoberturaXml(content);
|
|
129
|
+
}
|
|
121
130
|
}
|
|
122
131
|
catch {
|
|
123
132
|
// File not found, try next
|
|
@@ -290,6 +299,132 @@ function parseLcovInfo(content) {
|
|
|
290
299
|
},
|
|
291
300
|
};
|
|
292
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* Parse Cobertura XML format (Python coverage.py, Java JaCoCo, etc.)
|
|
304
|
+
* Handles both coverage.xml and cobertura-coverage.xml formats.
|
|
305
|
+
* Uses attribute-order-independent extraction to handle output from
|
|
306
|
+
* different tools (coverage.py, JaCoCo, Cobertura) that order attributes differently.
|
|
307
|
+
*/
|
|
308
|
+
function parseCoberturaXml(content) {
|
|
309
|
+
const files = [];
|
|
310
|
+
// Helper: extract a named attribute from an XML element string, order-independent
|
|
311
|
+
function attr(element, name) {
|
|
312
|
+
const match = element.match(new RegExp(`${name}=["']([^"']*)["']`));
|
|
313
|
+
return match ? match[1] : null;
|
|
314
|
+
}
|
|
315
|
+
// Find all <class ...> elements (handles any attribute order)
|
|
316
|
+
const classRegex = /<class\s[^>]*?>/g;
|
|
317
|
+
let classMatch;
|
|
318
|
+
while ((classMatch = classRegex.exec(content)) !== null) {
|
|
319
|
+
const classTag = classMatch[0];
|
|
320
|
+
const filename = attr(classTag, 'filename');
|
|
321
|
+
if (!filename)
|
|
322
|
+
continue;
|
|
323
|
+
const lineRate = parseFloat(attr(classTag, 'line-rate') || 'NaN');
|
|
324
|
+
const branchRate = parseFloat(attr(classTag, 'branch-rate') || 'NaN');
|
|
325
|
+
// Find the </class> boundary for this element
|
|
326
|
+
const classStart = classMatch.index;
|
|
327
|
+
const classEnd = content.indexOf('</class>', classStart);
|
|
328
|
+
const classContent = classEnd > classStart
|
|
329
|
+
? content.slice(classStart, classEnd)
|
|
330
|
+
: '';
|
|
331
|
+
let linesTotal = 0, linesCovered = 0;
|
|
332
|
+
let branchesTotal = 0, branchesCovered = 0;
|
|
333
|
+
let functionsTotal = 0, functionsCovered = 0;
|
|
334
|
+
const uncoveredLines = [];
|
|
335
|
+
const uncoveredBranches = [];
|
|
336
|
+
// Parse <line> elements (attribute-order-independent)
|
|
337
|
+
const lineRegex = /<line\s([^>]*?)\/>/g;
|
|
338
|
+
let lineMatch;
|
|
339
|
+
while ((lineMatch = lineRegex.exec(classContent)) !== null) {
|
|
340
|
+
const lineTag = lineMatch[1];
|
|
341
|
+
const lineNum = parseInt(attr(lineTag, 'number') || '0', 10);
|
|
342
|
+
const hits = parseInt(attr(lineTag, 'hits') || '0', 10);
|
|
343
|
+
const isBranch = attr(lineTag, 'branch') === 'true';
|
|
344
|
+
const condCoverage = attr(lineTag, 'condition-coverage');
|
|
345
|
+
linesTotal++;
|
|
346
|
+
if (hits > 0) {
|
|
347
|
+
linesCovered++;
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
uncoveredLines.push(lineNum);
|
|
351
|
+
}
|
|
352
|
+
if (isBranch) {
|
|
353
|
+
branchesTotal++;
|
|
354
|
+
const condPct = condCoverage ? parseInt(condCoverage, 10) : (hits > 0 ? 100 : 0);
|
|
355
|
+
if (condPct === 100) {
|
|
356
|
+
branchesCovered++;
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
uncoveredBranches.push(lineNum);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// Parse <method> elements for function coverage
|
|
364
|
+
const methodRegex = /<method\s([^>]*?)>/g;
|
|
365
|
+
let methodMatch;
|
|
366
|
+
while ((methodMatch = methodRegex.exec(classContent)) !== null) {
|
|
367
|
+
functionsTotal++;
|
|
368
|
+
// Check if method has any line hits (look for <line> within this method)
|
|
369
|
+
const methodStart = methodMatch.index;
|
|
370
|
+
const methodEnd = classContent.indexOf('</method>', methodStart);
|
|
371
|
+
if (methodEnd > methodStart) {
|
|
372
|
+
const methodContent = classContent.slice(methodStart, methodEnd);
|
|
373
|
+
const methodLineRegex = /<line\s([^>]*?)\/>/g;
|
|
374
|
+
let hasHits = false;
|
|
375
|
+
let mLineMatch;
|
|
376
|
+
while ((mLineMatch = methodLineRegex.exec(methodContent)) !== null) {
|
|
377
|
+
if (parseInt(attr(mLineMatch[1], 'hits') || '0', 10) > 0) {
|
|
378
|
+
hasHits = true;
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
if (hasHits)
|
|
383
|
+
functionsCovered++;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
// If no lines parsed, estimate from rates
|
|
387
|
+
if (linesTotal === 0 && !isNaN(lineRate)) {
|
|
388
|
+
linesTotal = 1;
|
|
389
|
+
linesCovered = lineRate >= 0.5 ? 1 : 0;
|
|
390
|
+
}
|
|
391
|
+
files.push({
|
|
392
|
+
path: filename,
|
|
393
|
+
lines: { covered: linesCovered, total: linesTotal },
|
|
394
|
+
branches: { covered: branchesCovered, total: branchesTotal },
|
|
395
|
+
functions: { covered: functionsCovered, total: functionsTotal },
|
|
396
|
+
statements: { covered: linesCovered, total: linesTotal },
|
|
397
|
+
uncoveredLines,
|
|
398
|
+
uncoveredBranches,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
// Calculate summary
|
|
402
|
+
let totalLines = 0, totalCoveredLines = 0;
|
|
403
|
+
let totalBranches = 0, totalCoveredBranches = 0;
|
|
404
|
+
let totalFunctions = 0, totalCoveredFunctions = 0;
|
|
405
|
+
for (const file of files) {
|
|
406
|
+
totalLines += file.lines.total;
|
|
407
|
+
totalCoveredLines += file.lines.covered;
|
|
408
|
+
totalBranches += file.branches.total;
|
|
409
|
+
totalCoveredBranches += file.branches.covered;
|
|
410
|
+
totalFunctions += file.functions.total;
|
|
411
|
+
totalCoveredFunctions += file.functions.covered;
|
|
412
|
+
}
|
|
413
|
+
// Also try to extract top-level summary from <coverage> element (order-independent)
|
|
414
|
+
const coverageTag = content.match(/<coverage\s[^>]*?>/);
|
|
415
|
+
const summaryLineRate = coverageTag ? parseFloat(attr(coverageTag[0], 'line-rate') || 'NaN') * 100 : NaN;
|
|
416
|
+
const summaryBranchRate = coverageTag ? parseFloat(attr(coverageTag[0], 'branch-rate') || 'NaN') * 100 : NaN;
|
|
417
|
+
return {
|
|
418
|
+
files,
|
|
419
|
+
summary: {
|
|
420
|
+
line: !isNaN(summaryLineRate) ? summaryLineRate : (totalLines > 0 ? (totalCoveredLines / totalLines) * 100 : 0),
|
|
421
|
+
branch: !isNaN(summaryBranchRate) ? summaryBranchRate : (totalBranches > 0 ? (totalCoveredBranches / totalBranches) * 100 : 0),
|
|
422
|
+
function: totalFunctions > 0 ? (totalCoveredFunctions / totalFunctions) * 100 : 0,
|
|
423
|
+
statement: totalLines > 0 ? (totalCoveredLines / totalLines) * 100 : 0,
|
|
424
|
+
files: files.length,
|
|
425
|
+
},
|
|
426
|
+
};
|
|
427
|
+
}
|
|
293
428
|
/**
|
|
294
429
|
* Discover source files in a directory
|
|
295
430
|
*/
|
|
@@ -297,7 +432,20 @@ async function discoverSourceFiles(targetPath, options = {}) {
|
|
|
297
432
|
const files = [];
|
|
298
433
|
const { includeTests = true, languages } = options;
|
|
299
434
|
// Determine file extensions to include
|
|
300
|
-
|
|
435
|
+
// Default: scan ALL common source languages unless explicitly filtered
|
|
436
|
+
let extensions = [
|
|
437
|
+
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', // JavaScript/TypeScript
|
|
438
|
+
'.py', '.pyw', // Python
|
|
439
|
+
'.go', // Go
|
|
440
|
+
'.rs', // Rust
|
|
441
|
+
'.java', '.kt', '.kts', // Java/Kotlin
|
|
442
|
+
'.rb', // Ruby
|
|
443
|
+
'.cs', // C#
|
|
444
|
+
'.php', // PHP
|
|
445
|
+
'.swift', // Swift
|
|
446
|
+
'.c', '.h', '.cpp', '.hpp', '.cc', // C/C++
|
|
447
|
+
'.scala', // Scala
|
|
448
|
+
];
|
|
301
449
|
if (languages && languages.length > 0) {
|
|
302
450
|
extensions = [];
|
|
303
451
|
for (const lang of languages) {
|
|
@@ -306,7 +454,27 @@ async function discoverSourceFiles(targetPath, options = {}) {
|
|
|
306
454
|
if (lang === 'javascript')
|
|
307
455
|
extensions.push('.js', '.jsx', '.mjs', '.cjs');
|
|
308
456
|
if (lang === 'python')
|
|
309
|
-
extensions.push('.py');
|
|
457
|
+
extensions.push('.py', '.pyw');
|
|
458
|
+
if (lang === 'go')
|
|
459
|
+
extensions.push('.go');
|
|
460
|
+
if (lang === 'rust')
|
|
461
|
+
extensions.push('.rs');
|
|
462
|
+
if (lang === 'java')
|
|
463
|
+
extensions.push('.java');
|
|
464
|
+
if (lang === 'kotlin')
|
|
465
|
+
extensions.push('.kt', '.kts');
|
|
466
|
+
if (lang === 'ruby')
|
|
467
|
+
extensions.push('.rb');
|
|
468
|
+
if (lang === 'csharp' || lang === 'c#')
|
|
469
|
+
extensions.push('.cs');
|
|
470
|
+
if (lang === 'php')
|
|
471
|
+
extensions.push('.php');
|
|
472
|
+
if (lang === 'swift')
|
|
473
|
+
extensions.push('.swift');
|
|
474
|
+
if (lang === 'c' || lang === 'cpp' || lang === 'c++')
|
|
475
|
+
extensions.push('.c', '.h', '.cpp', '.hpp', '.cc');
|
|
476
|
+
if (lang === 'scala')
|
|
477
|
+
extensions.push('.scala');
|
|
310
478
|
}
|
|
311
479
|
}
|
|
312
480
|
async function walkDir(dir) {
|
|
@@ -316,7 +484,9 @@ async function discoverSourceFiles(targetPath, options = {}) {
|
|
|
316
484
|
const fullPath = path.join(dir, entry.name);
|
|
317
485
|
// Skip common non-source directories
|
|
318
486
|
if (entry.isDirectory()) {
|
|
319
|
-
if (['node_modules', '.git', 'dist', 'build', 'coverage', '.nyc_output'
|
|
487
|
+
if (['node_modules', '.git', 'dist', 'build', 'coverage', '.nyc_output',
|
|
488
|
+
'__pycache__', '.venv', 'venv', '.tox', '.mypy_cache', 'target',
|
|
489
|
+
'.gradle', 'vendor', '.bundle'].includes(entry.name)) {
|
|
320
490
|
continue;
|
|
321
491
|
}
|
|
322
492
|
await walkDir(fullPath);
|
|
@@ -515,7 +685,15 @@ export class DomainTaskExecutor {
|
|
|
515
685
|
}
|
|
516
686
|
else if (payload.sourceCode) {
|
|
517
687
|
// Write temporary file for analysis if only source code provided
|
|
518
|
-
|
|
688
|
+
// Use correct file extension based on language parameter
|
|
689
|
+
const langExtMap = {
|
|
690
|
+
python: '.py', typescript: '.ts', javascript: '.js',
|
|
691
|
+
go: '.go', rust: '.rs', java: '.java', ruby: '.rb',
|
|
692
|
+
kotlin: '.kt', csharp: '.cs', php: '.php', swift: '.swift',
|
|
693
|
+
cpp: '.cpp', c: '.c', scala: '.scala',
|
|
694
|
+
};
|
|
695
|
+
const ext = langExtMap[payload.language?.toLowerCase() || 'typescript'] || '.ts';
|
|
696
|
+
const tempPath = `/tmp/aqe-temp-${uuidv4()}${ext}`;
|
|
519
697
|
await fs.writeFile(tempPath, payload.sourceCode, 'utf-8');
|
|
520
698
|
sourceFiles = [tempPath];
|
|
521
699
|
}
|
|
@@ -642,14 +820,125 @@ export class DomainTaskExecutor {
|
|
|
642
820
|
sast: payload.sast !== false,
|
|
643
821
|
dast: payload.dast || false,
|
|
644
822
|
},
|
|
645
|
-
warning: `No
|
|
823
|
+
warning: `No source files found in ${targetPath}`,
|
|
646
824
|
});
|
|
647
825
|
}
|
|
648
|
-
//
|
|
649
|
-
const
|
|
650
|
-
|
|
826
|
+
// Separate files by language capability
|
|
827
|
+
const jstsFiles = filesToScan.filter(f => /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(f));
|
|
828
|
+
const otherFiles = filesToScan.filter(f => !/\.(ts|tsx|js|jsx|mjs|cjs)$/.test(f));
|
|
829
|
+
// Run basic cross-language security patterns on non-JS/TS files
|
|
830
|
+
const crossLangVulns = [];
|
|
831
|
+
for (const filePath of otherFiles) {
|
|
832
|
+
try {
|
|
833
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
834
|
+
const lines = content.split('\n');
|
|
835
|
+
const relPath = filePath.startsWith(targetPath)
|
|
836
|
+
? filePath.slice(targetPath.length).replace(/^\//, '')
|
|
837
|
+
: filePath;
|
|
838
|
+
// Pattern: Hardcoded secrets/keys
|
|
839
|
+
// Fix #287: Use \w* around keywords to match SECRET_KEY, JWT_SECRET, API_TOKEN, etc.
|
|
840
|
+
const secretPatterns = [
|
|
841
|
+
{ regex: /\w*(?:secret|password|passwd|api_key|apikey|private_key|jwt_secret)\w*\s*[=:]\s*['"][^'"]{4,}['"]/gi, title: 'Hardcoded secret', severity: 'critical' },
|
|
842
|
+
{ regex: /\w*(?:token|auth_token|access_key|secret_key)\w*\s*[=:]\s*['"][^'"]{8,}['"]/gi, title: 'Hardcoded credential', severity: 'critical' },
|
|
843
|
+
{ regex: /(?:AWS_SECRET|GITHUB_TOKEN|SLACK_TOKEN|OPENAI_API_KEY)\s*[=:]\s*['"][^'"]+['"]/gi, title: 'Hardcoded cloud credential', severity: 'critical' },
|
|
844
|
+
];
|
|
845
|
+
for (const pattern of secretPatterns) {
|
|
846
|
+
for (let i = 0; i < lines.length; i++) {
|
|
847
|
+
// Use matchAll to find ALL secrets on a single line (not just first)
|
|
848
|
+
const matches = [...lines[i].matchAll(pattern.regex)];
|
|
849
|
+
for (const _m of matches) {
|
|
850
|
+
crossLangVulns.push({
|
|
851
|
+
title: pattern.title,
|
|
852
|
+
severity: pattern.severity,
|
|
853
|
+
location: { file: relPath, line: i + 1 },
|
|
854
|
+
description: `Potential hardcoded secret found at line ${i + 1}`,
|
|
855
|
+
category: 'sensitive-data',
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
// Pattern: SQL injection risks
|
|
861
|
+
const sqlPatterns = /(?:execute|query|cursor\.execute)\s*\(\s*(?:f['"]|['"].*%s|['"].*\+\s*\w)/gi;
|
|
862
|
+
for (let i = 0; i < lines.length; i++) {
|
|
863
|
+
if (sqlPatterns.test(lines[i])) {
|
|
864
|
+
crossLangVulns.push({
|
|
865
|
+
title: 'Potential SQL injection',
|
|
866
|
+
severity: 'high',
|
|
867
|
+
location: { file: relPath, line: i + 1 },
|
|
868
|
+
description: 'String interpolation in SQL query — use parameterized queries',
|
|
869
|
+
category: 'injection',
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
sqlPatterns.lastIndex = 0;
|
|
873
|
+
}
|
|
874
|
+
// Pattern: CORS wildcard (multi-framework)
|
|
875
|
+
const corsPatterns = [
|
|
876
|
+
/allow_origins\s*=\s*\[?\s*['"]?\*['"]?\s*\]?/i, // Python FastAPI/Flask
|
|
877
|
+
/cors\(\s*\{[^}]*origin:\s*['"]?\*['"]?/i, // Express.js cors()
|
|
878
|
+
/Access-Control-Allow-Origin['":\s]+\*/i, // Raw header / Nginx / .htaccess
|
|
879
|
+
/@CrossOrigin\(\s*origins?\s*=\s*["']\*["']/i, // Spring Boot
|
|
880
|
+
/\.Header\(\)\.Set\(["']Access-Control-Allow-Origin["'],\s*["']\*["']/i, // Go
|
|
881
|
+
];
|
|
882
|
+
for (const corsPattern of corsPatterns) {
|
|
883
|
+
if (corsPattern.test(content)) {
|
|
884
|
+
crossLangVulns.push({
|
|
885
|
+
title: 'CORS wildcard origin',
|
|
886
|
+
severity: 'high',
|
|
887
|
+
location: { file: relPath, line: lines.findIndex(l => corsPattern.test(l)) + 1 },
|
|
888
|
+
description: 'CORS configured with wildcard (*) origin — restrict to specific domains',
|
|
889
|
+
category: 'security-misconfiguration',
|
|
890
|
+
});
|
|
891
|
+
break; // One CORS finding per file is enough
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
// Pattern: Debug/development mode enabled
|
|
895
|
+
if (/(?:DEBUG|debug)\s*[=:]\s*(?:True|true|1)/i.test(content)) {
|
|
896
|
+
crossLangVulns.push({
|
|
897
|
+
title: 'Debug mode enabled',
|
|
898
|
+
severity: 'medium',
|
|
899
|
+
location: { file: relPath, line: lines.findIndex(l => /DEBUG\s*[=:]\s*(?:True|true|1)/i.test(l)) + 1 },
|
|
900
|
+
description: 'Debug mode should be disabled in production',
|
|
901
|
+
category: 'security-misconfiguration',
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
// Pattern: Eval/exec usage
|
|
905
|
+
if (/\b(?:eval|exec)\s*\(/i.test(content)) {
|
|
906
|
+
crossLangVulns.push({
|
|
907
|
+
title: 'Dangerous eval/exec usage',
|
|
908
|
+
severity: 'high',
|
|
909
|
+
location: { file: relPath, line: lines.findIndex(l => /\b(?:eval|exec)\s*\(/.test(l)) + 1 },
|
|
910
|
+
description: 'eval/exec can lead to code injection — avoid using with user input',
|
|
911
|
+
category: 'injection',
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
catch {
|
|
916
|
+
// Skip unreadable files
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
// Also check dependency manifests for known vulnerable packages
|
|
920
|
+
const depManifests = ['requirements.txt', 'pyproject.toml', 'Gemfile', 'go.mod', 'Cargo.toml'];
|
|
921
|
+
for (const manifest of depManifests) {
|
|
922
|
+
const manifestPath = path.join(targetPath, manifest);
|
|
923
|
+
try {
|
|
924
|
+
await fs.access(manifestPath);
|
|
925
|
+
crossLangVulns.push({
|
|
926
|
+
title: 'Dependency audit recommended',
|
|
927
|
+
severity: 'informational',
|
|
928
|
+
location: { file: manifest, line: 1 },
|
|
929
|
+
description: `Found ${manifest} — run language-specific dependency audit (e.g., pip-audit, npm audit, cargo audit)`,
|
|
930
|
+
category: 'dependencies',
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
catch {
|
|
934
|
+
// Manifest doesn't exist
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
// Convert JS/TS file paths to FilePath value objects for the SAST scanner
|
|
938
|
+
const filePathObjects = jstsFiles.map(filePath => FilePath.create(filePath));
|
|
939
|
+
// Run SAST scan on JS/TS files if requested and files exist
|
|
651
940
|
let sastResult = null;
|
|
652
|
-
if (payload.sast !== false) {
|
|
941
|
+
if (payload.sast !== false && filePathObjects.length > 0) {
|
|
653
942
|
const result = await scanner.scanFiles(filePathObjects);
|
|
654
943
|
if (result.success) {
|
|
655
944
|
sastResult = result.value;
|
|
@@ -667,18 +956,26 @@ export class DomainTaskExecutor {
|
|
|
667
956
|
dastResult = result.value;
|
|
668
957
|
}
|
|
669
958
|
}
|
|
670
|
-
// Combine results -
|
|
959
|
+
// Combine results from all scan sources - SAST, DAST, and cross-language patterns
|
|
960
|
+
const crossLangSeverityCounts = {
|
|
961
|
+
critical: crossLangVulns.filter(v => v.severity === 'critical').length,
|
|
962
|
+
high: crossLangVulns.filter(v => v.severity === 'high').length,
|
|
963
|
+
medium: crossLangVulns.filter(v => v.severity === 'medium').length,
|
|
964
|
+
low: crossLangVulns.filter(v => v.severity === 'low').length,
|
|
965
|
+
informational: crossLangVulns.filter(v => v.severity === 'informational').length,
|
|
966
|
+
};
|
|
671
967
|
const summary = {
|
|
672
|
-
critical: (sastResult?.summary?.critical || 0) + (dastResult?.summary?.critical || 0),
|
|
673
|
-
high: (sastResult?.summary?.high || 0) + (dastResult?.summary?.high || 0),
|
|
674
|
-
medium: (sastResult?.summary?.medium || 0) + (dastResult?.summary?.medium || 0),
|
|
675
|
-
low: (sastResult?.summary?.low || 0) + (dastResult?.summary?.low || 0),
|
|
676
|
-
informational: (sastResult?.summary?.informational || 0) + (dastResult?.summary?.informational || 0),
|
|
968
|
+
critical: (sastResult?.summary?.critical || 0) + (dastResult?.summary?.critical || 0) + crossLangSeverityCounts.critical,
|
|
969
|
+
high: (sastResult?.summary?.high || 0) + (dastResult?.summary?.high || 0) + crossLangSeverityCounts.high,
|
|
970
|
+
medium: (sastResult?.summary?.medium || 0) + (dastResult?.summary?.medium || 0) + crossLangSeverityCounts.medium,
|
|
971
|
+
low: (sastResult?.summary?.low || 0) + (dastResult?.summary?.low || 0) + crossLangSeverityCounts.low,
|
|
972
|
+
informational: (sastResult?.summary?.informational || 0) + (dastResult?.summary?.informational || 0) + crossLangSeverityCounts.informational,
|
|
677
973
|
};
|
|
678
|
-
// Extract top vulnerabilities
|
|
974
|
+
// Extract top vulnerabilities from all sources
|
|
679
975
|
const allVulns = [
|
|
680
976
|
...(sastResult?.vulnerabilities || []),
|
|
681
977
|
...(dastResult?.vulnerabilities || []),
|
|
978
|
+
...crossLangVulns,
|
|
682
979
|
];
|
|
683
980
|
const topVulnerabilities = allVulns
|
|
684
981
|
.sort((a, b) => {
|
|
@@ -709,7 +1006,12 @@ export class DomainTaskExecutor {
|
|
|
709
1006
|
dast: payload.dast || false,
|
|
710
1007
|
},
|
|
711
1008
|
filesScanned: filesToScan.length,
|
|
1009
|
+
jstsFilesScanned: jstsFiles.length,
|
|
1010
|
+
otherFilesScanned: otherFiles.length,
|
|
712
1011
|
coverage: sastResult?.coverage,
|
|
1012
|
+
...(otherFiles.length > 0 && jstsFiles.length === 0 ? {
|
|
1013
|
+
note: 'Non-JS/TS files were scanned with cross-language pattern matching. For deeper analysis, use language-specific security tools.',
|
|
1014
|
+
} : {}),
|
|
713
1015
|
});
|
|
714
1016
|
}
|
|
715
1017
|
catch (error) {
|
|
@@ -735,9 +1037,9 @@ export class DomainTaskExecutor {
|
|
|
735
1037
|
edgesCreated: 0,
|
|
736
1038
|
target: targetPath,
|
|
737
1039
|
incremental: payload.incremental || false,
|
|
738
|
-
languages: payload.languages || [
|
|
1040
|
+
languages: payload.languages || [],
|
|
739
1041
|
duration: Date.now() - startTime,
|
|
740
|
-
warning: `No source files found in ${targetPath}
|
|
1042
|
+
warning: `No source files found in ${targetPath}. Searched for: TypeScript, JavaScript, Python, Go, Rust, Java, Ruby, C/C++, and more.`,
|
|
741
1043
|
});
|
|
742
1044
|
}
|
|
743
1045
|
// Use the real KnowledgeGraphService to index files
|
|
@@ -753,14 +1055,21 @@ export class DomainTaskExecutor {
|
|
|
753
1055
|
const indexResult = result.value;
|
|
754
1056
|
// Detect languages from files
|
|
755
1057
|
const detectedLanguages = new Set();
|
|
1058
|
+
const extToLang = {
|
|
1059
|
+
ts: 'typescript', tsx: 'typescript',
|
|
1060
|
+
js: 'javascript', jsx: 'javascript', mjs: 'javascript', cjs: 'javascript',
|
|
1061
|
+
py: 'python', pyw: 'python',
|
|
1062
|
+
go: 'go', rs: 'rust',
|
|
1063
|
+
java: 'java', kt: 'kotlin', kts: 'kotlin',
|
|
1064
|
+
rb: 'ruby', cs: 'csharp', php: 'php', swift: 'swift',
|
|
1065
|
+
c: 'c', h: 'c', cpp: 'cpp', hpp: 'cpp', cc: 'cpp',
|
|
1066
|
+
scala: 'scala',
|
|
1067
|
+
};
|
|
756
1068
|
for (const file of filesToIndex) {
|
|
757
1069
|
const ext = path.extname(file).slice(1);
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
detectedLanguages.add('javascript');
|
|
762
|
-
if (ext === 'py')
|
|
763
|
-
detectedLanguages.add('python');
|
|
1070
|
+
const lang = extToLang[ext];
|
|
1071
|
+
if (lang)
|
|
1072
|
+
detectedLanguages.add(lang);
|
|
764
1073
|
}
|
|
765
1074
|
return ok({
|
|
766
1075
|
filesIndexed: indexResult.filesIndexed,
|
|
@@ -848,115 +1157,314 @@ export class DomainTaskExecutor {
|
|
|
848
1157
|
return err(toError(error));
|
|
849
1158
|
}
|
|
850
1159
|
});
|
|
851
|
-
// Register test execution handler
|
|
1160
|
+
// Register test execution handler - runs real tests via child process
|
|
852
1161
|
this.taskHandlers.set('execute-tests', async (task) => {
|
|
853
1162
|
const payload = task.payload;
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
1163
|
+
try {
|
|
1164
|
+
const { execSync } = await import('child_process');
|
|
1165
|
+
const testFiles = payload.testFiles || [];
|
|
1166
|
+
if (testFiles.length === 0) {
|
|
1167
|
+
return ok({
|
|
1168
|
+
total: 0, passed: 0, failed: 0, skipped: 0,
|
|
1169
|
+
duration: 0, coverage: 0, failedTests: [],
|
|
1170
|
+
warning: 'No test files specified. Provide testFiles array with paths to test files.',
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
// Attempt to run tests using common test runners
|
|
1174
|
+
const cwd = process.cwd();
|
|
1175
|
+
let output;
|
|
1176
|
+
try {
|
|
1177
|
+
// Try vitest first, then jest, then mocha
|
|
1178
|
+
output = execSync(`npx vitest run ${testFiles.join(' ')} --reporter=json 2>/dev/null || npx jest ${testFiles.join(' ')} --json 2>/dev/null`, { cwd, timeout: 120000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
1179
|
+
}
|
|
1180
|
+
catch (execError) {
|
|
1181
|
+
// Test runner may exit non-zero when tests fail — that's expected
|
|
1182
|
+
output = execError.stdout || '';
|
|
1183
|
+
}
|
|
1184
|
+
// Try to parse JSON output from test runner
|
|
1185
|
+
try {
|
|
1186
|
+
const jsonStart = output.indexOf('{');
|
|
1187
|
+
if (jsonStart >= 0) {
|
|
1188
|
+
const json = JSON.parse(output.slice(jsonStart));
|
|
1189
|
+
// vitest format
|
|
1190
|
+
if (json.testResults) {
|
|
1191
|
+
const total = json.numTotalTests || 0;
|
|
1192
|
+
const passed = json.numPassedTests || 0;
|
|
1193
|
+
const failed = json.numFailedTests || 0;
|
|
1194
|
+
return ok({ total, passed, failed, skipped: total - passed - failed, duration: 0, coverage: 0, failedTests: [] });
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
catch {
|
|
1199
|
+
// JSON parsing failed — return raw info
|
|
1200
|
+
}
|
|
1201
|
+
return ok({
|
|
1202
|
+
total: testFiles.length, passed: 0, failed: 0, skipped: 0,
|
|
1203
|
+
duration: 0, coverage: 0, failedTests: [],
|
|
1204
|
+
warning: 'Could not parse test runner output. Check that vitest or jest is installed.',
|
|
1205
|
+
rawOutput: output.slice(0, 500),
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
catch (error) {
|
|
1209
|
+
return err(toError(error));
|
|
1210
|
+
}
|
|
867
1211
|
});
|
|
868
|
-
// Register defect prediction handler
|
|
1212
|
+
// Register defect prediction handler - REAL IMPLEMENTATION
|
|
869
1213
|
this.taskHandlers.set('predict-defects', async (task) => {
|
|
870
1214
|
const payload = task.payload;
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
]
|
|
889
|
-
|
|
1215
|
+
try {
|
|
1216
|
+
const targetPath = payload.target || process.cwd();
|
|
1217
|
+
const minConfidence = payload.minConfidence || 0.5;
|
|
1218
|
+
// Discover actual source files in the target directory
|
|
1219
|
+
const sourceFiles = await discoverSourceFiles(targetPath, { includeTests: false });
|
|
1220
|
+
if (sourceFiles.length === 0) {
|
|
1221
|
+
return ok({
|
|
1222
|
+
predictedDefects: [],
|
|
1223
|
+
riskScore: 0,
|
|
1224
|
+
recommendations: [
|
|
1225
|
+
`No source files found in ${targetPath}. Ensure the path contains source code files.`,
|
|
1226
|
+
],
|
|
1227
|
+
warning: `No source files found in ${targetPath}`,
|
|
1228
|
+
filesAnalyzed: 0,
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
// Analyze each file for defect indicators based on real metrics
|
|
1232
|
+
const predictedDefects = [];
|
|
1233
|
+
for (const filePath of sourceFiles) {
|
|
1234
|
+
try {
|
|
1235
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
1236
|
+
const lines = content.split('\n');
|
|
1237
|
+
const lineCount = lines.length;
|
|
1238
|
+
// Calculate complexity indicators from real code
|
|
1239
|
+
let probability = 0;
|
|
1240
|
+
const reasons = [];
|
|
1241
|
+
// Factor 1: File size (large files are more defect-prone)
|
|
1242
|
+
if (lineCount > 500) {
|
|
1243
|
+
probability += 0.25;
|
|
1244
|
+
reasons.push(`Large file (${lineCount} lines)`);
|
|
1245
|
+
}
|
|
1246
|
+
else if (lineCount > 300) {
|
|
1247
|
+
probability += 0.15;
|
|
1248
|
+
reasons.push(`Medium-large file (${lineCount} lines)`);
|
|
1249
|
+
}
|
|
1250
|
+
// Factor 2: Cyclomatic complexity indicators
|
|
1251
|
+
const branchKeywords = content.match(/\b(if|else|switch|case|for|while|catch|&&|\|\|)\b/g) || [];
|
|
1252
|
+
const branchDensity = branchKeywords.length / Math.max(lineCount, 1);
|
|
1253
|
+
if (branchDensity > 0.15) {
|
|
1254
|
+
probability += 0.25;
|
|
1255
|
+
reasons.push(`High branch density (${branchKeywords.length} branches in ${lineCount} lines)`);
|
|
1256
|
+
}
|
|
1257
|
+
else if (branchDensity > 0.08) {
|
|
1258
|
+
probability += 0.10;
|
|
1259
|
+
reasons.push('Moderate branch complexity');
|
|
1260
|
+
}
|
|
1261
|
+
// Factor 3: Deeply nested code
|
|
1262
|
+
const maxIndent = Math.max(...lines.map(l => {
|
|
1263
|
+
const match = l.match(/^(\s*)/);
|
|
1264
|
+
return match ? match[1].length : 0;
|
|
1265
|
+
}));
|
|
1266
|
+
if (maxIndent > 20) {
|
|
1267
|
+
probability += 0.15;
|
|
1268
|
+
reasons.push('Deep nesting detected');
|
|
1269
|
+
}
|
|
1270
|
+
// Factor 4: TODO/FIXME/HACK comments
|
|
1271
|
+
const debtComments = (content.match(/\b(TODO|FIXME|HACK|XXX|WORKAROUND)\b/gi) || []).length;
|
|
1272
|
+
if (debtComments > 3) {
|
|
1273
|
+
probability += 0.15;
|
|
1274
|
+
reasons.push(`${debtComments} technical debt markers`);
|
|
1275
|
+
}
|
|
1276
|
+
// Factor 5: Long functions (heuristic)
|
|
1277
|
+
const functionStarts = (content.match(/\b(function|def|func|async)\b/g) || []).length;
|
|
1278
|
+
if (functionStarts > 0 && lineCount / functionStarts > 80) {
|
|
1279
|
+
probability += 0.10;
|
|
1280
|
+
reasons.push('Potentially long functions');
|
|
1281
|
+
}
|
|
1282
|
+
probability = Math.min(probability, 0.95);
|
|
1283
|
+
if (probability >= minConfidence) {
|
|
1284
|
+
// Use relative path for readability
|
|
1285
|
+
const relativePath = filePath.startsWith(targetPath)
|
|
1286
|
+
? filePath.slice(targetPath.length).replace(/^\//, '')
|
|
1287
|
+
: filePath;
|
|
1288
|
+
predictedDefects.push({
|
|
1289
|
+
file: relativePath,
|
|
1290
|
+
probability: Math.round(probability * 100) / 100,
|
|
1291
|
+
reason: reasons.join('; '),
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
catch {
|
|
1296
|
+
// Skip files that can't be read
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
// Sort by probability descending
|
|
1300
|
+
predictedDefects.sort((a, b) => b.probability - a.probability);
|
|
1301
|
+
// Calculate overall risk score
|
|
1302
|
+
const avgProb = predictedDefects.length > 0
|
|
1303
|
+
? predictedDefects.reduce((sum, d) => sum + d.probability, 0) / predictedDefects.length
|
|
1304
|
+
: 0;
|
|
1305
|
+
const riskScore = Math.round(avgProb * 100);
|
|
1306
|
+
// Generate recommendations from actual findings
|
|
1307
|
+
const recommendations = [];
|
|
1308
|
+
if (predictedDefects.length > 0) {
|
|
1309
|
+
recommendations.push(`${predictedDefects.length} files flagged for potential defects out of ${sourceFiles.length} analyzed`);
|
|
1310
|
+
const topFile = predictedDefects[0];
|
|
1311
|
+
recommendations.push(`Highest risk: ${topFile.file} (${Math.round(topFile.probability * 100)}%) — ${topFile.reason}`);
|
|
1312
|
+
}
|
|
1313
|
+
if (predictedDefects.some(d => d.reason.includes('Large file'))) {
|
|
1314
|
+
recommendations.push('Consider splitting large files to reduce complexity');
|
|
1315
|
+
}
|
|
1316
|
+
if (predictedDefects.some(d => d.reason.includes('technical debt'))) {
|
|
1317
|
+
recommendations.push('Address TODO/FIXME comments to reduce technical debt');
|
|
1318
|
+
}
|
|
1319
|
+
if (predictedDefects.length === 0) {
|
|
1320
|
+
recommendations.push('No files exceeded the defect probability threshold — code looks healthy');
|
|
1321
|
+
}
|
|
1322
|
+
return ok({
|
|
1323
|
+
predictedDefects: predictedDefects.slice(0, 20), // Top 20
|
|
1324
|
+
riskScore,
|
|
1325
|
+
recommendations,
|
|
1326
|
+
filesAnalyzed: sourceFiles.length,
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
catch (error) {
|
|
1330
|
+
return err(toError(error));
|
|
1331
|
+
}
|
|
890
1332
|
});
|
|
891
1333
|
// Register requirements validation handler
|
|
892
1334
|
this.taskHandlers.set('validate-requirements', async (task) => {
|
|
893
1335
|
const payload = task.payload;
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1336
|
+
try {
|
|
1337
|
+
const targetPath = payload.requirementsPath || process.cwd();
|
|
1338
|
+
// Look for requirements files (markdown, feature files, etc.)
|
|
1339
|
+
const reqFiles = await discoverSourceFiles(targetPath, {
|
|
1340
|
+
includeTests: false,
|
|
1341
|
+
languages: [],
|
|
1342
|
+
});
|
|
1343
|
+
// Scan for requirement-like files
|
|
1344
|
+
const reqPatterns = ['.md', '.feature', '.gherkin', '.txt', '.rst'];
|
|
1345
|
+
const requirementFiles = [];
|
|
1346
|
+
for (const f of reqFiles) {
|
|
1347
|
+
if (reqPatterns.some(ext => f.endsWith(ext))) {
|
|
1348
|
+
requirementFiles.push(f);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
return ok({
|
|
1352
|
+
requirementsAnalyzed: requirementFiles.length,
|
|
1353
|
+
testable: 0,
|
|
1354
|
+
ambiguous: 0,
|
|
1355
|
+
untestable: 0,
|
|
1356
|
+
coverage: 0,
|
|
1357
|
+
bddScenarios: [],
|
|
1358
|
+
warning: requirementFiles.length === 0
|
|
1359
|
+
? 'No requirement files (.md, .feature, .gherkin) found. Provide requirementsPath or add requirement docs.'
|
|
1360
|
+
: 'Requirements validation requires LLM analysis. File inventory returned — use task_orchestrate for deep analysis.',
|
|
1361
|
+
files: requirementFiles.map(f => f.startsWith(targetPath) ? f.slice(targetPath.length + 1) : f).slice(0, 20),
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
catch (error) {
|
|
1365
|
+
return err(toError(error));
|
|
1366
|
+
}
|
|
905
1367
|
});
|
|
906
1368
|
// Register contract validation handler
|
|
907
1369
|
this.taskHandlers.set('validate-contracts', async (task) => {
|
|
908
1370
|
const payload = task.payload;
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
1371
|
+
try {
|
|
1372
|
+
if (!payload.contractPath) {
|
|
1373
|
+
return ok({
|
|
1374
|
+
contractPath: '',
|
|
1375
|
+
valid: false,
|
|
1376
|
+
breakingChanges: [],
|
|
1377
|
+
warnings: [],
|
|
1378
|
+
coverage: 0,
|
|
1379
|
+
error: 'contractPath is required. Provide a path to an OpenAPI spec, JSON Schema, or Protocol Buffer file.',
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
// Check if the contract file exists
|
|
1383
|
+
try {
|
|
1384
|
+
const content = await fs.readFile(payload.contractPath, 'utf-8');
|
|
1385
|
+
const isJson = payload.contractPath.endsWith('.json');
|
|
1386
|
+
const isYaml = payload.contractPath.endsWith('.yaml') || payload.contractPath.endsWith('.yml');
|
|
1387
|
+
// Basic structural validation
|
|
1388
|
+
if (isJson) {
|
|
1389
|
+
JSON.parse(content); // throws if invalid
|
|
1390
|
+
}
|
|
1391
|
+
return ok({
|
|
1392
|
+
contractPath: payload.contractPath,
|
|
1393
|
+
valid: true,
|
|
1394
|
+
format: isJson ? 'json' : isYaml ? 'yaml' : 'unknown',
|
|
1395
|
+
breakingChanges: [],
|
|
1396
|
+
warnings: [],
|
|
1397
|
+
linesAnalyzed: content.split('\n').length,
|
|
1398
|
+
note: 'Structural validation passed. For semantic contract testing, use consumer-driven contract tests.',
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
catch (readErr) {
|
|
1402
|
+
return ok({
|
|
1403
|
+
contractPath: payload.contractPath,
|
|
1404
|
+
valid: false,
|
|
1405
|
+
breakingChanges: [],
|
|
1406
|
+
warnings: [],
|
|
1407
|
+
error: `Could not read or parse contract file: ${toErrorMessage(readErr)}`,
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
catch (error) {
|
|
1412
|
+
return err(toError(error));
|
|
1413
|
+
}
|
|
918
1414
|
});
|
|
919
1415
|
// Register accessibility test handler
|
|
920
1416
|
this.taskHandlers.set('test-accessibility', async (task) => {
|
|
921
1417
|
const payload = task.payload;
|
|
1418
|
+
// Accessibility testing requires a browser/DOM — return honest guidance
|
|
922
1419
|
return ok({
|
|
923
|
-
url: payload.url,
|
|
1420
|
+
url: payload.url || '',
|
|
924
1421
|
standard: payload.standard || 'wcag21-aa',
|
|
925
|
-
passed:
|
|
1422
|
+
passed: false,
|
|
926
1423
|
violations: [],
|
|
927
|
-
warnings: [
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1424
|
+
warnings: [],
|
|
1425
|
+
score: 0,
|
|
1426
|
+
note: 'Accessibility testing requires a browser environment (Puppeteer/Playwright). ' +
|
|
1427
|
+
'Use tools like axe-core, pa11y, or Lighthouse CLI for WCAG compliance testing. ' +
|
|
1428
|
+
'Example: npx pa11y ' + (payload.url || '<url>'),
|
|
931
1429
|
});
|
|
932
1430
|
});
|
|
933
1431
|
// Register chaos test handler
|
|
934
1432
|
this.taskHandlers.set('run-chaos', async (task) => {
|
|
935
1433
|
const payload = task.payload;
|
|
1434
|
+
// Chaos testing requires infrastructure access — return honest guidance
|
|
936
1435
|
return ok({
|
|
937
|
-
faultType: payload.faultType,
|
|
938
|
-
target: payload.target,
|
|
939
|
-
dryRun: payload.dryRun,
|
|
940
|
-
duration: payload.duration,
|
|
941
|
-
systemBehavior:
|
|
942
|
-
resilience:
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
},
|
|
1436
|
+
faultType: payload.faultType || 'unknown',
|
|
1437
|
+
target: payload.target || 'unknown',
|
|
1438
|
+
dryRun: payload.dryRun ?? true,
|
|
1439
|
+
duration: payload.duration || 0,
|
|
1440
|
+
systemBehavior: 'not-executed',
|
|
1441
|
+
resilience: null,
|
|
1442
|
+
note: 'Chaos engineering requires infrastructure-level fault injection. ' +
|
|
1443
|
+
'Use tools like Chaos Monkey, Litmus, or toxiproxy for real resilience testing. ' +
|
|
1444
|
+
'For Node.js apps, consider: nock (HTTP faults), testcontainers (dependency failures).',
|
|
947
1445
|
});
|
|
948
1446
|
});
|
|
949
1447
|
// Register learning optimization handler
|
|
950
1448
|
this.taskHandlers.set('optimize-learning', async (_task) => {
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1449
|
+
// Check actual pattern store state
|
|
1450
|
+
try {
|
|
1451
|
+
const memUsage = await import('../kernel/unified-memory-hnsw.js');
|
|
1452
|
+
return ok({
|
|
1453
|
+
patternsLearned: 0,
|
|
1454
|
+
modelsUpdated: 0,
|
|
1455
|
+
memoryConsolidated: false,
|
|
1456
|
+
note: 'Learning optimization runs during the dream cycle (SessionEnd hook). ' +
|
|
1457
|
+
'Use "npx agentic-qe hooks session-end --save-state" to trigger pattern consolidation.',
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
catch {
|
|
1461
|
+
return ok({
|
|
1462
|
+
patternsLearned: 0,
|
|
1463
|
+
modelsUpdated: 0,
|
|
1464
|
+
memoryConsolidated: false,
|
|
1465
|
+
note: 'Learning system not initialized. Run "aqe init --auto" first.',
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
960
1468
|
});
|
|
961
1469
|
}
|
|
962
1470
|
// ============================================================================
|