agentic-qe 3.7.0 → 3.7.1
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/skills/pr-review/SKILL.md +1 -1
- package/.claude/skills/skills-manifest.json +6 -6
- package/README.md +18 -10
- package/package.json +2 -2
- package/scripts/generate-opencode-agents.ts +523 -0
- package/scripts/generate-opencode-skills.ts +318 -0
- package/v3/CHANGELOG.md +26 -0
- package/v3/README.md +7 -7
- package/v3/assets/skills/pr-review/SKILL.md +1 -1
- package/v3/dist/adapters/a2a/notifications/retry-queue.d.ts.map +1 -1
- package/v3/dist/adapters/a2a/notifications/retry-queue.js +2 -1
- package/v3/dist/adapters/a2a/notifications/retry-queue.js.map +1 -1
- package/v3/dist/adapters/a2a/notifications/subscription-store.d.ts.map +1 -1
- package/v3/dist/adapters/a2a/notifications/subscription-store.js +2 -1
- package/v3/dist/adapters/a2a/notifications/subscription-store.js.map +1 -1
- package/v3/dist/adapters/a2a/tasks/task-manager.d.ts.map +1 -1
- package/v3/dist/adapters/a2a/tasks/task-manager.js +4 -3
- package/v3/dist/adapters/a2a/tasks/task-manager.js.map +1 -1
- package/v3/dist/agents/claim-verifier/index.d.ts.map +1 -1
- package/v3/dist/agents/claim-verifier/index.js +2 -1
- package/v3/dist/agents/claim-verifier/index.js.map +1 -1
- package/v3/dist/cli/bundle.js +2830 -1371
- package/v3/dist/cli/commands/hooks.d.ts.map +1 -1
- package/v3/dist/cli/commands/hooks.js +17 -2
- package/v3/dist/cli/commands/hooks.js.map +1 -1
- package/v3/dist/cli/commands/init.d.ts.map +1 -1
- package/v3/dist/cli/commands/init.js +2 -0
- package/v3/dist/cli/commands/init.js.map +1 -1
- package/v3/dist/cli/commands/learning-helpers.d.ts.map +1 -1
- package/v3/dist/cli/commands/learning-helpers.js +11 -0
- package/v3/dist/cli/commands/learning-helpers.js.map +1 -1
- package/v3/dist/cli/handlers/init-handler.d.ts +1 -0
- package/v3/dist/cli/handlers/init-handler.d.ts.map +1 -1
- package/v3/dist/cli/handlers/init-handler.js +3 -0
- package/v3/dist/cli/handlers/init-handler.js.map +1 -1
- package/v3/dist/cli/scheduler/persistent-scheduler.d.ts.map +1 -1
- package/v3/dist/cli/scheduler/persistent-scheduler.js +2 -1
- package/v3/dist/cli/scheduler/persistent-scheduler.js.map +1 -1
- package/v3/dist/coordination/consensus/consensus-engine.d.ts.map +1 -1
- package/v3/dist/coordination/consensus/consensus-engine.js +2 -1
- package/v3/dist/coordination/consensus/consensus-engine.js.map +1 -1
- package/v3/dist/coordination/cross-domain-router.d.ts.map +1 -1
- package/v3/dist/coordination/cross-domain-router.js +7 -8
- package/v3/dist/coordination/cross-domain-router.js.map +1 -1
- package/v3/dist/coordination/handlers/code-intelligence-handlers.d.ts +9 -0
- package/v3/dist/coordination/handlers/code-intelligence-handlers.d.ts.map +1 -0
- package/v3/dist/coordination/handlers/code-intelligence-handlers.js +203 -0
- package/v3/dist/coordination/handlers/code-intelligence-handlers.js.map +1 -0
- package/v3/dist/coordination/handlers/coverage-handlers.d.ts +9 -0
- package/v3/dist/coordination/handlers/coverage-handlers.d.ts.map +1 -0
- package/v3/dist/coordination/handlers/coverage-handlers.js +120 -0
- package/v3/dist/coordination/handlers/coverage-handlers.js.map +1 -0
- package/v3/dist/coordination/handlers/handler-types.d.ts +35 -0
- package/v3/dist/coordination/handlers/handler-types.d.ts.map +1 -0
- package/v3/dist/coordination/handlers/handler-types.js +8 -0
- package/v3/dist/coordination/handlers/handler-types.js.map +1 -0
- package/v3/dist/coordination/handlers/handler-utils.d.ts +106 -0
- package/v3/dist/coordination/handlers/handler-utils.d.ts.map +1 -0
- package/v3/dist/coordination/handlers/handler-utils.js +480 -0
- package/v3/dist/coordination/handlers/handler-utils.js.map +1 -0
- package/v3/dist/coordination/handlers/index.d.ts +15 -0
- package/v3/dist/coordination/handlers/index.d.ts.map +1 -0
- package/v3/dist/coordination/handlers/index.js +14 -0
- package/v3/dist/coordination/handlers/index.js.map +1 -0
- package/v3/dist/coordination/handlers/misc-handlers.d.ts +9 -0
- package/v3/dist/coordination/handlers/misc-handlers.d.ts.map +1 -0
- package/v3/dist/coordination/handlers/misc-handlers.js +64 -0
- package/v3/dist/coordination/handlers/misc-handlers.js.map +1 -0
- package/v3/dist/coordination/handlers/quality-handlers.d.ts +9 -0
- package/v3/dist/coordination/handlers/quality-handlers.d.ts.map +1 -0
- package/v3/dist/coordination/handlers/quality-handlers.js +83 -0
- package/v3/dist/coordination/handlers/quality-handlers.js.map +1 -0
- package/v3/dist/coordination/handlers/requirements-handlers.d.ts +9 -0
- package/v3/dist/coordination/handlers/requirements-handlers.d.ts.map +1 -0
- package/v3/dist/coordination/handlers/requirements-handlers.js +95 -0
- package/v3/dist/coordination/handlers/requirements-handlers.js.map +1 -0
- package/v3/dist/coordination/handlers/security-handlers.d.ts +9 -0
- package/v3/dist/coordination/handlers/security-handlers.d.ts.map +1 -0
- package/v3/dist/coordination/handlers/security-handlers.js +255 -0
- package/v3/dist/coordination/handlers/security-handlers.js.map +1 -0
- package/v3/dist/coordination/handlers/test-execution-handlers.d.ts +9 -0
- package/v3/dist/coordination/handlers/test-execution-handlers.d.ts.map +1 -0
- package/v3/dist/coordination/handlers/test-execution-handlers.js +153 -0
- package/v3/dist/coordination/handlers/test-execution-handlers.js.map +1 -0
- package/v3/dist/coordination/queen-coordinator.d.ts.map +1 -1
- package/v3/dist/coordination/queen-coordinator.js +28 -25
- package/v3/dist/coordination/queen-coordinator.js.map +1 -1
- package/v3/dist/coordination/task-executor.d.ts +25 -9
- package/v3/dist/coordination/task-executor.d.ts.map +1 -1
- package/v3/dist/coordination/task-executor.js +38 -1352
- package/v3/dist/coordination/task-executor.js.map +1 -1
- package/v3/dist/domains/code-intelligence/services/metric-collector/loc-counter.js +15 -9
- package/v3/dist/domains/code-intelligence/services/metric-collector/loc-counter.js.map +1 -1
- package/v3/dist/domains/defect-intelligence/services/causal-root-cause-analyzer.d.ts.map +1 -1
- package/v3/dist/domains/defect-intelligence/services/causal-root-cause-analyzer.js +2 -1
- package/v3/dist/domains/defect-intelligence/services/causal-root-cause-analyzer.js.map +1 -1
- package/v3/dist/domains/requirements-validation/services/product-factors-assessment/types/index.d.ts.map +1 -1
- package/v3/dist/domains/requirements-validation/services/product-factors-assessment/types/index.js +2 -1
- package/v3/dist/domains/requirements-validation/services/product-factors-assessment/types/index.js.map +1 -1
- package/v3/dist/domains/test-execution/types/e2e-step.types.d.ts.map +1 -1
- package/v3/dist/domains/test-execution/types/e2e-step.types.js +2 -1
- package/v3/dist/domains/test-execution/types/e2e-step.types.js.map +1 -1
- package/v3/dist/feedback/coverage-learner.d.ts.map +1 -1
- package/v3/dist/feedback/coverage-learner.js +2 -1
- package/v3/dist/feedback/coverage-learner.js.map +1 -1
- package/v3/dist/init/opencode-installer.d.ts +67 -0
- package/v3/dist/init/opencode-installer.d.ts.map +1 -0
- package/v3/dist/init/opencode-installer.js +267 -0
- package/v3/dist/init/opencode-installer.js.map +1 -0
- package/v3/dist/init/orchestrator.d.ts.map +1 -1
- package/v3/dist/init/orchestrator.js +1 -0
- package/v3/dist/init/orchestrator.js.map +1 -1
- package/v3/dist/init/phases/09-assets.d.ts +2 -0
- package/v3/dist/init/phases/09-assets.d.ts.map +1 -1
- package/v3/dist/init/phases/09-assets.js +26 -0
- package/v3/dist/init/phases/09-assets.js.map +1 -1
- package/v3/dist/init/phases/phase-interface.d.ts +2 -0
- package/v3/dist/init/phases/phase-interface.d.ts.map +1 -1
- package/v3/dist/init/phases/phase-interface.js.map +1 -1
- package/v3/dist/integrations/agentic-flow/onnx-embeddings/adapter.d.ts.map +1 -1
- package/v3/dist/integrations/agentic-flow/onnx-embeddings/adapter.js +2 -1
- package/v3/dist/integrations/agentic-flow/onnx-embeddings/adapter.js.map +1 -1
- package/v3/dist/integrations/n8n/agent-factory.d.ts.map +1 -1
- package/v3/dist/integrations/n8n/agent-factory.js +2 -1
- package/v3/dist/integrations/n8n/agent-factory.js.map +1 -1
- package/v3/dist/integrations/rl-suite/sona.d.ts.map +1 -1
- package/v3/dist/integrations/rl-suite/sona.js +2 -1
- package/v3/dist/integrations/rl-suite/sona.js.map +1 -1
- package/v3/dist/integrations/ruvector/brain-exporter.d.ts.map +1 -1
- package/v3/dist/integrations/ruvector/brain-exporter.js +4 -3
- package/v3/dist/integrations/ruvector/brain-exporter.js.map +1 -1
- package/v3/dist/integrations/ruvector/hypergraph-schema.d.ts.map +1 -1
- package/v3/dist/integrations/ruvector/hypergraph-schema.js +7 -0
- package/v3/dist/integrations/ruvector/hypergraph-schema.js.map +1 -1
- package/v3/dist/integrations/ruvector/index.d.ts +1 -0
- package/v3/dist/integrations/ruvector/index.d.ts.map +1 -1
- package/v3/dist/integrations/ruvector/index.js +1 -0
- package/v3/dist/integrations/ruvector/index.js.map +1 -1
- package/v3/dist/integrations/ruvector/shared-rvf-dual-writer.d.ts +41 -0
- package/v3/dist/integrations/ruvector/shared-rvf-dual-writer.d.ts.map +1 -0
- package/v3/dist/integrations/ruvector/shared-rvf-dual-writer.js +122 -0
- package/v3/dist/integrations/ruvector/shared-rvf-dual-writer.js.map +1 -0
- package/v3/dist/integrations/ruvector/sona-wrapper.d.ts.map +1 -1
- package/v3/dist/integrations/ruvector/sona-wrapper.js +2 -1
- package/v3/dist/integrations/ruvector/sona-wrapper.js.map +1 -1
- package/v3/dist/integrations/vibium/client.d.ts.map +1 -1
- package/v3/dist/integrations/vibium/client.js +2 -1
- package/v3/dist/integrations/vibium/client.js.map +1 -1
- package/v3/dist/integrations/vibium/fallback.d.ts.map +1 -1
- package/v3/dist/integrations/vibium/fallback.js +2 -1
- package/v3/dist/integrations/vibium/fallback.js.map +1 -1
- package/v3/dist/kernel/kernel.d.ts.map +1 -1
- package/v3/dist/kernel/kernel.js +2 -1
- package/v3/dist/kernel/kernel.js.map +1 -1
- package/v3/dist/kernel/unified-memory.d.ts.map +1 -1
- package/v3/dist/kernel/unified-memory.js +19 -9
- package/v3/dist/kernel/unified-memory.js.map +1 -1
- package/v3/dist/learning/aqe-learning-engine.d.ts.map +1 -1
- package/v3/dist/learning/aqe-learning-engine.js +14 -2
- package/v3/dist/learning/aqe-learning-engine.js.map +1 -1
- package/v3/dist/learning/dream/concept-graph.d.ts +10 -5
- package/v3/dist/learning/dream/concept-graph.d.ts.map +1 -1
- package/v3/dist/learning/dream/concept-graph.js +77 -25
- package/v3/dist/learning/dream/concept-graph.js.map +1 -1
- package/v3/dist/learning/dream/dream-engine.d.ts.map +1 -1
- package/v3/dist/learning/dream/dream-engine.js +37 -29
- package/v3/dist/learning/dream/dream-engine.js.map +1 -1
- package/v3/dist/learning/dream/insight-generator.d.ts.map +1 -1
- package/v3/dist/learning/dream/insight-generator.js +2 -1
- package/v3/dist/learning/dream/insight-generator.js.map +1 -1
- package/v3/dist/learning/dream/spreading-activation.d.ts +1 -1
- package/v3/dist/learning/dream/spreading-activation.d.ts.map +1 -1
- package/v3/dist/learning/dream/spreading-activation.js +13 -5
- package/v3/dist/learning/dream/spreading-activation.js.map +1 -1
- package/v3/dist/learning/memory-auditor.d.ts.map +1 -1
- package/v3/dist/learning/memory-auditor.js +7 -4
- package/v3/dist/learning/memory-auditor.js.map +1 -1
- package/v3/dist/learning/real-embeddings.d.ts.map +1 -1
- package/v3/dist/learning/real-embeddings.js +26 -1
- package/v3/dist/learning/real-embeddings.js.map +1 -1
- package/v3/dist/learning/real-qe-reasoning-bank.d.ts.map +1 -1
- package/v3/dist/learning/real-qe-reasoning-bank.js +13 -2
- package/v3/dist/learning/real-qe-reasoning-bank.js.map +1 -1
- package/v3/dist/learning/sqlite-persistence.d.ts +5 -0
- package/v3/dist/learning/sqlite-persistence.d.ts.map +1 -1
- package/v3/dist/learning/sqlite-persistence.js +47 -7
- package/v3/dist/learning/sqlite-persistence.js.map +1 -1
- package/v3/dist/mcp/bundle.js +3185 -2017
- package/v3/dist/mcp/connection-pool.d.ts.map +1 -1
- package/v3/dist/mcp/connection-pool.js +3 -2
- package/v3/dist/mcp/connection-pool.js.map +1 -1
- package/v3/dist/mcp/entry.js +12 -0
- package/v3/dist/mcp/entry.js.map +1 -1
- package/v3/dist/mcp/handlers/core-handlers.d.ts +28 -0
- package/v3/dist/mcp/handlers/core-handlers.d.ts.map +1 -1
- package/v3/dist/mcp/handlers/core-handlers.js +117 -0
- package/v3/dist/mcp/handlers/core-handlers.js.map +1 -1
- package/v3/dist/mcp/handlers/index.d.ts +1 -1
- package/v3/dist/mcp/handlers/index.d.ts.map +1 -1
- package/v3/dist/mcp/handlers/index.js +1 -1
- package/v3/dist/mcp/handlers/index.js.map +1 -1
- package/v3/dist/mcp/http-server.d.ts.map +1 -1
- package/v3/dist/mcp/http-server.js +12 -9
- package/v3/dist/mcp/http-server.js.map +1 -1
- package/v3/dist/mcp/middleware/output-compaction.d.ts +45 -0
- package/v3/dist/mcp/middleware/output-compaction.d.ts.map +1 -0
- package/v3/dist/mcp/middleware/output-compaction.js +279 -0
- package/v3/dist/mcp/middleware/output-compaction.js.map +1 -0
- package/v3/dist/mcp/protocol-server.d.ts.map +1 -1
- package/v3/dist/mcp/protocol-server.js +53 -41
- package/v3/dist/mcp/protocol-server.js.map +1 -1
- package/v3/dist/mcp/security/sampling-server.js +2 -2
- package/v3/dist/mcp/security/sampling-server.js.map +1 -1
- package/v3/dist/mcp/services/reasoning-bank-service.d.ts.map +1 -1
- package/v3/dist/mcp/services/reasoning-bank-service.js +4 -5
- package/v3/dist/mcp/services/reasoning-bank-service.js.map +1 -1
- package/v3/dist/mcp/tools/base.d.ts +5 -0
- package/v3/dist/mcp/tools/base.d.ts.map +1 -1
- package/v3/dist/mcp/tools/base.js +25 -1
- package/v3/dist/mcp/tools/base.js.map +1 -1
- package/v3/dist/mcp/tools/learning-optimization/dream.d.ts.map +1 -1
- package/v3/dist/mcp/tools/learning-optimization/dream.js +22 -0
- package/v3/dist/mcp/tools/learning-optimization/dream.js.map +1 -1
- package/v3/dist/mcp/tools/security-compliance/scan.d.ts.map +1 -1
- package/v3/dist/mcp/tools/security-compliance/scan.js +2 -1
- package/v3/dist/mcp/tools/security-compliance/scan.js.map +1 -1
- package/v3/dist/mcp/transport/index.d.ts +8 -6
- package/v3/dist/mcp/transport/index.d.ts.map +1 -1
- package/v3/dist/mcp/transport/index.js +10 -9
- package/v3/dist/mcp/transport/index.js.map +1 -1
- package/v3/dist/migrations/20260120_add_hypergraph_tables.d.ts.map +1 -1
- package/v3/dist/migrations/20260120_add_hypergraph_tables.js +7 -0
- package/v3/dist/migrations/20260120_add_hypergraph_tables.js.map +1 -1
- package/v3/dist/optimization/token-optimizer-service.d.ts.map +1 -1
- package/v3/dist/optimization/token-optimizer-service.js +2 -1
- package/v3/dist/optimization/token-optimizer-service.js.map +1 -1
- package/v3/dist/planning/goap-planner.d.ts.map +1 -1
- package/v3/dist/planning/goap-planner.js +2 -1
- package/v3/dist/planning/goap-planner.js.map +1 -1
- package/v3/dist/planning/plan-executor.d.ts +5 -1
- package/v3/dist/planning/plan-executor.d.ts.map +1 -1
- package/v3/dist/planning/plan-executor.js +16 -2
- package/v3/dist/planning/plan-executor.js.map +1 -1
- package/v3/dist/routing/routing-feedback.d.ts.map +1 -1
- package/v3/dist/routing/routing-feedback.js +2 -1
- package/v3/dist/routing/routing-feedback.js.map +1 -1
- package/v3/dist/shared/llm/cost-tracker.d.ts.map +1 -1
- package/v3/dist/shared/llm/cost-tracker.js +2 -1
- package/v3/dist/shared/llm/cost-tracker.js.map +1 -1
- package/v3/dist/shared/llm/metrics/router-metrics.d.ts.map +1 -1
- package/v3/dist/shared/llm/metrics/router-metrics.js +2 -1
- package/v3/dist/shared/llm/metrics/router-metrics.js.map +1 -1
- package/v3/dist/strange-loop/belief-reconciler.d.ts.map +1 -1
- package/v3/dist/strange-loop/belief-reconciler.js +2 -1
- package/v3/dist/strange-loop/belief-reconciler.js.map +1 -1
- package/v3/dist/sync/readers/json-reader.d.ts.map +1 -1
- package/v3/dist/sync/readers/json-reader.js +2 -1
- package/v3/dist/sync/readers/json-reader.js.map +1 -1
- package/v3/dist/types/cross-phase-signals.d.ts.map +1 -1
- package/v3/dist/types/cross-phase-signals.js +2 -1
- package/v3/dist/types/cross-phase-signals.js.map +1 -1
- package/v3/dist/validation/swarm-skill-validator.d.ts.map +1 -1
- package/v3/dist/validation/swarm-skill-validator.js +2 -1
- package/v3/dist/validation/swarm-skill-validator.js.map +1 -1
- package/v3/dist/workers/base-worker.d.ts.map +1 -1
- package/v3/dist/workers/base-worker.js +2 -1
- package/v3/dist/workers/base-worker.js.map +1 -1
- package/v3/package.json +5 -2
|
@@ -9,14 +9,11 @@
|
|
|
9
9
|
* - Reads routingTier from task payload
|
|
10
10
|
* - Routes Tier 0 tasks to Agent Booster for mechanical transforms
|
|
11
11
|
* - Records outcomes back to TinyDancer for learning
|
|
12
|
+
*
|
|
13
|
+
* Handler implementations are extracted into ./handlers/ modules.
|
|
12
14
|
*/
|
|
13
15
|
import { v4 as uuidv4 } from 'uuid';
|
|
14
|
-
import
|
|
15
|
-
import * as path from 'path';
|
|
16
|
-
import { ok, err } from '../shared/types';
|
|
17
|
-
import { toError, toErrorMessage } from '../shared/error-utils.js';
|
|
18
|
-
import { safeJsonParse } from '../shared/safe-json.js';
|
|
19
|
-
import { FilePath } from '../shared/value-objects/index.js';
|
|
16
|
+
import { toErrorMessage } from '../shared/error-utils.js';
|
|
20
17
|
import { createResultSaver } from './result-saver';
|
|
21
18
|
// ADR-051: Agent Booster integration for Tier 0 tasks
|
|
22
19
|
import { createAgentBoosterAdapter, } from '../integrations/agentic-flow/agent-booster';
|
|
@@ -24,6 +21,8 @@ import { createAgentBoosterAdapter, } from '../integrations/agentic-flow/agent-b
|
|
|
24
21
|
import { getTaskRouter } from '../mcp/services/task-router';
|
|
25
22
|
// CQ-005: Use DomainServiceRegistry instead of dynamic imports from domains/
|
|
26
23
|
import { DomainServiceRegistry, ServiceKeys } from '../shared/domain-service-registry';
|
|
24
|
+
// Handler registration functions (extracted from the monolithic registerHandlers)
|
|
25
|
+
import { registerTestExecutionHandlers, registerCoverageHandlers, registerSecurityHandlers, registerQualityHandlers, registerRequirementsHandlers, registerCodeIntelligenceHandlers, registerMiscHandlers, } from './handlers/index';
|
|
27
26
|
// ============================================================================
|
|
28
27
|
// CQ-005: Domain Service Resolution via Registry (no coordination -> domains imports)
|
|
29
28
|
// Domain modules register their factories in their index.ts files.
|
|
@@ -105,482 +104,12 @@ function detectTransformType(task) {
|
|
|
105
104
|
return null;
|
|
106
105
|
}
|
|
107
106
|
// ============================================================================
|
|
108
|
-
// Helper Functions for Real Implementations
|
|
109
|
-
// ============================================================================
|
|
110
|
-
/**
|
|
111
|
-
* Load coverage data from common coverage file formats
|
|
112
|
-
*/
|
|
113
|
-
async function loadCoverageData(targetPath) {
|
|
114
|
-
// Try various coverage file locations (JS, Python, Java, etc.)
|
|
115
|
-
const coverageLocations = [
|
|
116
|
-
// JavaScript/TypeScript (Istanbul/nyc/vitest)
|
|
117
|
-
path.join(targetPath, 'coverage', 'coverage-final.json'),
|
|
118
|
-
path.join(targetPath, 'coverage', 'lcov.info'),
|
|
119
|
-
path.join(targetPath, '.nyc_output', 'coverage-final.json'),
|
|
120
|
-
path.join(targetPath, 'coverage-final.json'),
|
|
121
|
-
// Python (pytest-cov, coverage.py)
|
|
122
|
-
path.join(targetPath, 'coverage.xml'),
|
|
123
|
-
path.join(targetPath, 'htmlcov', 'status.json'),
|
|
124
|
-
// Cobertura (generic)
|
|
125
|
-
path.join(targetPath, 'cobertura-coverage.xml'),
|
|
126
|
-
];
|
|
127
|
-
for (const coveragePath of coverageLocations) {
|
|
128
|
-
try {
|
|
129
|
-
const content = await fs.readFile(coveragePath, 'utf-8');
|
|
130
|
-
if (coveragePath.endsWith('.json')) {
|
|
131
|
-
return parseCoverageJson(content);
|
|
132
|
-
}
|
|
133
|
-
else if (coveragePath.endsWith('.info')) {
|
|
134
|
-
return parseLcovInfo(content);
|
|
135
|
-
}
|
|
136
|
-
else if (coveragePath.endsWith('.xml')) {
|
|
137
|
-
return parseCoberturaXml(content);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
catch {
|
|
141
|
-
// File not found, try next
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Parse Istanbul/nyc coverage-final.json format
|
|
148
|
-
*/
|
|
149
|
-
function parseCoverageJson(content) {
|
|
150
|
-
const data = safeJsonParse(content);
|
|
151
|
-
const files = [];
|
|
152
|
-
let totalLines = 0, coveredLines = 0;
|
|
153
|
-
let totalBranches = 0, coveredBranches = 0;
|
|
154
|
-
let totalFunctions = 0, coveredFunctions = 0;
|
|
155
|
-
let totalStatements = 0, coveredStatements = 0;
|
|
156
|
-
for (const [filePath, fileData] of Object.entries(data)) {
|
|
157
|
-
const fd = fileData;
|
|
158
|
-
// Calculate file-level metrics
|
|
159
|
-
const lineMap = fd.statementMap || {};
|
|
160
|
-
const lineCounts = fd.s || {};
|
|
161
|
-
const branchMap = fd.branchMap || {};
|
|
162
|
-
const branchCounts = fd.b || {};
|
|
163
|
-
const fnCounts = fd.f || {};
|
|
164
|
-
const fileLines = Object.keys(lineCounts).length;
|
|
165
|
-
const fileCoveredLines = Object.values(lineCounts).filter((v) => v > 0).length;
|
|
166
|
-
const fileBranches = Object.values(branchCounts).flat().length;
|
|
167
|
-
const fileCoveredBranches = Object.values(branchCounts).flat().filter((v) => v > 0).length;
|
|
168
|
-
const fileFunctions = Object.keys(fnCounts).length;
|
|
169
|
-
const fileCoveredFunctions = Object.values(fnCounts).filter((v) => v > 0).length;
|
|
170
|
-
const fileStatements = Object.keys(lineMap).length;
|
|
171
|
-
const fileCoveredStatements = Object.values(lineCounts).filter((v) => v > 0).length;
|
|
172
|
-
// Find uncovered lines
|
|
173
|
-
const uncoveredLines = [];
|
|
174
|
-
for (const [key, count] of Object.entries(lineCounts)) {
|
|
175
|
-
if (count === 0) {
|
|
176
|
-
const stmtInfo = lineMap[key];
|
|
177
|
-
if (stmtInfo?.start?.line) {
|
|
178
|
-
uncoveredLines.push(stmtInfo.start.line);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
// Find uncovered branches
|
|
183
|
-
const uncoveredBranches = [];
|
|
184
|
-
for (const [branchId, counts] of Object.entries(branchCounts)) {
|
|
185
|
-
const branchInfo = branchMap[branchId];
|
|
186
|
-
counts.forEach((count, idx) => {
|
|
187
|
-
if (count === 0 && branchInfo?.locations?.[idx]?.start?.line) {
|
|
188
|
-
uncoveredBranches.push(branchInfo.locations[idx].start.line);
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
files.push({
|
|
193
|
-
path: filePath,
|
|
194
|
-
lines: { covered: fileCoveredLines, total: fileLines },
|
|
195
|
-
branches: { covered: fileCoveredBranches, total: fileBranches },
|
|
196
|
-
functions: { covered: fileCoveredFunctions, total: fileFunctions },
|
|
197
|
-
statements: { covered: fileCoveredStatements, total: fileStatements },
|
|
198
|
-
uncoveredLines: Array.from(new Set(uncoveredLines)).sort((a, b) => a - b),
|
|
199
|
-
uncoveredBranches: Array.from(new Set(uncoveredBranches)).sort((a, b) => a - b),
|
|
200
|
-
});
|
|
201
|
-
totalLines += fileLines;
|
|
202
|
-
coveredLines += fileCoveredLines;
|
|
203
|
-
totalBranches += fileBranches;
|
|
204
|
-
coveredBranches += fileCoveredBranches;
|
|
205
|
-
totalFunctions += fileFunctions;
|
|
206
|
-
coveredFunctions += fileCoveredFunctions;
|
|
207
|
-
totalStatements += fileStatements;
|
|
208
|
-
coveredStatements += fileCoveredStatements;
|
|
209
|
-
}
|
|
210
|
-
return {
|
|
211
|
-
files,
|
|
212
|
-
summary: {
|
|
213
|
-
line: totalLines > 0 ? (coveredLines / totalLines) * 100 : 0,
|
|
214
|
-
branch: totalBranches > 0 ? (coveredBranches / totalBranches) * 100 : 0,
|
|
215
|
-
function: totalFunctions > 0 ? (coveredFunctions / totalFunctions) * 100 : 0,
|
|
216
|
-
statement: totalStatements > 0 ? (coveredStatements / totalStatements) * 100 : 0,
|
|
217
|
-
files: files.length,
|
|
218
|
-
},
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* Parse LCOV info format
|
|
223
|
-
*/
|
|
224
|
-
function parseLcovInfo(content) {
|
|
225
|
-
const files = [];
|
|
226
|
-
const lines = content.split('\n');
|
|
227
|
-
let currentFile = null;
|
|
228
|
-
let linesTotal = 0, linesCovered = 0;
|
|
229
|
-
let branchesTotal = 0, branchesCovered = 0;
|
|
230
|
-
let functionsTotal = 0, functionsCovered = 0;
|
|
231
|
-
const uncoveredLines = [];
|
|
232
|
-
const uncoveredBranches = [];
|
|
233
|
-
for (const line of lines) {
|
|
234
|
-
if (line.startsWith('SF:')) {
|
|
235
|
-
currentFile = line.slice(3);
|
|
236
|
-
}
|
|
237
|
-
else if (line.startsWith('LF:')) {
|
|
238
|
-
linesTotal = parseInt(line.slice(3), 10);
|
|
239
|
-
}
|
|
240
|
-
else if (line.startsWith('LH:')) {
|
|
241
|
-
linesCovered = parseInt(line.slice(3), 10);
|
|
242
|
-
}
|
|
243
|
-
else if (line.startsWith('BRF:')) {
|
|
244
|
-
branchesTotal = parseInt(line.slice(4), 10);
|
|
245
|
-
}
|
|
246
|
-
else if (line.startsWith('BRH:')) {
|
|
247
|
-
branchesCovered = parseInt(line.slice(4), 10);
|
|
248
|
-
}
|
|
249
|
-
else if (line.startsWith('FNF:')) {
|
|
250
|
-
functionsTotal = parseInt(line.slice(4), 10);
|
|
251
|
-
}
|
|
252
|
-
else if (line.startsWith('FNH:')) {
|
|
253
|
-
functionsCovered = parseInt(line.slice(4), 10);
|
|
254
|
-
}
|
|
255
|
-
else if (line.startsWith('DA:')) {
|
|
256
|
-
const [lineNum, count] = line.slice(3).split(',').map(s => parseInt(s, 10));
|
|
257
|
-
if (count === 0) {
|
|
258
|
-
uncoveredLines.push(lineNum);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
else if (line.startsWith('BRDA:')) {
|
|
262
|
-
const parts = line.slice(5).split(',');
|
|
263
|
-
const lineNum = parseInt(parts[0], 10);
|
|
264
|
-
const taken = parts[3];
|
|
265
|
-
if (taken === '0' || taken === '-') {
|
|
266
|
-
uncoveredBranches.push(lineNum);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
else if (line === 'end_of_record' && currentFile) {
|
|
270
|
-
files.push({
|
|
271
|
-
path: currentFile,
|
|
272
|
-
lines: { covered: linesCovered, total: linesTotal },
|
|
273
|
-
branches: { covered: branchesCovered, total: branchesTotal },
|
|
274
|
-
functions: { covered: functionsCovered, total: functionsTotal },
|
|
275
|
-
statements: { covered: linesCovered, total: linesTotal },
|
|
276
|
-
uncoveredLines: Array.from(new Set(uncoveredLines)),
|
|
277
|
-
uncoveredBranches: Array.from(new Set(uncoveredBranches)),
|
|
278
|
-
});
|
|
279
|
-
// Reset for next file
|
|
280
|
-
currentFile = null;
|
|
281
|
-
linesTotal = linesCovered = 0;
|
|
282
|
-
branchesTotal = branchesCovered = 0;
|
|
283
|
-
functionsTotal = functionsCovered = 0;
|
|
284
|
-
uncoveredLines.length = 0;
|
|
285
|
-
uncoveredBranches.length = 0;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
// Calculate summary
|
|
289
|
-
let totalLines = 0, totalCoveredLines = 0;
|
|
290
|
-
let totalBranches = 0, totalCoveredBranches = 0;
|
|
291
|
-
let totalFunctions = 0, totalCoveredFunctions = 0;
|
|
292
|
-
for (const file of files) {
|
|
293
|
-
totalLines += file.lines.total;
|
|
294
|
-
totalCoveredLines += file.lines.covered;
|
|
295
|
-
totalBranches += file.branches.total;
|
|
296
|
-
totalCoveredBranches += file.branches.covered;
|
|
297
|
-
totalFunctions += file.functions.total;
|
|
298
|
-
totalCoveredFunctions += file.functions.covered;
|
|
299
|
-
}
|
|
300
|
-
return {
|
|
301
|
-
files,
|
|
302
|
-
summary: {
|
|
303
|
-
line: totalLines > 0 ? (totalCoveredLines / totalLines) * 100 : 0,
|
|
304
|
-
branch: totalBranches > 0 ? (totalCoveredBranches / totalBranches) * 100 : 0,
|
|
305
|
-
function: totalFunctions > 0 ? (totalCoveredFunctions / totalFunctions) * 100 : 0,
|
|
306
|
-
statement: totalLines > 0 ? (totalCoveredLines / totalLines) * 100 : 0,
|
|
307
|
-
files: files.length,
|
|
308
|
-
},
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* Parse Cobertura XML format (Python coverage.py, Java JaCoCo, etc.)
|
|
313
|
-
* Handles both coverage.xml and cobertura-coverage.xml formats.
|
|
314
|
-
* Uses attribute-order-independent extraction to handle output from
|
|
315
|
-
* different tools (coverage.py, JaCoCo, Cobertura) that order attributes differently.
|
|
316
|
-
*/
|
|
317
|
-
function parseCoberturaXml(content) {
|
|
318
|
-
const files = [];
|
|
319
|
-
// Helper: extract a named attribute from an XML element string, order-independent
|
|
320
|
-
function attr(element, name) {
|
|
321
|
-
const match = element.match(new RegExp(`${name}=["']([^"']*)["']`));
|
|
322
|
-
return match ? match[1] : null;
|
|
323
|
-
}
|
|
324
|
-
// Find all <class ...> elements (handles any attribute order)
|
|
325
|
-
const classRegex = /<class\s[^>]*?>/g;
|
|
326
|
-
let classMatch;
|
|
327
|
-
while ((classMatch = classRegex.exec(content)) !== null) {
|
|
328
|
-
const classTag = classMatch[0];
|
|
329
|
-
const filename = attr(classTag, 'filename');
|
|
330
|
-
if (!filename)
|
|
331
|
-
continue;
|
|
332
|
-
const lineRate = parseFloat(attr(classTag, 'line-rate') || 'NaN');
|
|
333
|
-
const branchRate = parseFloat(attr(classTag, 'branch-rate') || 'NaN');
|
|
334
|
-
// Find the </class> boundary for this element
|
|
335
|
-
const classStart = classMatch.index;
|
|
336
|
-
const classEnd = content.indexOf('</class>', classStart);
|
|
337
|
-
const classContent = classEnd > classStart
|
|
338
|
-
? content.slice(classStart, classEnd)
|
|
339
|
-
: '';
|
|
340
|
-
let linesTotal = 0, linesCovered = 0;
|
|
341
|
-
let branchesTotal = 0, branchesCovered = 0;
|
|
342
|
-
let functionsTotal = 0, functionsCovered = 0;
|
|
343
|
-
const uncoveredLines = [];
|
|
344
|
-
const uncoveredBranches = [];
|
|
345
|
-
// Parse <line> elements (attribute-order-independent)
|
|
346
|
-
const lineRegex = /<line\s([^>]*?)\/>/g;
|
|
347
|
-
let lineMatch;
|
|
348
|
-
while ((lineMatch = lineRegex.exec(classContent)) !== null) {
|
|
349
|
-
const lineTag = lineMatch[1];
|
|
350
|
-
const lineNum = parseInt(attr(lineTag, 'number') || '0', 10);
|
|
351
|
-
const hits = parseInt(attr(lineTag, 'hits') || '0', 10);
|
|
352
|
-
const isBranch = attr(lineTag, 'branch') === 'true';
|
|
353
|
-
const condCoverage = attr(lineTag, 'condition-coverage');
|
|
354
|
-
linesTotal++;
|
|
355
|
-
if (hits > 0) {
|
|
356
|
-
linesCovered++;
|
|
357
|
-
}
|
|
358
|
-
else {
|
|
359
|
-
uncoveredLines.push(lineNum);
|
|
360
|
-
}
|
|
361
|
-
if (isBranch) {
|
|
362
|
-
branchesTotal++;
|
|
363
|
-
const condPct = condCoverage ? parseInt(condCoverage, 10) : (hits > 0 ? 100 : 0);
|
|
364
|
-
if (condPct === 100) {
|
|
365
|
-
branchesCovered++;
|
|
366
|
-
}
|
|
367
|
-
else {
|
|
368
|
-
uncoveredBranches.push(lineNum);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
// Parse <method> elements for function coverage
|
|
373
|
-
const methodRegex = /<method\s([^>]*?)>/g;
|
|
374
|
-
let methodMatch;
|
|
375
|
-
while ((methodMatch = methodRegex.exec(classContent)) !== null) {
|
|
376
|
-
functionsTotal++;
|
|
377
|
-
// Check if method has any line hits (look for <line> within this method)
|
|
378
|
-
const methodStart = methodMatch.index;
|
|
379
|
-
const methodEnd = classContent.indexOf('</method>', methodStart);
|
|
380
|
-
if (methodEnd > methodStart) {
|
|
381
|
-
const methodContent = classContent.slice(methodStart, methodEnd);
|
|
382
|
-
const methodLineRegex = /<line\s([^>]*?)\/>/g;
|
|
383
|
-
let hasHits = false;
|
|
384
|
-
let mLineMatch;
|
|
385
|
-
while ((mLineMatch = methodLineRegex.exec(methodContent)) !== null) {
|
|
386
|
-
if (parseInt(attr(mLineMatch[1], 'hits') || '0', 10) > 0) {
|
|
387
|
-
hasHits = true;
|
|
388
|
-
break;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
if (hasHits)
|
|
392
|
-
functionsCovered++;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
// If no lines parsed, estimate from rates
|
|
396
|
-
if (linesTotal === 0 && !isNaN(lineRate)) {
|
|
397
|
-
linesTotal = 1;
|
|
398
|
-
linesCovered = lineRate >= 0.5 ? 1 : 0;
|
|
399
|
-
}
|
|
400
|
-
files.push({
|
|
401
|
-
path: filename,
|
|
402
|
-
lines: { covered: linesCovered, total: linesTotal },
|
|
403
|
-
branches: { covered: branchesCovered, total: branchesTotal },
|
|
404
|
-
functions: { covered: functionsCovered, total: functionsTotal },
|
|
405
|
-
statements: { covered: linesCovered, total: linesTotal },
|
|
406
|
-
uncoveredLines,
|
|
407
|
-
uncoveredBranches,
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
// Calculate summary
|
|
411
|
-
let totalLines = 0, totalCoveredLines = 0;
|
|
412
|
-
let totalBranches = 0, totalCoveredBranches = 0;
|
|
413
|
-
let totalFunctions = 0, totalCoveredFunctions = 0;
|
|
414
|
-
for (const file of files) {
|
|
415
|
-
totalLines += file.lines.total;
|
|
416
|
-
totalCoveredLines += file.lines.covered;
|
|
417
|
-
totalBranches += file.branches.total;
|
|
418
|
-
totalCoveredBranches += file.branches.covered;
|
|
419
|
-
totalFunctions += file.functions.total;
|
|
420
|
-
totalCoveredFunctions += file.functions.covered;
|
|
421
|
-
}
|
|
422
|
-
// Also try to extract top-level summary from <coverage> element (order-independent)
|
|
423
|
-
const coverageTag = content.match(/<coverage\s[^>]*?>/);
|
|
424
|
-
const summaryLineRate = coverageTag ? parseFloat(attr(coverageTag[0], 'line-rate') || 'NaN') * 100 : NaN;
|
|
425
|
-
const summaryBranchRate = coverageTag ? parseFloat(attr(coverageTag[0], 'branch-rate') || 'NaN') * 100 : NaN;
|
|
426
|
-
return {
|
|
427
|
-
files,
|
|
428
|
-
summary: {
|
|
429
|
-
line: !isNaN(summaryLineRate) ? summaryLineRate : (totalLines > 0 ? (totalCoveredLines / totalLines) * 100 : 0),
|
|
430
|
-
branch: !isNaN(summaryBranchRate) ? summaryBranchRate : (totalBranches > 0 ? (totalCoveredBranches / totalBranches) * 100 : 0),
|
|
431
|
-
function: totalFunctions > 0 ? (totalCoveredFunctions / totalFunctions) * 100 : 0,
|
|
432
|
-
statement: totalLines > 0 ? (totalCoveredLines / totalLines) * 100 : 0,
|
|
433
|
-
files: files.length,
|
|
434
|
-
},
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
/**
|
|
438
|
-
* Discover source files in a directory
|
|
439
|
-
*/
|
|
440
|
-
async function discoverSourceFiles(targetPath, options = {}) {
|
|
441
|
-
const files = [];
|
|
442
|
-
const { includeTests = true, languages } = options;
|
|
443
|
-
// Determine file extensions to include
|
|
444
|
-
// Default: scan ALL common source languages unless explicitly filtered
|
|
445
|
-
let extensions = [
|
|
446
|
-
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', // JavaScript/TypeScript
|
|
447
|
-
'.py', '.pyw', // Python
|
|
448
|
-
'.go', // Go
|
|
449
|
-
'.rs', // Rust
|
|
450
|
-
'.java', '.kt', '.kts', // Java/Kotlin
|
|
451
|
-
'.rb', // Ruby
|
|
452
|
-
'.cs', // C#
|
|
453
|
-
'.php', // PHP
|
|
454
|
-
'.swift', // Swift
|
|
455
|
-
'.c', '.h', '.cpp', '.hpp', '.cc', // C/C++
|
|
456
|
-
'.scala', // Scala
|
|
457
|
-
];
|
|
458
|
-
if (languages && languages.length > 0) {
|
|
459
|
-
extensions = [];
|
|
460
|
-
for (const lang of languages) {
|
|
461
|
-
if (lang === 'typescript')
|
|
462
|
-
extensions.push('.ts', '.tsx');
|
|
463
|
-
if (lang === 'javascript')
|
|
464
|
-
extensions.push('.js', '.jsx', '.mjs', '.cjs');
|
|
465
|
-
if (lang === 'python')
|
|
466
|
-
extensions.push('.py', '.pyw');
|
|
467
|
-
if (lang === 'go')
|
|
468
|
-
extensions.push('.go');
|
|
469
|
-
if (lang === 'rust')
|
|
470
|
-
extensions.push('.rs');
|
|
471
|
-
if (lang === 'java')
|
|
472
|
-
extensions.push('.java');
|
|
473
|
-
if (lang === 'kotlin')
|
|
474
|
-
extensions.push('.kt', '.kts');
|
|
475
|
-
if (lang === 'ruby')
|
|
476
|
-
extensions.push('.rb');
|
|
477
|
-
if (lang === 'csharp' || lang === 'c#')
|
|
478
|
-
extensions.push('.cs');
|
|
479
|
-
if (lang === 'php')
|
|
480
|
-
extensions.push('.php');
|
|
481
|
-
if (lang === 'swift')
|
|
482
|
-
extensions.push('.swift');
|
|
483
|
-
if (lang === 'c' || lang === 'cpp' || lang === 'c++')
|
|
484
|
-
extensions.push('.c', '.h', '.cpp', '.hpp', '.cc');
|
|
485
|
-
if (lang === 'scala')
|
|
486
|
-
extensions.push('.scala');
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
async function walkDir(dir) {
|
|
490
|
-
try {
|
|
491
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
492
|
-
for (const entry of entries) {
|
|
493
|
-
const fullPath = path.join(dir, entry.name);
|
|
494
|
-
// Skip common non-source directories
|
|
495
|
-
if (entry.isDirectory()) {
|
|
496
|
-
if (['node_modules', '.git', 'dist', 'build', 'coverage', '.nyc_output',
|
|
497
|
-
'__pycache__', '.venv', 'venv', '.tox', '.mypy_cache', 'target',
|
|
498
|
-
'.gradle', 'vendor', '.bundle'].includes(entry.name)) {
|
|
499
|
-
continue;
|
|
500
|
-
}
|
|
501
|
-
await walkDir(fullPath);
|
|
502
|
-
}
|
|
503
|
-
else if (entry.isFile()) {
|
|
504
|
-
const ext = path.extname(entry.name);
|
|
505
|
-
// Check extension
|
|
506
|
-
if (!extensions.includes(ext))
|
|
507
|
-
continue;
|
|
508
|
-
// Skip test files if not including tests
|
|
509
|
-
if (!includeTests) {
|
|
510
|
-
const isTestFile = entry.name.includes('.test.') ||
|
|
511
|
-
entry.name.includes('.spec.') ||
|
|
512
|
-
entry.name.endsWith('_test.ts') ||
|
|
513
|
-
entry.name.endsWith('_test.js') ||
|
|
514
|
-
fullPath.includes('/__tests__/') ||
|
|
515
|
-
fullPath.includes('/test/') ||
|
|
516
|
-
fullPath.includes('/tests/');
|
|
517
|
-
if (isTestFile)
|
|
518
|
-
continue;
|
|
519
|
-
}
|
|
520
|
-
files.push(fullPath);
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
catch {
|
|
525
|
-
// Directory not accessible
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
// Check if targetPath is a file or directory
|
|
529
|
-
try {
|
|
530
|
-
const stat = await fs.stat(targetPath);
|
|
531
|
-
if (stat.isFile()) {
|
|
532
|
-
return [targetPath];
|
|
533
|
-
}
|
|
534
|
-
await walkDir(targetPath);
|
|
535
|
-
}
|
|
536
|
-
catch {
|
|
537
|
-
// Path doesn't exist
|
|
538
|
-
}
|
|
539
|
-
return files;
|
|
540
|
-
}
|
|
541
|
-
/**
|
|
542
|
-
* Generate security recommendations based on findings
|
|
543
|
-
*/
|
|
544
|
-
function generateSecurityRecommendations(vulnerabilities) {
|
|
545
|
-
const recommendations = new Set();
|
|
546
|
-
const categoryRecommendations = {
|
|
547
|
-
'injection': 'Use parameterized queries and input validation to prevent injection attacks',
|
|
548
|
-
'xss': 'Sanitize user input and use Content-Security-Policy headers',
|
|
549
|
-
'sensitive-data': 'Never hardcode secrets; use environment variables or secret managers',
|
|
550
|
-
'access-control': 'Implement proper authentication and authorization checks',
|
|
551
|
-
'security-misconfiguration': 'Review and harden security configurations',
|
|
552
|
-
'insecure-deserialization': 'Avoid deserializing untrusted data; use safe alternatives',
|
|
553
|
-
'broken-auth': 'Use strong authentication mechanisms and secure session management',
|
|
554
|
-
'dependencies': 'Keep dependencies updated and regularly audit for vulnerabilities',
|
|
555
|
-
};
|
|
556
|
-
// Add category-specific recommendations
|
|
557
|
-
for (const vuln of vulnerabilities) {
|
|
558
|
-
const rec = categoryRecommendations[vuln.category];
|
|
559
|
-
if (rec) {
|
|
560
|
-
recommendations.add(rec);
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
// Add severity-based general recommendations
|
|
564
|
-
const hasCritical = vulnerabilities.some(v => v.severity === 'critical');
|
|
565
|
-
const hasHigh = vulnerabilities.some(v => v.severity === 'high');
|
|
566
|
-
if (hasCritical) {
|
|
567
|
-
recommendations.add('CRITICAL: Address critical vulnerabilities immediately before deployment');
|
|
568
|
-
}
|
|
569
|
-
if (hasHigh) {
|
|
570
|
-
recommendations.add('Prioritize fixing high-severity issues in the next sprint');
|
|
571
|
-
}
|
|
572
|
-
if (recommendations.size === 0 && vulnerabilities.length === 0) {
|
|
573
|
-
recommendations.add('No vulnerabilities found - maintain current security practices');
|
|
574
|
-
}
|
|
575
|
-
return Array.from(recommendations);
|
|
576
|
-
}
|
|
577
|
-
// ============================================================================
|
|
578
107
|
// Task Executor
|
|
579
108
|
// ============================================================================
|
|
580
109
|
export class DomainTaskExecutor {
|
|
581
110
|
kernel;
|
|
582
111
|
eventBus;
|
|
583
|
-
|
|
112
|
+
_config;
|
|
584
113
|
resultSaver;
|
|
585
114
|
// Instance-level service caches to prevent cross-contamination between executor instances
|
|
586
115
|
coverageAnalyzer = null;
|
|
@@ -598,7 +127,7 @@ export class DomainTaskExecutor {
|
|
|
598
127
|
constructor(kernel, eventBus, config) {
|
|
599
128
|
this.kernel = kernel;
|
|
600
129
|
this.eventBus = eventBus;
|
|
601
|
-
this.
|
|
130
|
+
this._config = {
|
|
602
131
|
timeout: config?.timeout ?? 300000,
|
|
603
132
|
maxRetries: config?.maxRetries ?? 3,
|
|
604
133
|
enableCaching: config?.enableCaching ?? true,
|
|
@@ -607,9 +136,20 @@ export class DomainTaskExecutor {
|
|
|
607
136
|
defaultLanguage: config?.defaultLanguage ?? 'typescript',
|
|
608
137
|
defaultFramework: config?.defaultFramework ?? 'vitest',
|
|
609
138
|
};
|
|
610
|
-
this.resultSaver = createResultSaver(this.
|
|
139
|
+
this.resultSaver = createResultSaver(this._config.resultsDir);
|
|
611
140
|
this.registerHandlers();
|
|
612
141
|
}
|
|
142
|
+
// ============================================================================
|
|
143
|
+
// TaskHandlerContext implementation (used by handler modules)
|
|
144
|
+
// ============================================================================
|
|
145
|
+
/** Expose config to handler modules */
|
|
146
|
+
get config() {
|
|
147
|
+
return this._config;
|
|
148
|
+
}
|
|
149
|
+
/** Register a handler for a given task type */
|
|
150
|
+
registerHandler(type, handler) {
|
|
151
|
+
this.taskHandlers.set(type, handler);
|
|
152
|
+
}
|
|
613
153
|
/** Connect QualityFeedbackLoop for routing outcome recording */
|
|
614
154
|
setQualityFeedbackLoop(loop) {
|
|
615
155
|
this.qualityFeedbackLoop = loop;
|
|
@@ -647,6 +187,21 @@ export class DomainTaskExecutor {
|
|
|
647
187
|
}
|
|
648
188
|
return this.qualityAnalyzer;
|
|
649
189
|
}
|
|
190
|
+
// ============================================================================
|
|
191
|
+
// Handler Registration (delegates to extracted handler modules)
|
|
192
|
+
// ============================================================================
|
|
193
|
+
registerHandlers() {
|
|
194
|
+
registerTestExecutionHandlers(this);
|
|
195
|
+
registerCoverageHandlers(this);
|
|
196
|
+
registerSecurityHandlers(this);
|
|
197
|
+
registerQualityHandlers(this);
|
|
198
|
+
registerRequirementsHandlers(this);
|
|
199
|
+
registerCodeIntelligenceHandlers(this);
|
|
200
|
+
registerMiscHandlers(this);
|
|
201
|
+
}
|
|
202
|
+
// ============================================================================
|
|
203
|
+
// ADR-051: Agent Booster Execution (Tier 0)
|
|
204
|
+
// ============================================================================
|
|
650
205
|
/**
|
|
651
206
|
* Get or create Agent Booster adapter (instance-level)
|
|
652
207
|
*/
|
|
@@ -675,875 +230,6 @@ export class DomainTaskExecutor {
|
|
|
675
230
|
}
|
|
676
231
|
return this.taskRouter;
|
|
677
232
|
}
|
|
678
|
-
// ============================================================================
|
|
679
|
-
// Task Handler Registration (instance-level)
|
|
680
|
-
// ============================================================================
|
|
681
|
-
registerHandlers() {
|
|
682
|
-
// Register test generation handler - REAL IMPLEMENTATION
|
|
683
|
-
this.taskHandlers.set('generate-tests', async (task) => {
|
|
684
|
-
const payload = task.payload;
|
|
685
|
-
try {
|
|
686
|
-
const generator = this.getTestGenerator();
|
|
687
|
-
// Determine source files to analyze
|
|
688
|
-
let sourceFiles = [];
|
|
689
|
-
if (payload.sourceFiles && payload.sourceFiles.length > 0) {
|
|
690
|
-
sourceFiles = payload.sourceFiles;
|
|
691
|
-
}
|
|
692
|
-
else if (payload.filePath) {
|
|
693
|
-
sourceFiles = [payload.filePath];
|
|
694
|
-
}
|
|
695
|
-
else if (payload.sourceCode) {
|
|
696
|
-
// Write temporary file for analysis if only source code provided
|
|
697
|
-
// Use correct file extension based on language parameter
|
|
698
|
-
const langExtMap = {
|
|
699
|
-
python: '.py', typescript: '.ts', javascript: '.js',
|
|
700
|
-
go: '.go', rust: '.rs', java: '.java', ruby: '.rb',
|
|
701
|
-
kotlin: '.kt', csharp: '.cs', php: '.php', swift: '.swift',
|
|
702
|
-
cpp: '.cpp', c: '.c', scala: '.scala',
|
|
703
|
-
};
|
|
704
|
-
const ext = langExtMap[payload.language?.toLowerCase() || 'typescript'] || '.ts';
|
|
705
|
-
const tempPath = `/tmp/aqe-temp-${uuidv4()}${ext}`;
|
|
706
|
-
await fs.writeFile(tempPath, payload.sourceCode, 'utf-8');
|
|
707
|
-
sourceFiles = [tempPath];
|
|
708
|
-
}
|
|
709
|
-
if (sourceFiles.length === 0) {
|
|
710
|
-
// Return a graceful fallback with warning when no source files provided
|
|
711
|
-
return ok({
|
|
712
|
-
testsGenerated: 0,
|
|
713
|
-
coverageEstimate: 0,
|
|
714
|
-
tests: [],
|
|
715
|
-
patternsUsed: [],
|
|
716
|
-
warning: 'No source files or code provided for test generation. Provide sourceCode, filePath, or sourceFiles in the payload.',
|
|
717
|
-
});
|
|
718
|
-
}
|
|
719
|
-
// Use the real TestGeneratorService
|
|
720
|
-
const framework = (payload.framework || 'vitest');
|
|
721
|
-
const result = await generator.generateTests({
|
|
722
|
-
sourceFiles,
|
|
723
|
-
testType: payload.testType || 'unit',
|
|
724
|
-
framework,
|
|
725
|
-
coverageTarget: payload.coverageGoal || 80,
|
|
726
|
-
patterns: [],
|
|
727
|
-
});
|
|
728
|
-
if (!result.success) {
|
|
729
|
-
return result;
|
|
730
|
-
}
|
|
731
|
-
const generatedTests = result.value;
|
|
732
|
-
return ok({
|
|
733
|
-
testsGenerated: generatedTests.tests.length,
|
|
734
|
-
coverageEstimate: generatedTests.coverageEstimate,
|
|
735
|
-
tests: generatedTests.tests.map(t => ({
|
|
736
|
-
name: t.name,
|
|
737
|
-
file: t.testFile,
|
|
738
|
-
type: t.type,
|
|
739
|
-
sourceFile: t.sourceFile,
|
|
740
|
-
assertions: t.assertions,
|
|
741
|
-
testCode: t.testCode,
|
|
742
|
-
})),
|
|
743
|
-
patternsUsed: generatedTests.patternsUsed,
|
|
744
|
-
});
|
|
745
|
-
}
|
|
746
|
-
catch (error) {
|
|
747
|
-
return err(toError(error));
|
|
748
|
-
}
|
|
749
|
-
});
|
|
750
|
-
// Register coverage analysis handler - REAL IMPLEMENTATION
|
|
751
|
-
this.taskHandlers.set('analyze-coverage', async (task) => {
|
|
752
|
-
const payload = task.payload;
|
|
753
|
-
try {
|
|
754
|
-
const analyzer = this.getCoverageAnalyzer();
|
|
755
|
-
const targetPath = payload.target || process.cwd();
|
|
756
|
-
const threshold = payload.threshold || 80;
|
|
757
|
-
// Try to find and read actual coverage files
|
|
758
|
-
let coverageData = await loadCoverageData(targetPath);
|
|
759
|
-
if (!coverageData) {
|
|
760
|
-
// No coverage data found — attempt to collect it by running tests with coverage
|
|
761
|
-
let collected = false;
|
|
762
|
-
try {
|
|
763
|
-
const { execSync } = await import('child_process');
|
|
764
|
-
// Detect test runner from package.json
|
|
765
|
-
let coverageCmd = 'npx vitest run --coverage --reporter=json 2>/dev/null';
|
|
766
|
-
try {
|
|
767
|
-
const pkgContent = await fs.readFile(path.join(targetPath, 'package.json'), 'utf-8');
|
|
768
|
-
const pkg = safeJsonParse(pkgContent);
|
|
769
|
-
const deps = { ...(pkg.devDependencies || {}), ...(pkg.dependencies || {}) };
|
|
770
|
-
if (deps['jest'] || deps['@jest/core']) {
|
|
771
|
-
coverageCmd = 'npx jest --coverage --json 2>/dev/null';
|
|
772
|
-
}
|
|
773
|
-
else if (deps['mocha'] || deps['nyc']) {
|
|
774
|
-
coverageCmd = 'npx nyc mocha 2>/dev/null';
|
|
775
|
-
}
|
|
776
|
-
// vitest is the default — covers vitest, @vitest/coverage-v8, etc.
|
|
777
|
-
}
|
|
778
|
-
catch {
|
|
779
|
-
// No package.json — use default vitest
|
|
780
|
-
}
|
|
781
|
-
execSync(coverageCmd, {
|
|
782
|
-
cwd: targetPath,
|
|
783
|
-
timeout: 120000,
|
|
784
|
-
encoding: 'utf-8',
|
|
785
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
786
|
-
});
|
|
787
|
-
collected = true;
|
|
788
|
-
}
|
|
789
|
-
catch {
|
|
790
|
-
// Test runner failed or not available — that's OK, we'll check for output anyway
|
|
791
|
-
}
|
|
792
|
-
// Re-check for coverage data after collection attempt
|
|
793
|
-
coverageData = await loadCoverageData(targetPath);
|
|
794
|
-
if (!coverageData) {
|
|
795
|
-
return ok({
|
|
796
|
-
lineCoverage: 0,
|
|
797
|
-
branchCoverage: 0,
|
|
798
|
-
functionCoverage: 0,
|
|
799
|
-
statementCoverage: 0,
|
|
800
|
-
totalFiles: 0,
|
|
801
|
-
coverageByFile: [],
|
|
802
|
-
gaps: [],
|
|
803
|
-
algorithm: 'sublinear-O(log n)',
|
|
804
|
-
warning: collected
|
|
805
|
-
? 'Tests ran but no coverage output was generated. Ensure a coverage provider is configured (e.g., @vitest/coverage-v8, istanbul).'
|
|
806
|
-
: 'No coverage data found and could not run tests automatically. Run: npm test -- --coverage',
|
|
807
|
-
});
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
// Analyze coverage using the real CoverageAnalyzerService
|
|
811
|
-
const analysisResult = await analyzer.analyze({
|
|
812
|
-
coverageData,
|
|
813
|
-
threshold,
|
|
814
|
-
includeFileDetails: payload.detectGaps,
|
|
815
|
-
});
|
|
816
|
-
if (!analysisResult.success) {
|
|
817
|
-
return analysisResult;
|
|
818
|
-
}
|
|
819
|
-
const report = analysisResult.value;
|
|
820
|
-
// Find gaps if requested
|
|
821
|
-
let gaps = [];
|
|
822
|
-
if (payload.detectGaps) {
|
|
823
|
-
const gapsResult = await analyzer.findGaps(coverageData, threshold);
|
|
824
|
-
if (gapsResult.success) {
|
|
825
|
-
gaps = gapsResult.value.gaps.map(gap => ({
|
|
826
|
-
file: gap.file,
|
|
827
|
-
lines: gap.lines,
|
|
828
|
-
risk: gap.severity,
|
|
829
|
-
}));
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
return ok({
|
|
833
|
-
lineCoverage: Math.round(report.summary.line * 10) / 10,
|
|
834
|
-
branchCoverage: Math.round(report.summary.branch * 10) / 10,
|
|
835
|
-
functionCoverage: Math.round(report.summary.function * 10) / 10,
|
|
836
|
-
statementCoverage: Math.round(report.summary.statement * 10) / 10,
|
|
837
|
-
totalFiles: report.summary.files,
|
|
838
|
-
coverageByFile: coverageData.files.map(f => ({
|
|
839
|
-
file: f.path,
|
|
840
|
-
lineCoverage: f.lines.total > 0 ? Math.round((f.lines.covered / f.lines.total) * 1000) / 10 : 0,
|
|
841
|
-
branchCoverage: f.branches.total > 0 ? Math.round((f.branches.covered / f.branches.total) * 1000) / 10 : 0,
|
|
842
|
-
functionCoverage: f.functions.total > 0 ? Math.round((f.functions.covered / f.functions.total) * 1000) / 10 : 0,
|
|
843
|
-
})),
|
|
844
|
-
gaps,
|
|
845
|
-
meetsThreshold: report.meetsThreshold,
|
|
846
|
-
delta: report.delta,
|
|
847
|
-
recommendations: report.recommendations,
|
|
848
|
-
algorithm: 'sublinear-O(log n)',
|
|
849
|
-
});
|
|
850
|
-
}
|
|
851
|
-
catch (error) {
|
|
852
|
-
return err(toError(error));
|
|
853
|
-
}
|
|
854
|
-
});
|
|
855
|
-
// Register security scan handler - REAL IMPLEMENTATION
|
|
856
|
-
this.taskHandlers.set('scan-security', async (task) => {
|
|
857
|
-
const payload = task.payload;
|
|
858
|
-
try {
|
|
859
|
-
const scanner = this.getSecurityScanner();
|
|
860
|
-
const targetPath = payload.target || process.cwd();
|
|
861
|
-
// Discover files to scan
|
|
862
|
-
const filesToScan = await discoverSourceFiles(targetPath);
|
|
863
|
-
if (filesToScan.length === 0) {
|
|
864
|
-
return ok({
|
|
865
|
-
vulnerabilities: 0,
|
|
866
|
-
critical: 0,
|
|
867
|
-
high: 0,
|
|
868
|
-
medium: 0,
|
|
869
|
-
low: 0,
|
|
870
|
-
informational: 0,
|
|
871
|
-
topVulnerabilities: [],
|
|
872
|
-
recommendations: ['No source files found to scan'],
|
|
873
|
-
scanTypes: {
|
|
874
|
-
sast: payload.sast !== false,
|
|
875
|
-
dast: payload.dast || false,
|
|
876
|
-
},
|
|
877
|
-
warning: `No source files found in ${targetPath}`,
|
|
878
|
-
});
|
|
879
|
-
}
|
|
880
|
-
// Separate files by language capability
|
|
881
|
-
const jstsFiles = filesToScan.filter(f => /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(f));
|
|
882
|
-
const otherFiles = filesToScan.filter(f => !/\.(ts|tsx|js|jsx|mjs|cjs)$/.test(f));
|
|
883
|
-
// Run basic cross-language security patterns on non-JS/TS files
|
|
884
|
-
const crossLangVulns = [];
|
|
885
|
-
// Run secret/CORS patterns on ALL files (not just otherFiles) to catch JS/TS secrets too
|
|
886
|
-
for (const filePath of filesToScan) {
|
|
887
|
-
try {
|
|
888
|
-
const content = await fs.readFile(filePath, 'utf-8');
|
|
889
|
-
const lines = content.split('\n');
|
|
890
|
-
const relPath = filePath.startsWith(targetPath)
|
|
891
|
-
? filePath.slice(targetPath.length).replace(/^\//, '')
|
|
892
|
-
: filePath;
|
|
893
|
-
// Pattern: Hardcoded secrets/keys
|
|
894
|
-
// Fix #287: Use \w* around keywords to match SECRET_KEY, JWT_SECRET, API_TOKEN, etc.
|
|
895
|
-
const secretPatterns = [
|
|
896
|
-
{ regex: /\w*(?:secret|password|passwd|api_key|apikey|private_key|jwt_secret)\w*\s*[=:]\s*['"][^'"]{4,}['"]/gi, title: 'Hardcoded secret', severity: 'critical' },
|
|
897
|
-
{ regex: /\w*(?:token|auth_token|access_key|secret_key)\w*\s*[=:]\s*['"][^'"]{8,}['"]/gi, title: 'Hardcoded credential', severity: 'critical' },
|
|
898
|
-
{ regex: /(?:AWS_SECRET|GITHUB_TOKEN|SLACK_TOKEN|OPENAI_API_KEY)\s*[=:]\s*['"][^'"]+['"]/gi, title: 'Hardcoded cloud credential', severity: 'critical' },
|
|
899
|
-
];
|
|
900
|
-
for (const pattern of secretPatterns) {
|
|
901
|
-
for (let i = 0; i < lines.length; i++) {
|
|
902
|
-
// Use matchAll to find ALL secrets on a single line (not just first)
|
|
903
|
-
const matches = [...lines[i].matchAll(pattern.regex)];
|
|
904
|
-
for (const _m of matches) {
|
|
905
|
-
crossLangVulns.push({
|
|
906
|
-
title: pattern.title,
|
|
907
|
-
severity: pattern.severity,
|
|
908
|
-
location: { file: relPath, line: i + 1 },
|
|
909
|
-
description: `Potential hardcoded secret found at line ${i + 1}`,
|
|
910
|
-
category: 'sensitive-data',
|
|
911
|
-
});
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
// Pattern: SQL injection risks
|
|
916
|
-
const sqlPatterns = /(?:execute|query|cursor\.execute)\s*\(\s*(?:f['"]|['"].*%s|['"].*\+\s*\w)/gi;
|
|
917
|
-
for (let i = 0; i < lines.length; i++) {
|
|
918
|
-
if (sqlPatterns.test(lines[i])) {
|
|
919
|
-
crossLangVulns.push({
|
|
920
|
-
title: 'Potential SQL injection',
|
|
921
|
-
severity: 'high',
|
|
922
|
-
location: { file: relPath, line: i + 1 },
|
|
923
|
-
description: 'String interpolation in SQL query — use parameterized queries',
|
|
924
|
-
category: 'injection',
|
|
925
|
-
});
|
|
926
|
-
}
|
|
927
|
-
sqlPatterns.lastIndex = 0;
|
|
928
|
-
}
|
|
929
|
-
// Pattern: CORS wildcard (multi-framework)
|
|
930
|
-
const corsPatterns = [
|
|
931
|
-
/allow_origins\s*=\s*\[?\s*['"]?\*['"]?\s*\]?/i, // Python FastAPI/Flask
|
|
932
|
-
/cors\(\s*\{[^}]*origin:\s*['"]?\*['"]?/i, // Express.js cors()
|
|
933
|
-
/Access-Control-Allow-Origin['":\s]+\*/i, // Raw header / Nginx / .htaccess
|
|
934
|
-
/@CrossOrigin\(\s*origins?\s*=\s*["']\*["']/i, // Spring Boot
|
|
935
|
-
/\.Header\(\)\.Set\(["']Access-Control-Allow-Origin["'],\s*["']\*["']/i, // Go
|
|
936
|
-
];
|
|
937
|
-
for (const corsPattern of corsPatterns) {
|
|
938
|
-
if (corsPattern.test(content)) {
|
|
939
|
-
crossLangVulns.push({
|
|
940
|
-
title: 'CORS wildcard origin',
|
|
941
|
-
severity: 'high',
|
|
942
|
-
location: { file: relPath, line: lines.findIndex(l => corsPattern.test(l)) + 1 },
|
|
943
|
-
description: 'CORS configured with wildcard (*) origin — restrict to specific domains',
|
|
944
|
-
category: 'security-misconfiguration',
|
|
945
|
-
});
|
|
946
|
-
break; // One CORS finding per file is enough
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
// Pattern: Debug/development mode enabled
|
|
950
|
-
if (/(?:DEBUG|debug)\s*[=:]\s*(?:True|true|1)/i.test(content)) {
|
|
951
|
-
crossLangVulns.push({
|
|
952
|
-
title: 'Debug mode enabled',
|
|
953
|
-
severity: 'medium',
|
|
954
|
-
location: { file: relPath, line: lines.findIndex(l => /DEBUG\s*[=:]\s*(?:True|true|1)/i.test(l)) + 1 },
|
|
955
|
-
description: 'Debug mode should be disabled in production',
|
|
956
|
-
category: 'security-misconfiguration',
|
|
957
|
-
});
|
|
958
|
-
}
|
|
959
|
-
// Pattern: Eval/exec usage
|
|
960
|
-
if (/\b(?:eval|exec)\s*\(/i.test(content)) {
|
|
961
|
-
crossLangVulns.push({
|
|
962
|
-
title: 'Dangerous eval/exec usage',
|
|
963
|
-
severity: 'high',
|
|
964
|
-
location: { file: relPath, line: lines.findIndex(l => /\b(?:eval|exec)\s*\(/.test(l)) + 1 },
|
|
965
|
-
description: 'eval/exec can lead to code injection — avoid using with user input',
|
|
966
|
-
category: 'injection',
|
|
967
|
-
});
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
catch {
|
|
971
|
-
// Skip unreadable files
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
// Also check dependency manifests for known vulnerable packages
|
|
975
|
-
const depManifests = ['requirements.txt', 'pyproject.toml', 'Gemfile', 'go.mod', 'Cargo.toml'];
|
|
976
|
-
for (const manifest of depManifests) {
|
|
977
|
-
const manifestPath = path.join(targetPath, manifest);
|
|
978
|
-
try {
|
|
979
|
-
const manifestContent = await fs.readFile(manifestPath, 'utf-8');
|
|
980
|
-
crossLangVulns.push({
|
|
981
|
-
title: 'Dependency audit recommended',
|
|
982
|
-
severity: 'informational',
|
|
983
|
-
location: { file: manifest, line: 1 },
|
|
984
|
-
description: `Found ${manifest} — run language-specific dependency audit (e.g., pip-audit, npm audit, cargo audit)`,
|
|
985
|
-
category: 'dependencies',
|
|
986
|
-
});
|
|
987
|
-
// Check for known high-severity CVEs in Python dependencies
|
|
988
|
-
if (manifest === 'requirements.txt' || manifest === 'pyproject.toml') {
|
|
989
|
-
const knownCVEs = [
|
|
990
|
-
{ pkg: 'python-jose', pattern: /python-jose/i, cve: 'CVE-2024-33663', severity: 'high', title: 'python-jose ECDSA key confusion (CVE-2024-33663)', description: 'python-jose allows ECDSA key confusion — upgrade to >=3.3.0 or switch to PyJWT' },
|
|
991
|
-
{ pkg: 'python-jose', pattern: /python-jose/i, cve: 'CVE-2024-33664', severity: 'high', title: 'python-jose JWT algorithm confusion (CVE-2024-33664)', description: 'python-jose JWT algorithm confusion vulnerability — upgrade or switch to PyJWT' },
|
|
992
|
-
{ pkg: 'python-multipart', pattern: /python-multipart/i, cve: 'CVE-2026-24486', severity: 'critical', title: 'python-multipart DoS (CVE-2026-24486)', description: 'python-multipart denial of service via crafted multipart data — upgrade to >=0.0.18' },
|
|
993
|
-
];
|
|
994
|
-
for (const known of knownCVEs) {
|
|
995
|
-
if (known.pattern.test(manifestContent)) {
|
|
996
|
-
crossLangVulns.push({
|
|
997
|
-
title: known.title,
|
|
998
|
-
severity: known.severity,
|
|
999
|
-
location: { file: manifest, line: manifestContent.split('\n').findIndex(l => known.pattern.test(l)) + 1 },
|
|
1000
|
-
description: known.description,
|
|
1001
|
-
category: 'dependencies',
|
|
1002
|
-
});
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
catch {
|
|
1008
|
-
// Manifest doesn't exist
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
// Convert JS/TS file paths to FilePath value objects for the SAST scanner
|
|
1012
|
-
const filePathObjects = jstsFiles.map(filePath => FilePath.create(filePath));
|
|
1013
|
-
// Run SAST scan on JS/TS files if requested and files exist
|
|
1014
|
-
let sastResult = null;
|
|
1015
|
-
if (payload.sast !== false && filePathObjects.length > 0) {
|
|
1016
|
-
const result = await scanner.scanFiles(filePathObjects);
|
|
1017
|
-
if (result.success) {
|
|
1018
|
-
sastResult = result.value;
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
// Run DAST scan if URL provided and dast is enabled
|
|
1022
|
-
let dastResult = null;
|
|
1023
|
-
if (payload.dast && payload.targetUrl) {
|
|
1024
|
-
const result = await scanner.scanUrl(payload.targetUrl, {
|
|
1025
|
-
activeScanning: true,
|
|
1026
|
-
maxDepth: 3,
|
|
1027
|
-
timeout: 30000,
|
|
1028
|
-
});
|
|
1029
|
-
if (result.success) {
|
|
1030
|
-
dastResult = result.value;
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
// Combine results from all scan sources - SAST, DAST, and cross-language patterns
|
|
1034
|
-
const crossLangSeverityCounts = {
|
|
1035
|
-
critical: crossLangVulns.filter(v => v.severity === 'critical').length,
|
|
1036
|
-
high: crossLangVulns.filter(v => v.severity === 'high').length,
|
|
1037
|
-
medium: crossLangVulns.filter(v => v.severity === 'medium').length,
|
|
1038
|
-
low: crossLangVulns.filter(v => v.severity === 'low').length,
|
|
1039
|
-
informational: crossLangVulns.filter(v => v.severity === 'informational').length,
|
|
1040
|
-
};
|
|
1041
|
-
const summary = {
|
|
1042
|
-
critical: (sastResult?.summary?.critical || 0) + (dastResult?.summary?.critical || 0) + crossLangSeverityCounts.critical,
|
|
1043
|
-
high: (sastResult?.summary?.high || 0) + (dastResult?.summary?.high || 0) + crossLangSeverityCounts.high,
|
|
1044
|
-
medium: (sastResult?.summary?.medium || 0) + (dastResult?.summary?.medium || 0) + crossLangSeverityCounts.medium,
|
|
1045
|
-
low: (sastResult?.summary?.low || 0) + (dastResult?.summary?.low || 0) + crossLangSeverityCounts.low,
|
|
1046
|
-
informational: (sastResult?.summary?.informational || 0) + (dastResult?.summary?.informational || 0) + crossLangSeverityCounts.informational,
|
|
1047
|
-
};
|
|
1048
|
-
// Extract top vulnerabilities from all sources
|
|
1049
|
-
const allVulns = [
|
|
1050
|
-
...(sastResult?.vulnerabilities || []),
|
|
1051
|
-
...(dastResult?.vulnerabilities || []),
|
|
1052
|
-
...crossLangVulns,
|
|
1053
|
-
];
|
|
1054
|
-
const topVulnerabilities = allVulns
|
|
1055
|
-
.sort((a, b) => {
|
|
1056
|
-
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3, informational: 4 };
|
|
1057
|
-
return severityOrder[a.severity] - severityOrder[b.severity];
|
|
1058
|
-
})
|
|
1059
|
-
.slice(0, 10)
|
|
1060
|
-
.map(v => ({
|
|
1061
|
-
type: v.title,
|
|
1062
|
-
severity: v.severity,
|
|
1063
|
-
file: v.location.file,
|
|
1064
|
-
line: v.location.line,
|
|
1065
|
-
description: v.description,
|
|
1066
|
-
}));
|
|
1067
|
-
// Generate recommendations based on findings
|
|
1068
|
-
const recommendations = generateSecurityRecommendations(allVulns);
|
|
1069
|
-
return ok({
|
|
1070
|
-
vulnerabilities: allVulns.length,
|
|
1071
|
-
critical: summary.critical,
|
|
1072
|
-
high: summary.high,
|
|
1073
|
-
medium: summary.medium,
|
|
1074
|
-
low: summary.low,
|
|
1075
|
-
informational: summary.informational,
|
|
1076
|
-
topVulnerabilities,
|
|
1077
|
-
recommendations,
|
|
1078
|
-
scanTypes: {
|
|
1079
|
-
sast: payload.sast !== false,
|
|
1080
|
-
dast: payload.dast || false,
|
|
1081
|
-
},
|
|
1082
|
-
filesScanned: filesToScan.length,
|
|
1083
|
-
jstsFilesScanned: jstsFiles.length,
|
|
1084
|
-
otherFilesScanned: otherFiles.length,
|
|
1085
|
-
coverage: sastResult?.coverage,
|
|
1086
|
-
...(otherFiles.length > 0 && jstsFiles.length === 0 ? {
|
|
1087
|
-
note: 'Non-JS/TS files were scanned with cross-language pattern matching. For deeper analysis, use language-specific security tools.',
|
|
1088
|
-
} : {}),
|
|
1089
|
-
});
|
|
1090
|
-
}
|
|
1091
|
-
catch (error) {
|
|
1092
|
-
return err(toError(error));
|
|
1093
|
-
}
|
|
1094
|
-
});
|
|
1095
|
-
// Register code indexing handler - REAL IMPLEMENTATION
|
|
1096
|
-
this.taskHandlers.set('index-code', async (task) => {
|
|
1097
|
-
const payload = task.payload;
|
|
1098
|
-
try {
|
|
1099
|
-
const kg = this.getKnowledgeGraph();
|
|
1100
|
-
const targetPath = payload.target || process.cwd();
|
|
1101
|
-
const startTime = Date.now();
|
|
1102
|
-
// Discover files to index
|
|
1103
|
-
const filesToIndex = await discoverSourceFiles(targetPath, {
|
|
1104
|
-
includeTests: payload.includeTests !== false,
|
|
1105
|
-
languages: payload.languages,
|
|
1106
|
-
});
|
|
1107
|
-
if (filesToIndex.length === 0) {
|
|
1108
|
-
return ok({
|
|
1109
|
-
filesIndexed: 0,
|
|
1110
|
-
nodesCreated: 0,
|
|
1111
|
-
edgesCreated: 0,
|
|
1112
|
-
target: targetPath,
|
|
1113
|
-
incremental: payload.incremental || false,
|
|
1114
|
-
languages: payload.languages || [],
|
|
1115
|
-
duration: Date.now() - startTime,
|
|
1116
|
-
warning: `No source files found in ${targetPath}. Searched for: TypeScript, JavaScript, Python, Go, Rust, Java, Ruby, C/C++, and more.`,
|
|
1117
|
-
});
|
|
1118
|
-
}
|
|
1119
|
-
// Use the real KnowledgeGraphService to index files
|
|
1120
|
-
const result = await kg.index({
|
|
1121
|
-
paths: filesToIndex,
|
|
1122
|
-
incremental: payload.incremental || false,
|
|
1123
|
-
includeTests: payload.includeTests !== false,
|
|
1124
|
-
languages: payload.languages,
|
|
1125
|
-
});
|
|
1126
|
-
if (!result.success) {
|
|
1127
|
-
return result;
|
|
1128
|
-
}
|
|
1129
|
-
const indexResult = result.value;
|
|
1130
|
-
// Detect languages from files
|
|
1131
|
-
const detectedLanguages = new Set();
|
|
1132
|
-
const extToLang = {
|
|
1133
|
-
ts: 'typescript', tsx: 'typescript',
|
|
1134
|
-
js: 'javascript', jsx: 'javascript', mjs: 'javascript', cjs: 'javascript',
|
|
1135
|
-
py: 'python', pyw: 'python',
|
|
1136
|
-
go: 'go', rs: 'rust',
|
|
1137
|
-
java: 'java', kt: 'kotlin', kts: 'kotlin',
|
|
1138
|
-
rb: 'ruby', cs: 'csharp', php: 'php', swift: 'swift',
|
|
1139
|
-
c: 'c', h: 'c', cpp: 'cpp', hpp: 'cpp', cc: 'cpp',
|
|
1140
|
-
scala: 'scala',
|
|
1141
|
-
};
|
|
1142
|
-
for (const file of filesToIndex) {
|
|
1143
|
-
const ext = path.extname(file).slice(1);
|
|
1144
|
-
const lang = extToLang[ext];
|
|
1145
|
-
if (lang)
|
|
1146
|
-
detectedLanguages.add(lang);
|
|
1147
|
-
}
|
|
1148
|
-
return ok({
|
|
1149
|
-
filesIndexed: indexResult.filesIndexed,
|
|
1150
|
-
nodesCreated: indexResult.nodesCreated,
|
|
1151
|
-
edgesCreated: indexResult.edgesCreated,
|
|
1152
|
-
target: targetPath,
|
|
1153
|
-
incremental: payload.incremental || false,
|
|
1154
|
-
languages: Array.from(detectedLanguages),
|
|
1155
|
-
duration: indexResult.duration,
|
|
1156
|
-
errors: indexResult.errors,
|
|
1157
|
-
});
|
|
1158
|
-
}
|
|
1159
|
-
catch (error) {
|
|
1160
|
-
return err(toError(error));
|
|
1161
|
-
}
|
|
1162
|
-
});
|
|
1163
|
-
// Register quality assessment handler - REAL IMPLEMENTATION
|
|
1164
|
-
this.taskHandlers.set('assess-quality', async (task) => {
|
|
1165
|
-
const payload = task.payload;
|
|
1166
|
-
try {
|
|
1167
|
-
const analyzer = this.getQualityAnalyzer();
|
|
1168
|
-
const threshold = payload.threshold || 80;
|
|
1169
|
-
// Determine source files to analyze
|
|
1170
|
-
let sourceFiles = [];
|
|
1171
|
-
if (payload.sourceFiles && payload.sourceFiles.length > 0) {
|
|
1172
|
-
sourceFiles = payload.sourceFiles;
|
|
1173
|
-
}
|
|
1174
|
-
else if (payload.target) {
|
|
1175
|
-
sourceFiles = await discoverSourceFiles(payload.target, { includeTests: false });
|
|
1176
|
-
}
|
|
1177
|
-
else {
|
|
1178
|
-
sourceFiles = await discoverSourceFiles(process.cwd(), { includeTests: false });
|
|
1179
|
-
}
|
|
1180
|
-
if (sourceFiles.length === 0) {
|
|
1181
|
-
return ok({
|
|
1182
|
-
qualityScore: 0,
|
|
1183
|
-
passed: false,
|
|
1184
|
-
threshold,
|
|
1185
|
-
metrics: {
|
|
1186
|
-
coverage: 0,
|
|
1187
|
-
complexity: 0,
|
|
1188
|
-
maintainability: 0,
|
|
1189
|
-
testability: 0,
|
|
1190
|
-
},
|
|
1191
|
-
recommendations: ['No source files found for quality assessment'],
|
|
1192
|
-
warning: 'No source files found',
|
|
1193
|
-
});
|
|
1194
|
-
}
|
|
1195
|
-
// Use the real QualityAnalyzerService
|
|
1196
|
-
const result = await analyzer.analyzeQuality({
|
|
1197
|
-
sourceFiles,
|
|
1198
|
-
includeMetrics: payload.metrics || ['coverage', 'complexity', 'maintainability', 'testability'],
|
|
1199
|
-
});
|
|
1200
|
-
if (!result.success) {
|
|
1201
|
-
return result;
|
|
1202
|
-
}
|
|
1203
|
-
const report = result.value;
|
|
1204
|
-
const passed = report.score.overall >= threshold;
|
|
1205
|
-
// Convert metrics to the expected format
|
|
1206
|
-
const metrics = {};
|
|
1207
|
-
for (const metric of report.metrics) {
|
|
1208
|
-
metrics[metric.name] = metric.value;
|
|
1209
|
-
}
|
|
1210
|
-
return ok({
|
|
1211
|
-
qualityScore: report.score.overall,
|
|
1212
|
-
passed,
|
|
1213
|
-
threshold,
|
|
1214
|
-
metrics: {
|
|
1215
|
-
coverage: report.score.coverage,
|
|
1216
|
-
complexity: report.score.complexity,
|
|
1217
|
-
maintainability: report.score.maintainability,
|
|
1218
|
-
security: report.score.security,
|
|
1219
|
-
...metrics,
|
|
1220
|
-
},
|
|
1221
|
-
recommendations: report.recommendations.map(r => `[${r.type}] ${r.title}: ${r.description}`),
|
|
1222
|
-
trends: report.trends.map(t => ({
|
|
1223
|
-
metric: t.metric,
|
|
1224
|
-
direction: t.direction,
|
|
1225
|
-
dataPoints: t.dataPoints.length,
|
|
1226
|
-
})),
|
|
1227
|
-
filesAnalyzed: sourceFiles.length,
|
|
1228
|
-
});
|
|
1229
|
-
}
|
|
1230
|
-
catch (error) {
|
|
1231
|
-
return err(toError(error));
|
|
1232
|
-
}
|
|
1233
|
-
});
|
|
1234
|
-
// Register test execution handler - runs real tests via child process
|
|
1235
|
-
this.taskHandlers.set('execute-tests', async (task) => {
|
|
1236
|
-
const payload = task.payload;
|
|
1237
|
-
try {
|
|
1238
|
-
const { execSync } = await import('child_process');
|
|
1239
|
-
const testFiles = payload.testFiles || [];
|
|
1240
|
-
if (testFiles.length === 0) {
|
|
1241
|
-
return ok({
|
|
1242
|
-
total: 0, passed: 0, failed: 0, skipped: 0,
|
|
1243
|
-
duration: 0, coverage: 0, failedTests: [],
|
|
1244
|
-
warning: 'No test files specified. Provide testFiles array with paths to test files.',
|
|
1245
|
-
});
|
|
1246
|
-
}
|
|
1247
|
-
// Attempt to run tests using common test runners
|
|
1248
|
-
const cwd = process.cwd();
|
|
1249
|
-
let output;
|
|
1250
|
-
try {
|
|
1251
|
-
// Try vitest first, then jest, then mocha
|
|
1252
|
-
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'] });
|
|
1253
|
-
}
|
|
1254
|
-
catch (execError) {
|
|
1255
|
-
// Test runner may exit non-zero when tests fail — that's expected
|
|
1256
|
-
output = execError.stdout || '';
|
|
1257
|
-
}
|
|
1258
|
-
// Try to parse JSON output from test runner
|
|
1259
|
-
try {
|
|
1260
|
-
const jsonStart = output.indexOf('{');
|
|
1261
|
-
if (jsonStart >= 0) {
|
|
1262
|
-
const json = JSON.parse(output.slice(jsonStart));
|
|
1263
|
-
// vitest format
|
|
1264
|
-
if (json.testResults) {
|
|
1265
|
-
const total = json.numTotalTests || 0;
|
|
1266
|
-
const passed = json.numPassedTests || 0;
|
|
1267
|
-
const failed = json.numFailedTests || 0;
|
|
1268
|
-
return ok({ total, passed, failed, skipped: total - passed - failed, duration: 0, coverage: 0, failedTests: [] });
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
catch {
|
|
1273
|
-
// JSON parsing failed — return raw info
|
|
1274
|
-
}
|
|
1275
|
-
return ok({
|
|
1276
|
-
total: testFiles.length, passed: 0, failed: 0, skipped: 0,
|
|
1277
|
-
duration: 0, coverage: 0, failedTests: [],
|
|
1278
|
-
warning: 'Could not parse test runner output. Check that vitest or jest is installed.',
|
|
1279
|
-
rawOutput: output.slice(0, 500),
|
|
1280
|
-
});
|
|
1281
|
-
}
|
|
1282
|
-
catch (error) {
|
|
1283
|
-
return err(toError(error));
|
|
1284
|
-
}
|
|
1285
|
-
});
|
|
1286
|
-
// Register defect prediction handler - REAL IMPLEMENTATION
|
|
1287
|
-
this.taskHandlers.set('predict-defects', async (task) => {
|
|
1288
|
-
const payload = task.payload;
|
|
1289
|
-
try {
|
|
1290
|
-
const targetPath = payload.target || process.cwd();
|
|
1291
|
-
const minConfidence = payload.minConfidence || 0.5;
|
|
1292
|
-
// Discover actual source files in the target directory
|
|
1293
|
-
const sourceFiles = await discoverSourceFiles(targetPath, { includeTests: false });
|
|
1294
|
-
if (sourceFiles.length === 0) {
|
|
1295
|
-
return ok({
|
|
1296
|
-
predictedDefects: [],
|
|
1297
|
-
riskScore: 0,
|
|
1298
|
-
recommendations: [
|
|
1299
|
-
`No source files found in ${targetPath}. Ensure the path contains source code files.`,
|
|
1300
|
-
],
|
|
1301
|
-
warning: `No source files found in ${targetPath}`,
|
|
1302
|
-
filesAnalyzed: 0,
|
|
1303
|
-
});
|
|
1304
|
-
}
|
|
1305
|
-
// Analyze each file for defect indicators based on real metrics
|
|
1306
|
-
const predictedDefects = [];
|
|
1307
|
-
for (const filePath of sourceFiles) {
|
|
1308
|
-
try {
|
|
1309
|
-
const content = await fs.readFile(filePath, 'utf-8');
|
|
1310
|
-
const lines = content.split('\n');
|
|
1311
|
-
const lineCount = lines.length;
|
|
1312
|
-
// Calculate complexity indicators from real code
|
|
1313
|
-
let probability = 0;
|
|
1314
|
-
const reasons = [];
|
|
1315
|
-
// Factor 1: File size (large files are more defect-prone)
|
|
1316
|
-
if (lineCount > 500) {
|
|
1317
|
-
probability += 0.25;
|
|
1318
|
-
reasons.push(`Large file (${lineCount} lines)`);
|
|
1319
|
-
}
|
|
1320
|
-
else if (lineCount > 300) {
|
|
1321
|
-
probability += 0.15;
|
|
1322
|
-
reasons.push(`Medium-large file (${lineCount} lines)`);
|
|
1323
|
-
}
|
|
1324
|
-
// Factor 2: Cyclomatic complexity indicators
|
|
1325
|
-
const branchKeywords = content.match(/\b(if|else|switch|case|for|while|catch|&&|\|\|)\b/g) || [];
|
|
1326
|
-
const branchDensity = branchKeywords.length / Math.max(lineCount, 1);
|
|
1327
|
-
if (branchDensity > 0.15) {
|
|
1328
|
-
probability += 0.25;
|
|
1329
|
-
reasons.push(`High branch density (${branchKeywords.length} branches in ${lineCount} lines)`);
|
|
1330
|
-
}
|
|
1331
|
-
else if (branchDensity > 0.08) {
|
|
1332
|
-
probability += 0.10;
|
|
1333
|
-
reasons.push('Moderate branch complexity');
|
|
1334
|
-
}
|
|
1335
|
-
// Factor 3: Deeply nested code
|
|
1336
|
-
const maxIndent = Math.max(...lines.map(l => {
|
|
1337
|
-
const match = l.match(/^(\s*)/);
|
|
1338
|
-
return match ? match[1].length : 0;
|
|
1339
|
-
}));
|
|
1340
|
-
if (maxIndent > 20) {
|
|
1341
|
-
probability += 0.15;
|
|
1342
|
-
reasons.push('Deep nesting detected');
|
|
1343
|
-
}
|
|
1344
|
-
// Factor 4: TODO/FIXME/HACK comments
|
|
1345
|
-
const debtComments = (content.match(/\b(TODO|FIXME|HACK|XXX|WORKAROUND)\b/gi) || []).length;
|
|
1346
|
-
if (debtComments > 3) {
|
|
1347
|
-
probability += 0.15;
|
|
1348
|
-
reasons.push(`${debtComments} technical debt markers`);
|
|
1349
|
-
}
|
|
1350
|
-
// Factor 5: Long functions (heuristic)
|
|
1351
|
-
const functionStarts = (content.match(/\b(function|def|func|async)\b/g) || []).length;
|
|
1352
|
-
if (functionStarts > 0 && lineCount / functionStarts > 80) {
|
|
1353
|
-
probability += 0.10;
|
|
1354
|
-
reasons.push('Potentially long functions');
|
|
1355
|
-
}
|
|
1356
|
-
probability = Math.min(probability, 0.95);
|
|
1357
|
-
if (probability >= minConfidence) {
|
|
1358
|
-
// Use relative path for readability
|
|
1359
|
-
const relativePath = filePath.startsWith(targetPath)
|
|
1360
|
-
? filePath.slice(targetPath.length).replace(/^\//, '')
|
|
1361
|
-
: filePath;
|
|
1362
|
-
predictedDefects.push({
|
|
1363
|
-
file: relativePath,
|
|
1364
|
-
probability: Math.round(probability * 100) / 100,
|
|
1365
|
-
reason: reasons.join('; '),
|
|
1366
|
-
});
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
catch {
|
|
1370
|
-
// Skip files that can't be read
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
// Sort by probability descending
|
|
1374
|
-
predictedDefects.sort((a, b) => b.probability - a.probability);
|
|
1375
|
-
// Calculate overall risk score
|
|
1376
|
-
const avgProb = predictedDefects.length > 0
|
|
1377
|
-
? predictedDefects.reduce((sum, d) => sum + d.probability, 0) / predictedDefects.length
|
|
1378
|
-
: 0;
|
|
1379
|
-
const riskScore = Math.round(avgProb * 100);
|
|
1380
|
-
// Generate recommendations from actual findings
|
|
1381
|
-
const recommendations = [];
|
|
1382
|
-
if (predictedDefects.length > 0) {
|
|
1383
|
-
recommendations.push(`${predictedDefects.length} files flagged for potential defects out of ${sourceFiles.length} analyzed`);
|
|
1384
|
-
const topFile = predictedDefects[0];
|
|
1385
|
-
recommendations.push(`Highest risk: ${topFile.file} (${Math.round(topFile.probability * 100)}%) — ${topFile.reason}`);
|
|
1386
|
-
}
|
|
1387
|
-
if (predictedDefects.some(d => d.reason.includes('Large file'))) {
|
|
1388
|
-
recommendations.push('Consider splitting large files to reduce complexity');
|
|
1389
|
-
}
|
|
1390
|
-
if (predictedDefects.some(d => d.reason.includes('technical debt'))) {
|
|
1391
|
-
recommendations.push('Address TODO/FIXME comments to reduce technical debt');
|
|
1392
|
-
}
|
|
1393
|
-
if (predictedDefects.length === 0) {
|
|
1394
|
-
recommendations.push('No files exceeded the defect probability threshold — code looks healthy');
|
|
1395
|
-
}
|
|
1396
|
-
return ok({
|
|
1397
|
-
predictedDefects: predictedDefects.slice(0, 20), // Top 20
|
|
1398
|
-
riskScore,
|
|
1399
|
-
recommendations,
|
|
1400
|
-
filesAnalyzed: sourceFiles.length,
|
|
1401
|
-
});
|
|
1402
|
-
}
|
|
1403
|
-
catch (error) {
|
|
1404
|
-
return err(toError(error));
|
|
1405
|
-
}
|
|
1406
|
-
});
|
|
1407
|
-
// Register requirements validation handler
|
|
1408
|
-
this.taskHandlers.set('validate-requirements', async (task) => {
|
|
1409
|
-
const payload = task.payload;
|
|
1410
|
-
try {
|
|
1411
|
-
const targetPath = payload.requirementsPath || process.cwd();
|
|
1412
|
-
// Look for requirements files (markdown, feature files, etc.)
|
|
1413
|
-
const reqFiles = await discoverSourceFiles(targetPath, {
|
|
1414
|
-
includeTests: false,
|
|
1415
|
-
languages: [],
|
|
1416
|
-
});
|
|
1417
|
-
// Scan for requirement-like files
|
|
1418
|
-
const reqPatterns = ['.md', '.feature', '.gherkin', '.txt', '.rst'];
|
|
1419
|
-
const requirementFiles = [];
|
|
1420
|
-
for (const f of reqFiles) {
|
|
1421
|
-
if (reqPatterns.some(ext => f.endsWith(ext))) {
|
|
1422
|
-
requirementFiles.push(f);
|
|
1423
|
-
}
|
|
1424
|
-
}
|
|
1425
|
-
return ok({
|
|
1426
|
-
requirementsAnalyzed: requirementFiles.length,
|
|
1427
|
-
testable: 0,
|
|
1428
|
-
ambiguous: 0,
|
|
1429
|
-
untestable: 0,
|
|
1430
|
-
coverage: 0,
|
|
1431
|
-
bddScenarios: [],
|
|
1432
|
-
warning: requirementFiles.length === 0
|
|
1433
|
-
? 'No requirement files (.md, .feature, .gherkin) found. Provide requirementsPath or add requirement docs.'
|
|
1434
|
-
: 'Requirements validation requires LLM analysis. File inventory returned — use task_orchestrate for deep analysis.',
|
|
1435
|
-
files: requirementFiles.map(f => f.startsWith(targetPath) ? f.slice(targetPath.length + 1) : f).slice(0, 20),
|
|
1436
|
-
});
|
|
1437
|
-
}
|
|
1438
|
-
catch (error) {
|
|
1439
|
-
return err(toError(error));
|
|
1440
|
-
}
|
|
1441
|
-
});
|
|
1442
|
-
// Register contract validation handler
|
|
1443
|
-
this.taskHandlers.set('validate-contracts', async (task) => {
|
|
1444
|
-
const payload = task.payload;
|
|
1445
|
-
try {
|
|
1446
|
-
if (!payload.contractPath) {
|
|
1447
|
-
return ok({
|
|
1448
|
-
contractPath: '',
|
|
1449
|
-
valid: false,
|
|
1450
|
-
breakingChanges: [],
|
|
1451
|
-
warnings: [],
|
|
1452
|
-
coverage: 0,
|
|
1453
|
-
error: 'contractPath is required. Provide a path to an OpenAPI spec, JSON Schema, or Protocol Buffer file.',
|
|
1454
|
-
});
|
|
1455
|
-
}
|
|
1456
|
-
// Check if the contract file exists
|
|
1457
|
-
try {
|
|
1458
|
-
const content = await fs.readFile(payload.contractPath, 'utf-8');
|
|
1459
|
-
const isJson = payload.contractPath.endsWith('.json');
|
|
1460
|
-
const isYaml = payload.contractPath.endsWith('.yaml') || payload.contractPath.endsWith('.yml');
|
|
1461
|
-
// Basic structural validation
|
|
1462
|
-
if (isJson) {
|
|
1463
|
-
JSON.parse(content); // throws if invalid
|
|
1464
|
-
}
|
|
1465
|
-
return ok({
|
|
1466
|
-
contractPath: payload.contractPath,
|
|
1467
|
-
valid: true,
|
|
1468
|
-
format: isJson ? 'json' : isYaml ? 'yaml' : 'unknown',
|
|
1469
|
-
breakingChanges: [],
|
|
1470
|
-
warnings: [],
|
|
1471
|
-
linesAnalyzed: content.split('\n').length,
|
|
1472
|
-
note: 'Structural validation passed. For semantic contract testing, use consumer-driven contract tests.',
|
|
1473
|
-
});
|
|
1474
|
-
}
|
|
1475
|
-
catch (readErr) {
|
|
1476
|
-
return ok({
|
|
1477
|
-
contractPath: payload.contractPath,
|
|
1478
|
-
valid: false,
|
|
1479
|
-
breakingChanges: [],
|
|
1480
|
-
warnings: [],
|
|
1481
|
-
error: `Could not read or parse contract file: ${toErrorMessage(readErr)}`,
|
|
1482
|
-
});
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
catch (error) {
|
|
1486
|
-
return err(toError(error));
|
|
1487
|
-
}
|
|
1488
|
-
});
|
|
1489
|
-
// Register accessibility test handler
|
|
1490
|
-
this.taskHandlers.set('test-accessibility', async (task) => {
|
|
1491
|
-
const payload = task.payload;
|
|
1492
|
-
// Accessibility testing requires a browser/DOM — return honest guidance
|
|
1493
|
-
return ok({
|
|
1494
|
-
url: payload.url || '',
|
|
1495
|
-
standard: payload.standard || 'wcag21-aa',
|
|
1496
|
-
passed: false,
|
|
1497
|
-
violations: [],
|
|
1498
|
-
warnings: [],
|
|
1499
|
-
score: 0,
|
|
1500
|
-
note: 'Accessibility testing requires a browser environment (Puppeteer/Playwright). ' +
|
|
1501
|
-
'Use tools like axe-core, pa11y, or Lighthouse CLI for WCAG compliance testing. ' +
|
|
1502
|
-
'Example: npx pa11y ' + (payload.url || '<url>'),
|
|
1503
|
-
});
|
|
1504
|
-
});
|
|
1505
|
-
// Register chaos test handler
|
|
1506
|
-
this.taskHandlers.set('run-chaos', async (task) => {
|
|
1507
|
-
const payload = task.payload;
|
|
1508
|
-
// Chaos testing requires infrastructure access — return honest guidance
|
|
1509
|
-
return ok({
|
|
1510
|
-
faultType: payload.faultType || 'unknown',
|
|
1511
|
-
target: payload.target || 'unknown',
|
|
1512
|
-
dryRun: payload.dryRun ?? true,
|
|
1513
|
-
duration: payload.duration || 0,
|
|
1514
|
-
systemBehavior: 'not-executed',
|
|
1515
|
-
resilience: null,
|
|
1516
|
-
note: 'Chaos engineering requires infrastructure-level fault injection. ' +
|
|
1517
|
-
'Use tools like Chaos Monkey, Litmus, or toxiproxy for real resilience testing. ' +
|
|
1518
|
-
'For Node.js apps, consider: nock (HTTP faults), testcontainers (dependency failures).',
|
|
1519
|
-
});
|
|
1520
|
-
});
|
|
1521
|
-
// Register learning optimization handler
|
|
1522
|
-
this.taskHandlers.set('optimize-learning', async (_task) => {
|
|
1523
|
-
// Check actual pattern store state
|
|
1524
|
-
try {
|
|
1525
|
-
const memUsage = await import('../kernel/unified-memory-hnsw.js');
|
|
1526
|
-
return ok({
|
|
1527
|
-
patternsLearned: 0,
|
|
1528
|
-
modelsUpdated: 0,
|
|
1529
|
-
memoryConsolidated: false,
|
|
1530
|
-
note: 'Learning optimization runs during the dream cycle (SessionEnd hook). ' +
|
|
1531
|
-
'Use "npx agentic-qe hooks session-end --save-state" to trigger pattern consolidation.',
|
|
1532
|
-
});
|
|
1533
|
-
}
|
|
1534
|
-
catch {
|
|
1535
|
-
return ok({
|
|
1536
|
-
patternsLearned: 0,
|
|
1537
|
-
modelsUpdated: 0,
|
|
1538
|
-
memoryConsolidated: false,
|
|
1539
|
-
note: 'Learning system not initialized. Run "aqe init --auto" first.',
|
|
1540
|
-
});
|
|
1541
|
-
}
|
|
1542
|
-
});
|
|
1543
|
-
}
|
|
1544
|
-
// ============================================================================
|
|
1545
|
-
// ADR-051: Agent Booster Execution (Tier 0)
|
|
1546
|
-
// ============================================================================
|
|
1547
233
|
/**
|
|
1548
234
|
* Execute task using Agent Booster for mechanical transforms
|
|
1549
235
|
* Falls back to normal execution if transform not applicable or low confidence
|
|
@@ -1669,7 +355,7 @@ export class DomainTaskExecutor {
|
|
|
1669
355
|
// Execute with timeout
|
|
1670
356
|
const result = await Promise.race([
|
|
1671
357
|
handler(task),
|
|
1672
|
-
this.timeout(task.timeout || this.
|
|
358
|
+
this.timeout(task.timeout || this._config.timeout),
|
|
1673
359
|
]);
|
|
1674
360
|
if (!result.success) {
|
|
1675
361
|
const errorMsg = 'error' in result ? result.error.message : 'Unknown error';
|
|
@@ -1689,11 +375,11 @@ export class DomainTaskExecutor {
|
|
|
1689
375
|
this.recordOutcome(task, routingTier, true, Date.now() - startTime).catch(() => { });
|
|
1690
376
|
// Save results to files if enabled
|
|
1691
377
|
let savedFiles;
|
|
1692
|
-
if (this.
|
|
378
|
+
if (this._config.saveResults) {
|
|
1693
379
|
try {
|
|
1694
380
|
const saveOptions = {
|
|
1695
|
-
language: payload?.language || this.
|
|
1696
|
-
framework: payload?.framework || this.
|
|
381
|
+
language: payload?.language || this._config.defaultLanguage,
|
|
382
|
+
framework: payload?.framework || this._config.defaultFramework,
|
|
1697
383
|
includeSecondary: true,
|
|
1698
384
|
};
|
|
1699
385
|
const saved = await this.resultSaver.save(task.id, task.type, result.value, saveOptions);
|