agentic-qe 3.7.19 → 3.7.21

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 (77) hide show
  1. package/.claude/agents/v3/qe-deployment-advisor.md +14 -0
  2. package/.claude/agents/v3/qe-gap-detector.md +8 -0
  3. package/.claude/agents/v3/qe-impact-analyzer.md +11 -0
  4. package/.claude/agents/v3/qe-queen-coordinator.md +45 -0
  5. package/.claude/agents/v3/qe-root-cause-analyzer.md +11 -0
  6. package/.claude/agents/v3/qe-security-scanner.md +25 -16
  7. package/.claude/helpers/brain-checkpoint.cjs +3 -3
  8. package/.claude/helpers/statusline-v3.cjs +4 -3
  9. package/.claude/skills/skills-manifest.json +1 -1
  10. package/CHANGELOG.md +27 -0
  11. package/assets/agents/v3/qe-deployment-advisor.md +14 -0
  12. package/assets/agents/v3/qe-gap-detector.md +8 -0
  13. package/assets/agents/v3/qe-impact-analyzer.md +11 -0
  14. package/assets/agents/v3/qe-queen-coordinator.md +45 -0
  15. package/assets/agents/v3/qe-root-cause-analyzer.md +11 -0
  16. package/assets/agents/v3/qe-security-scanner.md +25 -16
  17. package/assets/helpers/statusline-v3.cjs +4 -3
  18. package/dist/adapters/claude-flow/model-router-bridge.d.ts +0 -6
  19. package/dist/adapters/claude-flow/model-router-bridge.js +4 -17
  20. package/dist/adapters/claude-flow/pretrain-bridge.d.ts +0 -6
  21. package/dist/adapters/claude-flow/pretrain-bridge.js +6 -19
  22. package/dist/adapters/claude-flow/trajectory-bridge.d.ts +0 -6
  23. package/dist/adapters/claude-flow/trajectory-bridge.js +21 -23
  24. package/dist/cli/bundle.js +1821 -986
  25. package/dist/coordination/protocols/security-audit.d.ts +3 -6
  26. package/dist/coordination/protocols/security-audit.js +8 -88
  27. package/dist/coordination/queen-coordinator.d.ts +13 -0
  28. package/dist/coordination/queen-coordinator.js +76 -0
  29. package/dist/coordination/queen-task-management.d.ts +2 -0
  30. package/dist/coordination/queen-task-management.js +10 -0
  31. package/dist/coordination/queen-types.d.ts +3 -0
  32. package/dist/coordination/task-executor.js +7 -5
  33. package/dist/domains/security-compliance/services/scanners/sast-scanner.d.ts +25 -1
  34. package/dist/domains/security-compliance/services/scanners/sast-scanner.js +140 -11
  35. package/dist/domains/security-compliance/services/scanners/scanner-types.d.ts +2 -0
  36. package/dist/domains/security-compliance/services/scanners/scanner-types.js +1 -0
  37. package/dist/domains/test-execution/services/mincut-test-optimizer.js +2 -0
  38. package/dist/governance/continue-gate-integration.js +1 -1
  39. package/dist/governance/feature-flags.js +2 -2
  40. package/dist/init/agents-installer.d.ts +2 -0
  41. package/dist/init/agents-installer.js +13 -0
  42. package/dist/init/enhancements/claude-flow-adapter.js +51 -24
  43. package/dist/init/init-wizard.js +1 -1
  44. package/dist/init/phases/07-hooks.js +6 -6
  45. package/dist/init/settings-merge.js +2 -0
  46. package/dist/integrations/ruvector/brain-rvf-exporter.js +14 -2
  47. package/dist/learning/experience-capture-middleware.js +3 -1
  48. package/dist/learning/qe-reasoning-bank.js +3 -3
  49. package/dist/learning/sqlite-persistence.js +16 -0
  50. package/dist/learning/token-tracker.js +4 -2
  51. package/dist/mcp/bundle.js +1183 -504
  52. package/dist/routing/agent-dependency-graph.d.ts +77 -0
  53. package/dist/routing/agent-dependency-graph.js +359 -0
  54. package/dist/routing/co-execution-repository.d.ts +68 -0
  55. package/dist/routing/co-execution-repository.js +184 -0
  56. package/dist/routing/index.d.ts +6 -0
  57. package/dist/routing/index.js +6 -0
  58. package/dist/routing/qe-task-router.d.ts +7 -0
  59. package/dist/routing/qe-task-router.js +63 -1
  60. package/dist/routing/signal-merger.d.ts +81 -0
  61. package/dist/routing/signal-merger.js +136 -0
  62. package/dist/routing/types.d.ts +1 -0
  63. package/dist/shared/llm/providers/azure-openai.js +3 -2
  64. package/dist/shared/llm/providers/bedrock.js +3 -2
  65. package/dist/shared/llm/providers/claude.js +3 -2
  66. package/dist/shared/llm/providers/gemini.js +3 -2
  67. package/dist/shared/llm/providers/openai.js +3 -2
  68. package/dist/shared/llm/providers/openrouter.js +3 -2
  69. package/dist/shared/llm/retry.d.ts +10 -0
  70. package/dist/shared/llm/retry.js +16 -0
  71. package/dist/shared/llm/router/agent-router-config.d.ts +2 -1
  72. package/dist/shared/llm/router/agent-router-config.js +38 -88
  73. package/dist/validation/index.d.ts +2 -0
  74. package/dist/validation/index.js +4 -0
  75. package/dist/validation/steps/agent-mcp-validator.d.ts +88 -0
  76. package/dist/validation/steps/agent-mcp-validator.js +254 -0
  77. package/package.json +1 -1
@@ -169,14 +169,11 @@ export declare class SecurityAuditProtocol {
169
169
  */
170
170
  execute(trigger: SecurityAuditTrigger): Promise<Result<SecurityAuditResult>>;
171
171
  /**
172
- * Scan for vulnerabilities using SAST
173
- * Delegates to real SecurityScannerService with semgrep integration when available
172
+ * Scan for vulnerabilities using SAST.
173
+ * SASTScanner now integrates semgrep internally (runs alongside pattern scanning
174
+ * when semgrep is installed), so no separate fallback is needed here.
174
175
  */
175
176
  scanVulnerabilities(options: SecurityAuditOptions): Promise<Result<SASTResult>>;
176
- /**
177
- * Map semgrep OWASP category to VulnerabilityCategory
178
- */
179
- private mapSemgrepCategory;
180
177
  /**
181
178
  * Scan dependencies for vulnerabilities
182
179
  * Delegates to real SecurityScannerService which uses OSV API for real vulnerability data
@@ -10,6 +10,7 @@ import { v4 as uuidv4 } from 'uuid';
10
10
  import { ok, err, } from '../../shared/types/index.js';
11
11
  import { FilePath, RiskScore } from '../../shared/value-objects/index.js';
12
12
  import { createEvent, } from '../../shared/events/domain-events.js';
13
+ // Semgrep is now integrated directly into SASTScanner (no coordination-layer fallback needed)
13
14
  import { toError } from '../../shared/error-utils.js';
14
15
  // CQ-005: Use DomainServiceRegistry instead of dynamic imports from domains/
15
16
  import { DomainServiceRegistry, ServiceKeys } from '../../shared/domain-service-registry.js';
@@ -22,18 +23,6 @@ function resolveSecurityScannerService(memory) {
22
23
  const factory = DomainServiceRegistry.resolve(ServiceKeys.SecurityScannerService);
23
24
  return factory(memory);
24
25
  }
25
- function resolveIsSemgrepAvailable() {
26
- const fn = DomainServiceRegistry.resolve(ServiceKeys.isSemgrepAvailable);
27
- return fn();
28
- }
29
- function resolveRunSemgrepWithRules(targetPath, ruleSetIds) {
30
- const fn = DomainServiceRegistry.resolve(ServiceKeys.runSemgrepWithRules);
31
- return fn(targetPath, ruleSetIds);
32
- }
33
- function resolveConvertSemgrepFindings(findings) {
34
- const fn = DomainServiceRegistry.resolve(ServiceKeys.convertSemgrepFindings);
35
- return fn(findings);
36
- }
37
26
  // ============================================================================
38
27
  // Protocol Events
39
28
  // ============================================================================
@@ -208,8 +197,9 @@ export class SecurityAuditProtocol {
208
197
  // Scanning Methods
209
198
  // ==========================================================================
210
199
  /**
211
- * Scan for vulnerabilities using SAST
212
- * Delegates to real SecurityScannerService with semgrep integration when available
200
+ * Scan for vulnerabilities using SAST.
201
+ * SASTScanner now integrates semgrep internally (runs alongside pattern scanning
202
+ * when semgrep is installed), so no separate fallback is needed here.
213
203
  */
214
204
  async scanVulnerabilities(options) {
215
205
  try {
@@ -219,96 +209,26 @@ export class SecurityAuditProtocol {
219
209
  return err(agentId.error);
220
210
  }
221
211
  const files = this.config.scanPaths.map(path => FilePath.create(path));
222
- // Try real SecurityScannerService first
223
212
  try {
224
213
  const scanner = this.getSecurityScanner();
225
214
  const ruleSetIds = options.ruleSetIds || ['owasp-top-10', 'cwe-sans-25'];
215
+ // SASTScanner.scanWithRules now runs pattern scanning + semgrep in parallel
226
216
  const scanResult = await scanner.scanWithRules(files, ruleSetIds);
227
217
  if (scanResult.success) {
228
218
  return ok(scanResult.value);
229
219
  }
230
- // If scanner fails, continue to fallback
220
+ return err(scanResult.error);
231
221
  }
232
222
  catch (scannerError) {
233
- // Scanner unavailable - log and continue to fallback
234
223
  await this.memory.set('security-audit:scanner-error', { error: String(scannerError), timestamp: new Date().toISOString() }, { namespace: 'security-compliance', ttl: 3600 });
224
+ return err(new Error(`SAST scanning failed: ${String(scannerError)}. ` +
225
+ 'Ensure SecurityScannerService is properly configured.'));
235
226
  }
236
- // Try semgrep if available as secondary option
237
- const semgrepAvailable = await resolveIsSemgrepAvailable();
238
- if (semgrepAvailable) {
239
- try {
240
- const semgrepResult = await resolveRunSemgrepWithRules(this.config.scanPaths[0] || '.', options.ruleSetIds || ['owasp-top-10']);
241
- if (semgrepResult.success && semgrepResult.findings.length > 0) {
242
- const convertedFindings = resolveConvertSemgrepFindings(semgrepResult.findings);
243
- const vulnerabilities = convertedFindings.map(f => ({
244
- id: uuidv4(),
245
- cveId: undefined,
246
- title: f.title,
247
- description: f.description,
248
- severity: f.severity,
249
- category: this.mapSemgrepCategory(f.owaspCategory || 'injection'),
250
- location: {
251
- file: f.file,
252
- line: f.line,
253
- column: f.column,
254
- snippet: f.snippet,
255
- },
256
- remediation: {
257
- description: f.remediation,
258
- estimatedEffort: 'moderate',
259
- automatable: false,
260
- },
261
- references: f.references,
262
- }));
263
- const summary = this.calculateSummary(vulnerabilities);
264
- return ok({
265
- scanId: uuidv4(),
266
- vulnerabilities,
267
- summary,
268
- coverage: {
269
- filesScanned: files.length,
270
- linesScanned: vulnerabilities.length * 50,
271
- rulesApplied: 45,
272
- },
273
- });
274
- }
275
- }
276
- catch (semgrepError) {
277
- // Semgrep failed - log error
278
- await this.memory.set('security-audit:semgrep-error', { error: String(semgrepError), timestamp: new Date().toISOString() }, { namespace: 'security-compliance', ttl: 3600 });
279
- }
280
- }
281
- // NO FALLBACK - Security scans must either succeed or fail explicitly
282
- // An empty vulnerability list would falsely indicate "scan succeeded, nothing found"
283
- // when in reality we couldn't scan at all
284
- return err(new Error('SAST scanning unavailable: neither SecurityScannerService nor semgrep could execute. ' +
285
- 'Install semgrep (pip install semgrep) or ensure SecurityScannerService is properly configured.'));
286
227
  }
287
228
  catch (error) {
288
229
  return err(toError(error));
289
230
  }
290
231
  }
291
- /**
292
- * Map semgrep OWASP category to VulnerabilityCategory
293
- */
294
- mapSemgrepCategory(owaspCategory) {
295
- const categoryMap = {
296
- 'A01': 'access-control',
297
- 'A02': 'sensitive-data',
298
- 'A03': 'injection',
299
- 'A04': 'insecure-deserialization',
300
- 'A05': 'security-misconfiguration',
301
- 'A06': 'vulnerable-components',
302
- 'A07': 'broken-auth',
303
- 'A08': 'insecure-deserialization',
304
- 'A09': 'insufficient-logging',
305
- 'A10': 'xxe',
306
- 'injection': 'injection',
307
- 'xss': 'xss',
308
- 'broken-auth': 'broken-auth',
309
- };
310
- return categoryMap[owaspCategory] || 'security-misconfiguration';
311
- }
312
232
  /**
313
233
  * Scan dependencies for vulnerabilities
314
234
  * Delegates to real SecurityScannerService which uses OSV API for real vulnerability data
@@ -21,6 +21,7 @@ import { EventBus, AgentCoordinator, AgentInfo, DomainPlugin, DomainHealth, Memo
21
21
  import { CrossDomainRouter, ProtocolExecutor, WorkflowExecutor } from './interfaces';
22
22
  import { QueenMinCutBridge } from './mincut/queen-integration';
23
23
  import { QueenRouterAdapter } from '../routing/queen-integration.js';
24
+ import { type DependencyGraphResult } from '../routing/agent-dependency-graph.js';
24
25
  import { AgentTeamsAdapter } from './agent-teams/index.js';
25
26
  import { DomainTeamManager } from './agent-teams/domain-team-manager.js';
26
27
  import { DomainBreakerRegistry } from './circuit-breaker/index.js';
@@ -73,6 +74,9 @@ export declare class QueenCoordinator implements IQueenCoordinator {
73
74
  private hypothesisManager;
74
75
  private federationMailbox;
75
76
  private dynamicScaler;
77
+ private coExecutionRepo;
78
+ private dependencyGraph;
79
+ private availableMcpServers;
76
80
  constructor(eventBus: EventBus, agentCoordinator: AgentCoordinator, memory: MemoryBackend, router: CrossDomainRouter, protocolExecutor?: ProtocolExecutor | undefined, workflowExecutor?: WorkflowExecutor | undefined, domainPlugins?: Map<DomainName, DomainPlugin> | undefined, config?: Partial<QueenConfig>);
77
81
  initialize(): Promise<void>;
78
82
  dispose(): Promise<void>;
@@ -84,6 +88,15 @@ export declare class QueenCoordinator implements IQueenCoordinator {
84
88
  getDomainLoad(domain: DomainName): number;
85
89
  getIdleDomains(): DomainName[];
86
90
  getBusyDomains(): DomainName[];
91
+ /**
92
+ * Get a phased spawn plan that respects hard agent dependencies.
93
+ * Use this when spawning multiple agents for a swarm task.
94
+ */
95
+ getSpawnPlan(agentNames: string[]): import("..").SpawnPlan;
96
+ /**
97
+ * Get the dependency graph for the agent fleet.
98
+ */
99
+ getDependencyGraph(): DependencyGraphResult | null;
87
100
  enableWorkStealing(): void;
88
101
  disableWorkStealing(): void;
89
102
  triggerWorkStealing(): Promise<number>;
@@ -24,6 +24,10 @@ import { createQueenMinCutBridge, } from './mincut/queen-integration';
24
24
  import { getSharedMinCutGraph } from './mincut/shared-singleton';
25
25
  // V3 Integration: TinyDancer intelligent model routing (TD-004, TD-005, TD-006)
26
26
  import { QueenRouterAdapter, } from '../routing/queen-integration.js';
27
+ // Issue #342: Dependency intelligence integration
28
+ import { getCoExecutionRepository } from '../routing/co-execution-repository.js';
29
+ import { getAvailableMcpServers } from '../validation/steps/agent-mcp-validator.js';
30
+ import { buildDependencyGraph, createSpawnPlan } from '../routing/agent-dependency-graph.js';
27
31
  // V3 Integration: @claude-flow/guidance governance (ADR-058)
28
32
  import { queenGovernanceAdapter } from '../governance/index.js';
29
33
  // ADR-064 Integration: Agent Teams, Circuit Breakers, Fleet Tiers
@@ -102,6 +106,10 @@ export class QueenCoordinator {
102
106
  hypothesisManager = null;
103
107
  federationMailbox = null;
104
108
  dynamicScaler = null;
109
+ // Issue #342: Dependency intelligence
110
+ coExecutionRepo = null;
111
+ dependencyGraph = null;
112
+ availableMcpServers = [];
105
113
  constructor(eventBus, agentCoordinator, memory, router, protocolExecutor, workflowExecutor, domainPlugins, config = {}) {
106
114
  this.eventBus = eventBus;
107
115
  this.agentCoordinator = agentCoordinator;
@@ -177,6 +185,32 @@ export class QueenCoordinator {
177
185
  }
178
186
  // ADR-064: Initialize subsystems (non-fatal failures)
179
187
  this.initializeSubsystems();
188
+ // Issue #342: Initialize dependency intelligence (non-fatal)
189
+ try {
190
+ // Build dependency graph from agent definitions
191
+ const { join } = await import('path');
192
+ const agentsDir = join(process.cwd(), '.claude', 'agents', 'v3');
193
+ this.dependencyGraph = buildDependencyGraph(agentsDir);
194
+ this.availableMcpServers = getAvailableMcpServers(process.cwd());
195
+ // Initialize co-execution repository for behavioral learning
196
+ const { getUnifiedMemory } = await import('../kernel/unified-memory.js');
197
+ const unifiedMemory = getUnifiedMemory();
198
+ await unifiedMemory.initialize();
199
+ this.coExecutionRepo = getCoExecutionRepository();
200
+ this.coExecutionRepo.initialize(unifiedMemory.getDatabase());
201
+ if (this.dependencyGraph.warnings.length > 0) {
202
+ logger.warn(`Dependency graph: ${this.dependencyGraph.warnings.length} warning(s)`, {
203
+ warnings: this.dependencyGraph.warnings.slice(0, 5),
204
+ });
205
+ }
206
+ logger.info('Dependency intelligence initialized', {
207
+ agents: this.dependencyGraph.nodes.size,
208
+ mcpServers: this.availableMcpServers.length,
209
+ });
210
+ }
211
+ catch (depError) {
212
+ logger.warn('Dependency intelligence initialization failed (continuing)', { error: depError });
213
+ }
180
214
  // Publish initialization event
181
215
  await this.publishEvent('QueenInitialized', {
182
216
  timestamp: new Date(),
@@ -365,6 +399,24 @@ export class QueenCoordinator {
365
399
  return ALL_DOMAINS.filter(domain => this.getDomainLoad(domain) > this.config.workStealing.loadThreshold);
366
400
  }
367
401
  // ============================================================================
402
+ // Issue #342 Item 2: Dependency-Aware Spawn Planning
403
+ // ============================================================================
404
+ /**
405
+ * Get a phased spawn plan that respects hard agent dependencies.
406
+ * Use this when spawning multiple agents for a swarm task.
407
+ */
408
+ getSpawnPlan(agentNames) {
409
+ if (!this.dependencyGraph)
410
+ return { phases: [agentNames], warnings: [], unsatisfiedHardDeps: [] };
411
+ return createSpawnPlan(agentNames, this.dependencyGraph);
412
+ }
413
+ /**
414
+ * Get the dependency graph for the agent fleet.
415
+ */
416
+ getDependencyGraph() {
417
+ return this.dependencyGraph;
418
+ }
419
+ // ============================================================================
368
420
  // Work Stealing
369
421
  // ============================================================================
370
422
  enableWorkStealing() {
@@ -394,6 +446,29 @@ export class QueenCoordinator {
394
446
  if (!this.agentCoordinator.canSpawn()) {
395
447
  return err(new Error('Maximum concurrent agents reached (15)'));
396
448
  }
449
+ // Issue #342 Item 1: Pre-spawn MCP validation (advisory only — never blocks).
450
+ // Check dependency graph nodes in this domain for declared MCP server requirements.
451
+ if (this.dependencyGraph && this.availableMcpServers.length > 0) {
452
+ try {
453
+ // Find agents in this domain that have MCP server declarations
454
+ for (const [, node] of this.dependencyGraph.nodes) {
455
+ const mcpDeps = node.dependencies.mcpServers;
456
+ if (!mcpDeps || mcpDeps.length === 0)
457
+ continue;
458
+ const missing = mcpDeps
459
+ .filter(s => s.required && !this.availableMcpServers.includes(s.name));
460
+ if (missing.length > 0) {
461
+ logger.warn(`Pre-spawn MCP advisory: ${node.agentName} needs ${missing.map(m => m.name).join(', ')}`, {
462
+ agent: node.agentName, domain, missing: missing.map(m => m.name),
463
+ });
464
+ break; // Log once per spawn, not per agent
465
+ }
466
+ }
467
+ }
468
+ catch {
469
+ // Advisory validation must never block spawning
470
+ }
471
+ }
397
472
  const result = await this.agentCoordinator.spawn({
398
473
  name: `${domain}-${type}-${Date.now()}`, domain, type, capabilities,
399
474
  });
@@ -738,6 +813,7 @@ export class QueenCoordinator {
738
813
  get tierSelector() { return self.tierSelector; },
739
814
  get traceCollector() { return self.traceCollector; },
740
815
  taskTraceContexts: self.taskTraceContexts,
816
+ get coExecutionRepo() { return self.coExecutionRepo; },
741
817
  requestAgentSpawn: (d, t, c) => self.requestAgentSpawn(d, t, c),
742
818
  publishEvent: (t, p) => self.publishEvent(t, p),
743
819
  getDomainLoad: (d) => self.getDomainLoad(d),
@@ -7,6 +7,7 @@ import type { DomainName, Priority, Result } from '../shared/types';
7
7
  import type { AgentCoordinator, DomainPlugin } from '../kernel/interfaces';
8
8
  import type { TaskAuditLogger } from './services';
9
9
  import type { QueenRouterAdapter } from '../routing/queen-integration.js';
10
+ import type { CoExecutionRepository } from '../routing/co-execution-repository.js';
10
11
  import type { DomainBreakerRegistry } from './circuit-breaker/index.js';
11
12
  import type { DomainTeamManager } from './agent-teams/domain-team-manager.js';
12
13
  import type { TierSelector } from './fleet-tiers/index.js';
@@ -41,6 +42,7 @@ export interface QueenTaskContext {
41
42
  readonly tierSelector: TierSelector | null;
42
43
  readonly traceCollector: TraceCollector | null;
43
44
  readonly taskTraceContexts: Map<string, TraceContext>;
45
+ readonly coExecutionRepo: CoExecutionRepository | null;
44
46
  requestAgentSpawn(domain: DomainName, type: string, capabilities: string[]): Promise<Result<string, Error>>;
45
47
  publishEvent(type: string, payload: Record<string, unknown>): Promise<void>;
46
48
  getDomainLoad(domain: DomainName): number;
@@ -310,6 +310,16 @@ async function handleTaskCompletionCallback(ctx, result) {
310
310
  }
311
311
  // CC-002: Decrement running task counter
312
312
  ctx.runningTaskCounter = Math.max(0, ctx.runningTaskCounter - 1);
313
+ // Issue #342 Item 3: Record co-execution behavioral data for all agents in this task.
314
+ // This feeds the behavioral signal into the signal merger for future routing decisions.
315
+ if (ctx.coExecutionRepo && execution.assignedAgents.length >= 2) {
316
+ try {
317
+ ctx.coExecutionRepo.recordSwarmCoExecution(execution.assignedAgents, execution.assignedDomain || 'unknown', result.success, execution.task.type);
318
+ }
319
+ catch {
320
+ // Non-blocking: behavioral recording failure should not affect task completion
321
+ }
322
+ }
313
323
  // Stop assigned agents
314
324
  for (const agentId of execution.assignedAgents) {
315
325
  await ctx.agentCoordinator.stop(agentId);
@@ -9,6 +9,7 @@ import type { DomainBreakerRegistry } from './circuit-breaker/index.js';
9
9
  import type { DomainTeamManager } from './agent-teams/domain-team-manager.js';
10
10
  import type { TierSelector } from './fleet-tiers/index.js';
11
11
  import type { TraceCollector } from './agent-teams/tracing.js';
12
+ import type { DependencyGraphResult, SpawnPlan } from '../routing/agent-dependency-graph.js';
12
13
  import type { HypothesisManager } from './competing-hypotheses/index.js';
13
14
  import type { FederationMailbox } from './federation/index.js';
14
15
  import type { DynamicScaler } from './dynamic-scaling/index.js';
@@ -155,6 +156,8 @@ export interface IQueenCoordinator {
155
156
  getHypothesisManager(): HypothesisManager | null;
156
157
  getFederationMailbox(): FederationMailbox | null;
157
158
  getDynamicScaler(): DynamicScaler | null;
159
+ getSpawnPlan(agentNames: string[]): SpawnPlan;
160
+ getDependencyGraph(): DependencyGraphResult | null;
158
161
  }
159
162
  export interface TaskFilter {
160
163
  status?: TaskExecution['status'];
@@ -15,6 +15,7 @@
15
15
  import { v4 as uuidv4 } from 'uuid';
16
16
  import { toErrorMessage } from '../shared/error-utils.js';
17
17
  import { createResultSaver } from './result-saver';
18
+ import { createLogger } from '../logging/logger-factory.js';
18
19
  // ADR-051: Agent Booster integration for Tier 0 tasks
19
20
  import { createAgentBoosterAdapter, } from '../integrations/agentic-flow/agent-booster';
20
21
  // ADR-051: Task Router for outcome recording
@@ -23,6 +24,7 @@ import { getTaskRouter } from '../mcp/services/task-router';
23
24
  import { DomainServiceRegistry, ServiceKeys } from '../shared/domain-service-registry';
24
25
  // Handler registration functions (extracted from the monolithic registerHandlers)
25
26
  import { registerTestExecutionHandlers, registerCoverageHandlers, registerSecurityHandlers, registerQualityHandlers, registerRequirementsHandlers, registerCodeIntelligenceHandlers, registerMiscHandlers, } from './handlers/index';
27
+ const logger = createLogger('TaskExecutor');
26
28
  // ============================================================================
27
29
  // CQ-005: Domain Service Resolution via Registry (no coordination -> domains imports)
28
30
  // Domain modules register their factories in their index.ts files.
@@ -339,7 +341,7 @@ export class DomainTaskExecutor {
339
341
  const boosterResult = await this.executeWithAgentBooster(task, startTime, domain);
340
342
  if (boosterResult) {
341
343
  // Agent Booster succeeded - record outcome and return
342
- this.recordOutcome(task, 0, true, Date.now() - startTime).catch(() => { });
344
+ this.recordOutcome(task, 0, true, Date.now() - startTime).catch((e) => { logger.warn('recordOutcome failed', { error: e instanceof Error ? e.message : String(e), taskId: task.id }); });
343
345
  await this.publishTaskCompleted(task.id, boosterResult.data, domain);
344
346
  return boosterResult;
345
347
  }
@@ -355,7 +357,7 @@ export class DomainTaskExecutor {
355
357
  duration: Date.now() - startTime,
356
358
  domain,
357
359
  };
358
- this.recordOutcome(task, routingTier, false, Date.now() - startTime).catch(() => { });
360
+ this.recordOutcome(task, routingTier, false, Date.now() - startTime).catch((e) => { logger.warn('recordOutcome failed', { error: e instanceof Error ? e.message : String(e), taskId: task.id }); });
359
361
  return result;
360
362
  }
361
363
  // Execute with timeout
@@ -367,7 +369,7 @@ export class DomainTaskExecutor {
367
369
  const errorMsg = 'error' in result ? result.error.message : 'Unknown error';
368
370
  await this.publishTaskFailed(task.id, errorMsg, domain);
369
371
  // ADR-051: Record failed outcome
370
- this.recordOutcome(task, routingTier, false, Date.now() - startTime).catch(() => { });
372
+ this.recordOutcome(task, routingTier, false, Date.now() - startTime).catch((e) => { logger.warn('recordOutcome failed', { error: e instanceof Error ? e.message : String(e), taskId: task.id }); });
371
373
  return {
372
374
  taskId: task.id,
373
375
  success: false,
@@ -378,7 +380,7 @@ export class DomainTaskExecutor {
378
380
  }
379
381
  await this.publishTaskCompleted(task.id, result.value, domain);
380
382
  // ADR-051: Record successful outcome
381
- this.recordOutcome(task, routingTier, true, Date.now() - startTime).catch(() => { });
383
+ this.recordOutcome(task, routingTier, true, Date.now() - startTime).catch((e) => { logger.warn('recordOutcome failed', { error: e instanceof Error ? e.message : String(e), taskId: task.id }); });
382
384
  // Save results to files if enabled
383
385
  let savedFiles;
384
386
  if (this._config.saveResults) {
@@ -417,7 +419,7 @@ export class DomainTaskExecutor {
417
419
  const errorMessage = toErrorMessage(error);
418
420
  await this.publishTaskFailed(task.id, errorMessage, domain);
419
421
  // ADR-051: Record failed outcome
420
- this.recordOutcome(task, routingTier, false, Date.now() - startTime).catch(() => { });
422
+ this.recordOutcome(task, routingTier, false, Date.now() - startTime).catch((e) => { logger.warn('recordOutcome failed', { error: e instanceof Error ? e.message : String(e), taskId: task.id }); });
421
423
  return {
422
424
  taskId: task.id,
423
425
  success: false,
@@ -20,9 +20,33 @@ export declare class SASTScanner {
20
20
  */
21
21
  scanFiles(files: FilePath[]): Promise<Result<SASTResult>>;
22
22
  /**
23
- * Scan with specific rule sets
23
+ * Scan with specific rule sets.
24
+ * Runs pattern-based scanning and semgrep (when available) in parallel,
25
+ * then merges and deduplicates results.
24
26
  */
25
27
  scanWithRules(files: FilePath[], ruleSetIds: string[]): Promise<Result<SASTResult>>;
28
+ /**
29
+ * Run pattern-based scanning on all files
30
+ */
31
+ private runPatternScanning;
32
+ /**
33
+ * Run semgrep scanning when enabled and available.
34
+ * Returns converted vulnerabilities or empty array on failure/unavailability.
35
+ */
36
+ private runSemgrepScanning;
37
+ /**
38
+ * Resolve the common parent directory from a set of file paths
39
+ */
40
+ private resolveTargetDirectory;
41
+ /**
42
+ * Map semgrep OWASP category string to VulnerabilityCategory
43
+ */
44
+ private mapSemgrepCategory;
45
+ /**
46
+ * Merge pattern-based and semgrep vulnerabilities, deduplicating
47
+ * findings that overlap on the same file and line.
48
+ */
49
+ private mergeVulnerabilities;
26
50
  /**
27
51
  * Get available rule sets
28
52
  */
@@ -7,6 +7,7 @@ import { ok, err } from '@shared/types/index.js';
7
7
  import { ALL_SECURITY_PATTERNS, BUILT_IN_RULE_SETS } from './security-patterns.js';
8
8
  import { toError } from '@shared/error-utils.js';
9
9
  import { safeJsonParse } from '@shared/safe-json.js';
10
+ import { isSemgrepAvailable, runSemgrepWithRules, convertSemgrepFindings, } from '../semgrep-integration.js';
10
11
  // ============================================================================
11
12
  // SAST Scanner Service
12
13
  // ============================================================================
@@ -35,7 +36,9 @@ export class SASTScanner {
35
36
  return this.scanWithRules(files, this.config.defaultRuleSets);
36
37
  }
37
38
  /**
38
- * Scan with specific rule sets
39
+ * Scan with specific rule sets.
40
+ * Runs pattern-based scanning and semgrep (when available) in parallel,
41
+ * then merges and deduplicates results.
39
42
  */
40
43
  async scanWithRules(files, ruleSetIds) {
41
44
  const scanId = uuidv4();
@@ -50,22 +53,23 @@ export class SASTScanner {
50
53
  if (ruleSets.length === 0) {
51
54
  return err(new Error(`No valid rule sets found: ${ruleSetIds.join(', ')}`));
52
55
  }
53
- // Perform static analysis on each file
54
- const vulnerabilities = [];
55
- let linesScanned = 0;
56
- for (const file of files) {
57
- const fileVulns = await this.analyzeFile(file, ruleSets);
58
- vulnerabilities.push(...fileVulns.vulnerabilities);
59
- linesScanned += fileVulns.linesScanned;
60
- }
56
+ // Run pattern-based scanning and semgrep in parallel
57
+ const [patternResult, semgrepVulns] = await Promise.all([
58
+ this.runPatternScanning(files, ruleSets),
59
+ this.runSemgrepScanning(files, ruleSetIds),
60
+ ]);
61
+ // Merge pattern-based and semgrep findings, deduplicating by file+line
62
+ const vulnerabilities = this.mergeVulnerabilities(patternResult.vulnerabilities, semgrepVulns);
63
+ const linesScanned = patternResult.linesScanned;
61
64
  const scanDurationMs = Date.now() - startTime;
62
65
  // Calculate summary
63
66
  const summary = this.calculateSummary(vulnerabilities, files.length, scanDurationMs);
64
- // Calculate coverage
67
+ // Calculate coverage — include semgrep rules when they ran
68
+ const patternRules = ruleSets.reduce((acc, rs) => acc + rs.ruleCount, 0);
65
69
  const coverage = {
66
70
  filesScanned: files.length,
67
71
  linesScanned,
68
- rulesApplied: ruleSets.reduce((acc, rs) => acc + rs.ruleCount, 0),
72
+ rulesApplied: patternRules + (semgrepVulns.length > 0 ? semgrepVulns.length : 0),
69
73
  };
70
74
  // Store scan results in memory
71
75
  await this.storeScanResults(scanId, 'sast', vulnerabilities, summary);
@@ -82,6 +86,131 @@ export class SASTScanner {
82
86
  return err(toError(error));
83
87
  }
84
88
  }
89
+ /**
90
+ * Run pattern-based scanning on all files
91
+ */
92
+ async runPatternScanning(files, ruleSets) {
93
+ const vulnerabilities = [];
94
+ let linesScanned = 0;
95
+ for (const file of files) {
96
+ const fileVulns = await this.analyzeFile(file, ruleSets);
97
+ vulnerabilities.push(...fileVulns.vulnerabilities);
98
+ linesScanned += fileVulns.linesScanned;
99
+ }
100
+ return { vulnerabilities, linesScanned };
101
+ }
102
+ /**
103
+ * Run semgrep scanning when enabled and available.
104
+ * Returns converted vulnerabilities or empty array on failure/unavailability.
105
+ */
106
+ async runSemgrepScanning(files, ruleSetIds) {
107
+ if (!this.config.enableSemgrep) {
108
+ return [];
109
+ }
110
+ try {
111
+ const available = await isSemgrepAvailable();
112
+ if (!available) {
113
+ return [];
114
+ }
115
+ // Determine target directory from files (use common parent)
116
+ const targetDir = this.resolveTargetDirectory(files);
117
+ const semgrepResult = await runSemgrepWithRules(targetDir, ruleSetIds);
118
+ if (!semgrepResult.success || semgrepResult.findings.length === 0) {
119
+ return [];
120
+ }
121
+ // Convert semgrep findings to our Vulnerability format
122
+ const converted = convertSemgrepFindings(semgrepResult.findings);
123
+ return converted.map(f => ({
124
+ id: uuidv4(),
125
+ cveId: undefined,
126
+ title: f.title,
127
+ description: `[semgrep] ${f.description}`,
128
+ severity: f.severity,
129
+ category: this.mapSemgrepCategory(f.owaspCategory),
130
+ location: {
131
+ file: f.file,
132
+ line: f.line,
133
+ column: f.column,
134
+ snippet: f.snippet,
135
+ },
136
+ remediation: {
137
+ description: f.remediation,
138
+ estimatedEffort: 'moderate',
139
+ automatable: false,
140
+ },
141
+ references: f.references,
142
+ }));
143
+ }
144
+ catch {
145
+ // Semgrep failure is non-fatal — pattern scanning still covers us
146
+ return [];
147
+ }
148
+ }
149
+ /**
150
+ * Resolve the common parent directory from a set of file paths
151
+ */
152
+ resolveTargetDirectory(files) {
153
+ if (files.length === 0)
154
+ return '.';
155
+ if (files.length === 1)
156
+ return files[0].directory || '.';
157
+ // Find common prefix of all directories
158
+ const dirs = files.map(f => f.directory || '.');
159
+ const first = dirs[0];
160
+ let commonLen = first.length;
161
+ for (let i = 1; i < dirs.length; i++) {
162
+ const dir = dirs[i];
163
+ const maxLen = Math.min(commonLen, dir.length);
164
+ let j = 0;
165
+ while (j < maxLen && first[j] === dir[j])
166
+ j++;
167
+ commonLen = j;
168
+ }
169
+ const common = first.substring(0, commonLen);
170
+ // Trim to last path separator
171
+ const lastSep = common.lastIndexOf('/');
172
+ return lastSep > 0 ? common.substring(0, lastSep) : common || '.';
173
+ }
174
+ /**
175
+ * Map semgrep OWASP category string to VulnerabilityCategory
176
+ */
177
+ mapSemgrepCategory(owaspCategory) {
178
+ if (!owaspCategory)
179
+ return 'injection';
180
+ const categoryMap = {
181
+ 'A01': 'access-control',
182
+ 'A02': 'sensitive-data',
183
+ 'A03': 'injection',
184
+ 'A04': 'insecure-deserialization',
185
+ 'A05': 'security-misconfiguration',
186
+ 'A06': 'vulnerable-components',
187
+ 'A07': 'broken-auth',
188
+ 'A08': 'insecure-deserialization',
189
+ 'A09': 'insufficient-logging',
190
+ 'A10': 'xxe',
191
+ };
192
+ // Try exact match or prefix match (e.g. "A03:2021-Injection")
193
+ for (const [key, value] of Object.entries(categoryMap)) {
194
+ if (owaspCategory.startsWith(key))
195
+ return value;
196
+ }
197
+ return 'injection';
198
+ }
199
+ /**
200
+ * Merge pattern-based and semgrep vulnerabilities, deduplicating
201
+ * findings that overlap on the same file and line.
202
+ */
203
+ mergeVulnerabilities(patternVulns, semgrepVulns) {
204
+ if (semgrepVulns.length === 0)
205
+ return patternVulns;
206
+ if (patternVulns.length === 0)
207
+ return semgrepVulns;
208
+ // Build a set of file:line keys from pattern results
209
+ const patternKeys = new Set(patternVulns.map(v => `${v.location.file}:${v.location.line ?? 0}:${v.category}`));
210
+ // Only add semgrep findings that don't overlap with pattern findings
211
+ const uniqueSemgrep = semgrepVulns.filter(v => !patternKeys.has(`${v.location.file}:${v.location.line ?? 0}:${v.category}`));
212
+ return [...patternVulns, ...uniqueSemgrep];
213
+ }
85
214
  /**
86
215
  * Get available rule sets
87
216
  */
@@ -19,6 +19,8 @@ export interface SecurityScannerConfig {
19
19
  enableLLMAnalysis: boolean;
20
20
  /** ADR-051: Model tier for LLM calls (1=Haiku, 2=Sonnet, 4=Opus) */
21
21
  llmModelTier: number;
22
+ /** Enable semgrep integration when semgrep is installed (default: true) */
23
+ enableSemgrep: boolean;
22
24
  }
23
25
  /**
24
26
  * Dependencies for SecurityScannerService
@@ -11,5 +11,6 @@ export const DEFAULT_CONFIG = {
11
11
  dastActiveScanning: false,
12
12
  enableLLMAnalysis: true, // On by default - opt-out (ADR-051)
13
13
  llmModelTier: 4, // Opus for security analysis (needs expert reasoning)
14
+ enableSemgrep: true, // Use semgrep when installed for real SAST
14
15
  };
15
16
  //# sourceMappingURL=scanner-types.js.map