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.
Files changed (268) hide show
  1. package/.claude/skills/pr-review/SKILL.md +1 -1
  2. package/.claude/skills/skills-manifest.json +6 -6
  3. package/README.md +18 -10
  4. package/package.json +2 -2
  5. package/scripts/generate-opencode-agents.ts +523 -0
  6. package/scripts/generate-opencode-skills.ts +318 -0
  7. package/v3/CHANGELOG.md +26 -0
  8. package/v3/README.md +7 -7
  9. package/v3/assets/skills/pr-review/SKILL.md +1 -1
  10. package/v3/dist/adapters/a2a/notifications/retry-queue.d.ts.map +1 -1
  11. package/v3/dist/adapters/a2a/notifications/retry-queue.js +2 -1
  12. package/v3/dist/adapters/a2a/notifications/retry-queue.js.map +1 -1
  13. package/v3/dist/adapters/a2a/notifications/subscription-store.d.ts.map +1 -1
  14. package/v3/dist/adapters/a2a/notifications/subscription-store.js +2 -1
  15. package/v3/dist/adapters/a2a/notifications/subscription-store.js.map +1 -1
  16. package/v3/dist/adapters/a2a/tasks/task-manager.d.ts.map +1 -1
  17. package/v3/dist/adapters/a2a/tasks/task-manager.js +4 -3
  18. package/v3/dist/adapters/a2a/tasks/task-manager.js.map +1 -1
  19. package/v3/dist/agents/claim-verifier/index.d.ts.map +1 -1
  20. package/v3/dist/agents/claim-verifier/index.js +2 -1
  21. package/v3/dist/agents/claim-verifier/index.js.map +1 -1
  22. package/v3/dist/cli/bundle.js +2830 -1371
  23. package/v3/dist/cli/commands/hooks.d.ts.map +1 -1
  24. package/v3/dist/cli/commands/hooks.js +17 -2
  25. package/v3/dist/cli/commands/hooks.js.map +1 -1
  26. package/v3/dist/cli/commands/init.d.ts.map +1 -1
  27. package/v3/dist/cli/commands/init.js +2 -0
  28. package/v3/dist/cli/commands/init.js.map +1 -1
  29. package/v3/dist/cli/commands/learning-helpers.d.ts.map +1 -1
  30. package/v3/dist/cli/commands/learning-helpers.js +11 -0
  31. package/v3/dist/cli/commands/learning-helpers.js.map +1 -1
  32. package/v3/dist/cli/handlers/init-handler.d.ts +1 -0
  33. package/v3/dist/cli/handlers/init-handler.d.ts.map +1 -1
  34. package/v3/dist/cli/handlers/init-handler.js +3 -0
  35. package/v3/dist/cli/handlers/init-handler.js.map +1 -1
  36. package/v3/dist/cli/scheduler/persistent-scheduler.d.ts.map +1 -1
  37. package/v3/dist/cli/scheduler/persistent-scheduler.js +2 -1
  38. package/v3/dist/cli/scheduler/persistent-scheduler.js.map +1 -1
  39. package/v3/dist/coordination/consensus/consensus-engine.d.ts.map +1 -1
  40. package/v3/dist/coordination/consensus/consensus-engine.js +2 -1
  41. package/v3/dist/coordination/consensus/consensus-engine.js.map +1 -1
  42. package/v3/dist/coordination/cross-domain-router.d.ts.map +1 -1
  43. package/v3/dist/coordination/cross-domain-router.js +7 -8
  44. package/v3/dist/coordination/cross-domain-router.js.map +1 -1
  45. package/v3/dist/coordination/handlers/code-intelligence-handlers.d.ts +9 -0
  46. package/v3/dist/coordination/handlers/code-intelligence-handlers.d.ts.map +1 -0
  47. package/v3/dist/coordination/handlers/code-intelligence-handlers.js +203 -0
  48. package/v3/dist/coordination/handlers/code-intelligence-handlers.js.map +1 -0
  49. package/v3/dist/coordination/handlers/coverage-handlers.d.ts +9 -0
  50. package/v3/dist/coordination/handlers/coverage-handlers.d.ts.map +1 -0
  51. package/v3/dist/coordination/handlers/coverage-handlers.js +120 -0
  52. package/v3/dist/coordination/handlers/coverage-handlers.js.map +1 -0
  53. package/v3/dist/coordination/handlers/handler-types.d.ts +35 -0
  54. package/v3/dist/coordination/handlers/handler-types.d.ts.map +1 -0
  55. package/v3/dist/coordination/handlers/handler-types.js +8 -0
  56. package/v3/dist/coordination/handlers/handler-types.js.map +1 -0
  57. package/v3/dist/coordination/handlers/handler-utils.d.ts +106 -0
  58. package/v3/dist/coordination/handlers/handler-utils.d.ts.map +1 -0
  59. package/v3/dist/coordination/handlers/handler-utils.js +480 -0
  60. package/v3/dist/coordination/handlers/handler-utils.js.map +1 -0
  61. package/v3/dist/coordination/handlers/index.d.ts +15 -0
  62. package/v3/dist/coordination/handlers/index.d.ts.map +1 -0
  63. package/v3/dist/coordination/handlers/index.js +14 -0
  64. package/v3/dist/coordination/handlers/index.js.map +1 -0
  65. package/v3/dist/coordination/handlers/misc-handlers.d.ts +9 -0
  66. package/v3/dist/coordination/handlers/misc-handlers.d.ts.map +1 -0
  67. package/v3/dist/coordination/handlers/misc-handlers.js +64 -0
  68. package/v3/dist/coordination/handlers/misc-handlers.js.map +1 -0
  69. package/v3/dist/coordination/handlers/quality-handlers.d.ts +9 -0
  70. package/v3/dist/coordination/handlers/quality-handlers.d.ts.map +1 -0
  71. package/v3/dist/coordination/handlers/quality-handlers.js +83 -0
  72. package/v3/dist/coordination/handlers/quality-handlers.js.map +1 -0
  73. package/v3/dist/coordination/handlers/requirements-handlers.d.ts +9 -0
  74. package/v3/dist/coordination/handlers/requirements-handlers.d.ts.map +1 -0
  75. package/v3/dist/coordination/handlers/requirements-handlers.js +95 -0
  76. package/v3/dist/coordination/handlers/requirements-handlers.js.map +1 -0
  77. package/v3/dist/coordination/handlers/security-handlers.d.ts +9 -0
  78. package/v3/dist/coordination/handlers/security-handlers.d.ts.map +1 -0
  79. package/v3/dist/coordination/handlers/security-handlers.js +255 -0
  80. package/v3/dist/coordination/handlers/security-handlers.js.map +1 -0
  81. package/v3/dist/coordination/handlers/test-execution-handlers.d.ts +9 -0
  82. package/v3/dist/coordination/handlers/test-execution-handlers.d.ts.map +1 -0
  83. package/v3/dist/coordination/handlers/test-execution-handlers.js +153 -0
  84. package/v3/dist/coordination/handlers/test-execution-handlers.js.map +1 -0
  85. package/v3/dist/coordination/queen-coordinator.d.ts.map +1 -1
  86. package/v3/dist/coordination/queen-coordinator.js +28 -25
  87. package/v3/dist/coordination/queen-coordinator.js.map +1 -1
  88. package/v3/dist/coordination/task-executor.d.ts +25 -9
  89. package/v3/dist/coordination/task-executor.d.ts.map +1 -1
  90. package/v3/dist/coordination/task-executor.js +38 -1352
  91. package/v3/dist/coordination/task-executor.js.map +1 -1
  92. package/v3/dist/domains/code-intelligence/services/metric-collector/loc-counter.js +15 -9
  93. package/v3/dist/domains/code-intelligence/services/metric-collector/loc-counter.js.map +1 -1
  94. package/v3/dist/domains/defect-intelligence/services/causal-root-cause-analyzer.d.ts.map +1 -1
  95. package/v3/dist/domains/defect-intelligence/services/causal-root-cause-analyzer.js +2 -1
  96. package/v3/dist/domains/defect-intelligence/services/causal-root-cause-analyzer.js.map +1 -1
  97. package/v3/dist/domains/requirements-validation/services/product-factors-assessment/types/index.d.ts.map +1 -1
  98. package/v3/dist/domains/requirements-validation/services/product-factors-assessment/types/index.js +2 -1
  99. package/v3/dist/domains/requirements-validation/services/product-factors-assessment/types/index.js.map +1 -1
  100. package/v3/dist/domains/test-execution/types/e2e-step.types.d.ts.map +1 -1
  101. package/v3/dist/domains/test-execution/types/e2e-step.types.js +2 -1
  102. package/v3/dist/domains/test-execution/types/e2e-step.types.js.map +1 -1
  103. package/v3/dist/feedback/coverage-learner.d.ts.map +1 -1
  104. package/v3/dist/feedback/coverage-learner.js +2 -1
  105. package/v3/dist/feedback/coverage-learner.js.map +1 -1
  106. package/v3/dist/init/opencode-installer.d.ts +67 -0
  107. package/v3/dist/init/opencode-installer.d.ts.map +1 -0
  108. package/v3/dist/init/opencode-installer.js +267 -0
  109. package/v3/dist/init/opencode-installer.js.map +1 -0
  110. package/v3/dist/init/orchestrator.d.ts.map +1 -1
  111. package/v3/dist/init/orchestrator.js +1 -0
  112. package/v3/dist/init/orchestrator.js.map +1 -1
  113. package/v3/dist/init/phases/09-assets.d.ts +2 -0
  114. package/v3/dist/init/phases/09-assets.d.ts.map +1 -1
  115. package/v3/dist/init/phases/09-assets.js +26 -0
  116. package/v3/dist/init/phases/09-assets.js.map +1 -1
  117. package/v3/dist/init/phases/phase-interface.d.ts +2 -0
  118. package/v3/dist/init/phases/phase-interface.d.ts.map +1 -1
  119. package/v3/dist/init/phases/phase-interface.js.map +1 -1
  120. package/v3/dist/integrations/agentic-flow/onnx-embeddings/adapter.d.ts.map +1 -1
  121. package/v3/dist/integrations/agentic-flow/onnx-embeddings/adapter.js +2 -1
  122. package/v3/dist/integrations/agentic-flow/onnx-embeddings/adapter.js.map +1 -1
  123. package/v3/dist/integrations/n8n/agent-factory.d.ts.map +1 -1
  124. package/v3/dist/integrations/n8n/agent-factory.js +2 -1
  125. package/v3/dist/integrations/n8n/agent-factory.js.map +1 -1
  126. package/v3/dist/integrations/rl-suite/sona.d.ts.map +1 -1
  127. package/v3/dist/integrations/rl-suite/sona.js +2 -1
  128. package/v3/dist/integrations/rl-suite/sona.js.map +1 -1
  129. package/v3/dist/integrations/ruvector/brain-exporter.d.ts.map +1 -1
  130. package/v3/dist/integrations/ruvector/brain-exporter.js +4 -3
  131. package/v3/dist/integrations/ruvector/brain-exporter.js.map +1 -1
  132. package/v3/dist/integrations/ruvector/hypergraph-schema.d.ts.map +1 -1
  133. package/v3/dist/integrations/ruvector/hypergraph-schema.js +7 -0
  134. package/v3/dist/integrations/ruvector/hypergraph-schema.js.map +1 -1
  135. package/v3/dist/integrations/ruvector/index.d.ts +1 -0
  136. package/v3/dist/integrations/ruvector/index.d.ts.map +1 -1
  137. package/v3/dist/integrations/ruvector/index.js +1 -0
  138. package/v3/dist/integrations/ruvector/index.js.map +1 -1
  139. package/v3/dist/integrations/ruvector/shared-rvf-dual-writer.d.ts +41 -0
  140. package/v3/dist/integrations/ruvector/shared-rvf-dual-writer.d.ts.map +1 -0
  141. package/v3/dist/integrations/ruvector/shared-rvf-dual-writer.js +122 -0
  142. package/v3/dist/integrations/ruvector/shared-rvf-dual-writer.js.map +1 -0
  143. package/v3/dist/integrations/ruvector/sona-wrapper.d.ts.map +1 -1
  144. package/v3/dist/integrations/ruvector/sona-wrapper.js +2 -1
  145. package/v3/dist/integrations/ruvector/sona-wrapper.js.map +1 -1
  146. package/v3/dist/integrations/vibium/client.d.ts.map +1 -1
  147. package/v3/dist/integrations/vibium/client.js +2 -1
  148. package/v3/dist/integrations/vibium/client.js.map +1 -1
  149. package/v3/dist/integrations/vibium/fallback.d.ts.map +1 -1
  150. package/v3/dist/integrations/vibium/fallback.js +2 -1
  151. package/v3/dist/integrations/vibium/fallback.js.map +1 -1
  152. package/v3/dist/kernel/kernel.d.ts.map +1 -1
  153. package/v3/dist/kernel/kernel.js +2 -1
  154. package/v3/dist/kernel/kernel.js.map +1 -1
  155. package/v3/dist/kernel/unified-memory.d.ts.map +1 -1
  156. package/v3/dist/kernel/unified-memory.js +19 -9
  157. package/v3/dist/kernel/unified-memory.js.map +1 -1
  158. package/v3/dist/learning/aqe-learning-engine.d.ts.map +1 -1
  159. package/v3/dist/learning/aqe-learning-engine.js +14 -2
  160. package/v3/dist/learning/aqe-learning-engine.js.map +1 -1
  161. package/v3/dist/learning/dream/concept-graph.d.ts +10 -5
  162. package/v3/dist/learning/dream/concept-graph.d.ts.map +1 -1
  163. package/v3/dist/learning/dream/concept-graph.js +77 -25
  164. package/v3/dist/learning/dream/concept-graph.js.map +1 -1
  165. package/v3/dist/learning/dream/dream-engine.d.ts.map +1 -1
  166. package/v3/dist/learning/dream/dream-engine.js +37 -29
  167. package/v3/dist/learning/dream/dream-engine.js.map +1 -1
  168. package/v3/dist/learning/dream/insight-generator.d.ts.map +1 -1
  169. package/v3/dist/learning/dream/insight-generator.js +2 -1
  170. package/v3/dist/learning/dream/insight-generator.js.map +1 -1
  171. package/v3/dist/learning/dream/spreading-activation.d.ts +1 -1
  172. package/v3/dist/learning/dream/spreading-activation.d.ts.map +1 -1
  173. package/v3/dist/learning/dream/spreading-activation.js +13 -5
  174. package/v3/dist/learning/dream/spreading-activation.js.map +1 -1
  175. package/v3/dist/learning/memory-auditor.d.ts.map +1 -1
  176. package/v3/dist/learning/memory-auditor.js +7 -4
  177. package/v3/dist/learning/memory-auditor.js.map +1 -1
  178. package/v3/dist/learning/real-embeddings.d.ts.map +1 -1
  179. package/v3/dist/learning/real-embeddings.js +26 -1
  180. package/v3/dist/learning/real-embeddings.js.map +1 -1
  181. package/v3/dist/learning/real-qe-reasoning-bank.d.ts.map +1 -1
  182. package/v3/dist/learning/real-qe-reasoning-bank.js +13 -2
  183. package/v3/dist/learning/real-qe-reasoning-bank.js.map +1 -1
  184. package/v3/dist/learning/sqlite-persistence.d.ts +5 -0
  185. package/v3/dist/learning/sqlite-persistence.d.ts.map +1 -1
  186. package/v3/dist/learning/sqlite-persistence.js +47 -7
  187. package/v3/dist/learning/sqlite-persistence.js.map +1 -1
  188. package/v3/dist/mcp/bundle.js +3185 -2017
  189. package/v3/dist/mcp/connection-pool.d.ts.map +1 -1
  190. package/v3/dist/mcp/connection-pool.js +3 -2
  191. package/v3/dist/mcp/connection-pool.js.map +1 -1
  192. package/v3/dist/mcp/entry.js +12 -0
  193. package/v3/dist/mcp/entry.js.map +1 -1
  194. package/v3/dist/mcp/handlers/core-handlers.d.ts +28 -0
  195. package/v3/dist/mcp/handlers/core-handlers.d.ts.map +1 -1
  196. package/v3/dist/mcp/handlers/core-handlers.js +117 -0
  197. package/v3/dist/mcp/handlers/core-handlers.js.map +1 -1
  198. package/v3/dist/mcp/handlers/index.d.ts +1 -1
  199. package/v3/dist/mcp/handlers/index.d.ts.map +1 -1
  200. package/v3/dist/mcp/handlers/index.js +1 -1
  201. package/v3/dist/mcp/handlers/index.js.map +1 -1
  202. package/v3/dist/mcp/http-server.d.ts.map +1 -1
  203. package/v3/dist/mcp/http-server.js +12 -9
  204. package/v3/dist/mcp/http-server.js.map +1 -1
  205. package/v3/dist/mcp/middleware/output-compaction.d.ts +45 -0
  206. package/v3/dist/mcp/middleware/output-compaction.d.ts.map +1 -0
  207. package/v3/dist/mcp/middleware/output-compaction.js +279 -0
  208. package/v3/dist/mcp/middleware/output-compaction.js.map +1 -0
  209. package/v3/dist/mcp/protocol-server.d.ts.map +1 -1
  210. package/v3/dist/mcp/protocol-server.js +53 -41
  211. package/v3/dist/mcp/protocol-server.js.map +1 -1
  212. package/v3/dist/mcp/security/sampling-server.js +2 -2
  213. package/v3/dist/mcp/security/sampling-server.js.map +1 -1
  214. package/v3/dist/mcp/services/reasoning-bank-service.d.ts.map +1 -1
  215. package/v3/dist/mcp/services/reasoning-bank-service.js +4 -5
  216. package/v3/dist/mcp/services/reasoning-bank-service.js.map +1 -1
  217. package/v3/dist/mcp/tools/base.d.ts +5 -0
  218. package/v3/dist/mcp/tools/base.d.ts.map +1 -1
  219. package/v3/dist/mcp/tools/base.js +25 -1
  220. package/v3/dist/mcp/tools/base.js.map +1 -1
  221. package/v3/dist/mcp/tools/learning-optimization/dream.d.ts.map +1 -1
  222. package/v3/dist/mcp/tools/learning-optimization/dream.js +22 -0
  223. package/v3/dist/mcp/tools/learning-optimization/dream.js.map +1 -1
  224. package/v3/dist/mcp/tools/security-compliance/scan.d.ts.map +1 -1
  225. package/v3/dist/mcp/tools/security-compliance/scan.js +2 -1
  226. package/v3/dist/mcp/tools/security-compliance/scan.js.map +1 -1
  227. package/v3/dist/mcp/transport/index.d.ts +8 -6
  228. package/v3/dist/mcp/transport/index.d.ts.map +1 -1
  229. package/v3/dist/mcp/transport/index.js +10 -9
  230. package/v3/dist/mcp/transport/index.js.map +1 -1
  231. package/v3/dist/migrations/20260120_add_hypergraph_tables.d.ts.map +1 -1
  232. package/v3/dist/migrations/20260120_add_hypergraph_tables.js +7 -0
  233. package/v3/dist/migrations/20260120_add_hypergraph_tables.js.map +1 -1
  234. package/v3/dist/optimization/token-optimizer-service.d.ts.map +1 -1
  235. package/v3/dist/optimization/token-optimizer-service.js +2 -1
  236. package/v3/dist/optimization/token-optimizer-service.js.map +1 -1
  237. package/v3/dist/planning/goap-planner.d.ts.map +1 -1
  238. package/v3/dist/planning/goap-planner.js +2 -1
  239. package/v3/dist/planning/goap-planner.js.map +1 -1
  240. package/v3/dist/planning/plan-executor.d.ts +5 -1
  241. package/v3/dist/planning/plan-executor.d.ts.map +1 -1
  242. package/v3/dist/planning/plan-executor.js +16 -2
  243. package/v3/dist/planning/plan-executor.js.map +1 -1
  244. package/v3/dist/routing/routing-feedback.d.ts.map +1 -1
  245. package/v3/dist/routing/routing-feedback.js +2 -1
  246. package/v3/dist/routing/routing-feedback.js.map +1 -1
  247. package/v3/dist/shared/llm/cost-tracker.d.ts.map +1 -1
  248. package/v3/dist/shared/llm/cost-tracker.js +2 -1
  249. package/v3/dist/shared/llm/cost-tracker.js.map +1 -1
  250. package/v3/dist/shared/llm/metrics/router-metrics.d.ts.map +1 -1
  251. package/v3/dist/shared/llm/metrics/router-metrics.js +2 -1
  252. package/v3/dist/shared/llm/metrics/router-metrics.js.map +1 -1
  253. package/v3/dist/strange-loop/belief-reconciler.d.ts.map +1 -1
  254. package/v3/dist/strange-loop/belief-reconciler.js +2 -1
  255. package/v3/dist/strange-loop/belief-reconciler.js.map +1 -1
  256. package/v3/dist/sync/readers/json-reader.d.ts.map +1 -1
  257. package/v3/dist/sync/readers/json-reader.js +2 -1
  258. package/v3/dist/sync/readers/json-reader.js.map +1 -1
  259. package/v3/dist/types/cross-phase-signals.d.ts.map +1 -1
  260. package/v3/dist/types/cross-phase-signals.js +2 -1
  261. package/v3/dist/types/cross-phase-signals.js.map +1 -1
  262. package/v3/dist/validation/swarm-skill-validator.d.ts.map +1 -1
  263. package/v3/dist/validation/swarm-skill-validator.js +2 -1
  264. package/v3/dist/validation/swarm-skill-validator.js.map +1 -1
  265. package/v3/dist/workers/base-worker.d.ts.map +1 -1
  266. package/v3/dist/workers/base-worker.js +2 -1
  267. package/v3/dist/workers/base-worker.js.map +1 -1
  268. 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 * as fs from 'fs/promises';
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
- config;
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.config = {
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.config.resultsDir);
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.config.timeout),
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.config.saveResults) {
378
+ if (this._config.saveResults) {
1693
379
  try {
1694
380
  const saveOptions = {
1695
- language: payload?.language || this.config.defaultLanguage,
1696
- framework: payload?.framework || this.config.defaultFramework,
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);