agentic-qe 3.6.11 → 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 +14 -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 +198 -91
- 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/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 +322 -74
- 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.js +2 -2
- package/v3/dist/domains/code-intelligence/coordinator.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 +2 -2
- package/v3/dist/domains/coverage-analysis/services/hnsw-index.js +3 -3
- 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-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 +11 -0
- package/v3/dist/init/phases/07-hooks.d.ts.map +1 -1
- package/v3/dist/init/phases/07-hooks.js +91 -1
- 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/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 +733 -162
- 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
|
*/
|
|
@@ -701,13 +836,17 @@ export class DomainTaskExecutor {
|
|
|
701
836
|
? filePath.slice(targetPath.length).replace(/^\//, '')
|
|
702
837
|
: filePath;
|
|
703
838
|
// Pattern: Hardcoded secrets/keys
|
|
839
|
+
// Fix #287: Use \w* around keywords to match SECRET_KEY, JWT_SECRET, API_TOKEN, etc.
|
|
704
840
|
const secretPatterns = [
|
|
705
|
-
{ regex:
|
|
706
|
-
{ regex:
|
|
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' },
|
|
707
844
|
];
|
|
708
845
|
for (const pattern of secretPatterns) {
|
|
709
846
|
for (let i = 0; i < lines.length; i++) {
|
|
710
|
-
|
|
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) {
|
|
711
850
|
crossLangVulns.push({
|
|
712
851
|
title: pattern.title,
|
|
713
852
|
severity: pattern.severity,
|
|
@@ -716,8 +855,6 @@ export class DomainTaskExecutor {
|
|
|
716
855
|
category: 'sensitive-data',
|
|
717
856
|
});
|
|
718
857
|
}
|
|
719
|
-
// Reset regex lastIndex for global regexes
|
|
720
|
-
pattern.regex.lastIndex = 0;
|
|
721
858
|
}
|
|
722
859
|
}
|
|
723
860
|
// Pattern: SQL injection risks
|
|
@@ -734,15 +871,25 @@ export class DomainTaskExecutor {
|
|
|
734
871
|
}
|
|
735
872
|
sqlPatterns.lastIndex = 0;
|
|
736
873
|
}
|
|
737
|
-
// Pattern: CORS wildcard
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
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
|
+
}
|
|
746
893
|
}
|
|
747
894
|
// Pattern: Debug/development mode enabled
|
|
748
895
|
if (/(?:DEBUG|debug)\s*[=:]\s*(?:True|true|1)/i.test(content)) {
|
|
@@ -1010,22 +1157,57 @@ export class DomainTaskExecutor {
|
|
|
1010
1157
|
return err(toError(error));
|
|
1011
1158
|
}
|
|
1012
1159
|
});
|
|
1013
|
-
// Register test execution handler
|
|
1160
|
+
// Register test execution handler - runs real tests via child process
|
|
1014
1161
|
this.taskHandlers.set('execute-tests', async (task) => {
|
|
1015
1162
|
const payload = task.payload;
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
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
|
+
}
|
|
1029
1211
|
});
|
|
1030
1212
|
// Register defect prediction handler - REAL IMPLEMENTATION
|
|
1031
1213
|
this.taskHandlers.set('predict-defects', async (task) => {
|
|
@@ -1151,72 +1333,138 @@ export class DomainTaskExecutor {
|
|
|
1151
1333
|
// Register requirements validation handler
|
|
1152
1334
|
this.taskHandlers.set('validate-requirements', async (task) => {
|
|
1153
1335
|
const payload = task.payload;
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
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
|
+
}
|
|
1165
1367
|
});
|
|
1166
1368
|
// Register contract validation handler
|
|
1167
1369
|
this.taskHandlers.set('validate-contracts', async (task) => {
|
|
1168
1370
|
const payload = task.payload;
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
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
|
+
}
|
|
1178
1414
|
});
|
|
1179
1415
|
// Register accessibility test handler
|
|
1180
1416
|
this.taskHandlers.set('test-accessibility', async (task) => {
|
|
1181
1417
|
const payload = task.payload;
|
|
1418
|
+
// Accessibility testing requires a browser/DOM — return honest guidance
|
|
1182
1419
|
return ok({
|
|
1183
|
-
url: payload.url,
|
|
1420
|
+
url: payload.url || '',
|
|
1184
1421
|
standard: payload.standard || 'wcag21-aa',
|
|
1185
|
-
passed:
|
|
1422
|
+
passed: false,
|
|
1186
1423
|
violations: [],
|
|
1187
|
-
warnings: [
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
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>'),
|
|
1191
1429
|
});
|
|
1192
1430
|
});
|
|
1193
1431
|
// Register chaos test handler
|
|
1194
1432
|
this.taskHandlers.set('run-chaos', async (task) => {
|
|
1195
1433
|
const payload = task.payload;
|
|
1434
|
+
// Chaos testing requires infrastructure access — return honest guidance
|
|
1196
1435
|
return ok({
|
|
1197
|
-
faultType: payload.faultType,
|
|
1198
|
-
target: payload.target,
|
|
1199
|
-
dryRun: payload.dryRun,
|
|
1200
|
-
duration: payload.duration,
|
|
1201
|
-
systemBehavior:
|
|
1202
|
-
resilience:
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
},
|
|
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).',
|
|
1207
1445
|
});
|
|
1208
1446
|
});
|
|
1209
1447
|
// Register learning optimization handler
|
|
1210
1448
|
this.taskHandlers.set('optimize-learning', async (_task) => {
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
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
|
+
}
|
|
1220
1468
|
});
|
|
1221
1469
|
}
|
|
1222
1470
|
// ============================================================================
|