agentic-qe 2.7.2 → 2.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +74 -0
- package/README.md +1 -1
- package/dist/core/memory/HNSWVectorMemory.js +1 -1
- package/dist/learning/QLearning.d.ts +101 -1
- package/dist/learning/QLearning.d.ts.map +1 -1
- package/dist/learning/QLearning.js +168 -0
- package/dist/learning/QLearning.js.map +1 -1
- package/dist/mcp/server-instructions.d.ts +1 -1
- package/dist/mcp/server-instructions.js +1 -1
- package/dist/planning/GOAPPlanner.d.ts +42 -0
- package/dist/planning/GOAPPlanner.d.ts.map +1 -1
- package/dist/planning/GOAPPlanner.js +151 -0
- package/dist/planning/GOAPPlanner.js.map +1 -1
- package/dist/planning/PlanLearning.d.ts +184 -0
- package/dist/planning/PlanLearning.d.ts.map +1 -0
- package/dist/planning/PlanLearning.js +526 -0
- package/dist/planning/PlanLearning.js.map +1 -0
- package/dist/planning/PlanSimilarity.d.ts +148 -0
- package/dist/planning/PlanSimilarity.d.ts.map +1 -0
- package/dist/planning/PlanSimilarity.js +463 -0
- package/dist/planning/PlanSimilarity.js.map +1 -0
- package/dist/planning/execution/PlanExecutor.d.ts +101 -2
- package/dist/planning/execution/PlanExecutor.d.ts.map +1 -1
- package/dist/planning/execution/PlanExecutor.js +587 -14
- package/dist/planning/execution/PlanExecutor.js.map +1 -1
- package/dist/planning/index.d.ts +2 -0
- package/dist/planning/index.d.ts.map +1 -1
- package/dist/planning/index.js +15 -4
- package/dist/planning/index.js.map +1 -1
- package/dist/planning/integration/GOAPQualityGateIntegration.d.ts +6 -0
- package/dist/planning/integration/GOAPQualityGateIntegration.d.ts.map +1 -1
- package/dist/planning/integration/GOAPQualityGateIntegration.js +7 -0
- package/dist/planning/integration/GOAPQualityGateIntegration.js.map +1 -1
- package/dist/planning/types.d.ts +2 -0
- package/dist/planning/types.d.ts.map +1 -1
- package/dist/planning/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
* Executes remediation plans generated by GOAPQualityGateIntegration:
|
|
6
6
|
* - Spawns agents for each action
|
|
7
7
|
* - Executes actions in sequence with dependency ordering
|
|
8
|
-
* - Records outcomes for learning
|
|
8
|
+
* - Records outcomes for learning via PlanLearning integration
|
|
9
9
|
* - Handles failures with automatic replanning
|
|
10
|
+
* - Phase 6: Live agent execution with real world state updates
|
|
10
11
|
*
|
|
11
12
|
* @module planning/execution/PlanExecutor
|
|
12
|
-
* @version 1.
|
|
13
|
+
* @version 1.2.0 - Phase 6: Live Agent Execution
|
|
13
14
|
*/
|
|
14
15
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
15
16
|
if (k2 === undefined) k2 = k;
|
|
@@ -50,6 +51,8 @@ exports.createPlanExecutor = createPlanExecutor;
|
|
|
50
51
|
exports.executeQualityGateRemediation = executeQualityGateRemediation;
|
|
51
52
|
const GOAPQualityGateIntegration_1 = require("../integration/GOAPQualityGateIntegration");
|
|
52
53
|
const Logger_1 = require("../../utils/Logger");
|
|
54
|
+
const PlanLearning_1 = require("../PlanLearning");
|
|
55
|
+
const types_1 = require("../types");
|
|
53
56
|
// Lazy import to avoid circular dependencies and reduce memory in tests
|
|
54
57
|
let AgentRegistryModule = null;
|
|
55
58
|
async function getAgentRegistryModule() {
|
|
@@ -70,6 +73,7 @@ const DEFAULT_CONFIG = {
|
|
|
70
73
|
*
|
|
71
74
|
* Bridges plan generation with actual agent execution.
|
|
72
75
|
* Implements the OODA (Observe-Orient-Decide-Act) loop for plan execution.
|
|
76
|
+
* Integrates with PlanLearning for continuous improvement.
|
|
73
77
|
*/
|
|
74
78
|
class PlanExecutor {
|
|
75
79
|
constructor(db, integration, config = {}) {
|
|
@@ -77,10 +81,38 @@ class PlanExecutor {
|
|
|
77
81
|
this.ownsRegistry = false; // Track if we created the registry
|
|
78
82
|
this.db = db;
|
|
79
83
|
this.integration = integration;
|
|
84
|
+
this.planLearning = new PlanLearning_1.PlanLearning(db);
|
|
80
85
|
this.logger = Logger_1.Logger.getInstance();
|
|
81
86
|
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
87
|
+
this.currentWorldState = { ...types_1.DEFAULT_WORLD_STATE };
|
|
82
88
|
// Registry is lazily initialized only when needed (not in dry-run mode)
|
|
83
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Get the PlanLearning instance for external access
|
|
92
|
+
*/
|
|
93
|
+
getPlanLearning() {
|
|
94
|
+
return this.planLearning;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Update current world state (called by integration or externally)
|
|
98
|
+
*/
|
|
99
|
+
updateWorldState(state) {
|
|
100
|
+
this.currentWorldState = {
|
|
101
|
+
...this.currentWorldState,
|
|
102
|
+
...state,
|
|
103
|
+
coverage: { ...this.currentWorldState.coverage, ...state.coverage },
|
|
104
|
+
quality: { ...this.currentWorldState.quality, ...state.quality },
|
|
105
|
+
fleet: { ...this.currentWorldState.fleet, ...state.fleet },
|
|
106
|
+
resources: { ...this.currentWorldState.resources, ...state.resources },
|
|
107
|
+
context: { ...this.currentWorldState.context, ...state.context }
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get current world state
|
|
112
|
+
*/
|
|
113
|
+
getWorldState() {
|
|
114
|
+
return { ...this.currentWorldState };
|
|
115
|
+
}
|
|
84
116
|
/**
|
|
85
117
|
* Initialize registry only when needed for actual execution
|
|
86
118
|
*/
|
|
@@ -119,6 +151,11 @@ class PlanExecutor {
|
|
|
119
151
|
actionResults: [],
|
|
120
152
|
replanned: false
|
|
121
153
|
};
|
|
154
|
+
// Track executed actions for learning (Phase 5 integration)
|
|
155
|
+
const executedActions = [];
|
|
156
|
+
// Initialize world state from metrics
|
|
157
|
+
this.initializeWorldStateFromMetrics(metrics, context);
|
|
158
|
+
const initialWorldState = this.getWorldState();
|
|
122
159
|
this.logger.info('[PlanExecutor] Starting plan execution', {
|
|
123
160
|
planId: plan.planId,
|
|
124
161
|
actionCount: plan.actions.length,
|
|
@@ -132,17 +169,46 @@ class PlanExecutor {
|
|
|
132
169
|
try {
|
|
133
170
|
// Execute each action in sequence
|
|
134
171
|
for (const action of currentPlan.actions) {
|
|
172
|
+
// Capture state before action
|
|
173
|
+
const stateBefore = this.getWorldState();
|
|
135
174
|
const actionResult = await this.executeAction(action, context);
|
|
136
175
|
result.actionResults.push(actionResult);
|
|
137
176
|
result.actionsExecuted++;
|
|
177
|
+
// Phase 6: Capture state after action
|
|
178
|
+
// Use real output parsing in live mode, simulation in dry-run
|
|
179
|
+
let stateAfter;
|
|
180
|
+
if (this.config.dryRun) {
|
|
181
|
+
stateAfter = this.simulateActionEffects(stateBefore, action, actionResult.success);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
// Live mode: parse actual agent output for real measurements
|
|
185
|
+
stateAfter = this.updateWorldStateFromAgentOutput(action, actionResult.output, stateBefore);
|
|
186
|
+
// Fallback to simulation if no output parsed
|
|
187
|
+
if (JSON.stringify(stateAfter) === JSON.stringify(stateBefore)) {
|
|
188
|
+
stateAfter = this.simulateActionEffects(stateBefore, action, actionResult.success);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
this.currentWorldState = stateAfter;
|
|
192
|
+
// Build ExecutedAction for learning
|
|
193
|
+
const executedAction = {
|
|
194
|
+
action: this.remediationActionToGOAPAction(action),
|
|
195
|
+
success: actionResult.success,
|
|
196
|
+
result: actionResult.output,
|
|
197
|
+
error: actionResult.error,
|
|
198
|
+
stateBefore,
|
|
199
|
+
stateAfter,
|
|
200
|
+
executionTimeMs: actionResult.durationMs,
|
|
201
|
+
agentId: actionResult.agentId
|
|
202
|
+
};
|
|
203
|
+
executedActions.push(executedAction);
|
|
138
204
|
if (actionResult.success) {
|
|
139
205
|
result.actionsSucceeded++;
|
|
140
|
-
// Record success for learning
|
|
206
|
+
// Record success for learning (legacy + new)
|
|
141
207
|
await this.integration.recordActionOutcome(action.id, true);
|
|
142
208
|
}
|
|
143
209
|
else {
|
|
144
210
|
result.actionsFailed++;
|
|
145
|
-
// Record failure for learning
|
|
211
|
+
// Record failure for learning (legacy + new)
|
|
146
212
|
await this.integration.recordActionOutcome(action.id, false);
|
|
147
213
|
if (!this.config.continueOnFailure) {
|
|
148
214
|
this.logger.warn('[PlanExecutor] Action failed, attempting replan', {
|
|
@@ -204,6 +270,44 @@ class PlanExecutor {
|
|
|
204
270
|
await this.updatePlanStatus(plan.planId, result.success ? 'completed' : 'failed', result.success, result.error);
|
|
205
271
|
// Mark plan as completed via integration
|
|
206
272
|
await this.integration.completePlan(plan.planId, result.success, result.error);
|
|
273
|
+
// Phase 5 & 6: Learn from execution and store signatures
|
|
274
|
+
if (executedActions.length > 0) {
|
|
275
|
+
try {
|
|
276
|
+
const goapPlan = this.remediationPlanToGOAPPlan(plan, initialWorldState);
|
|
277
|
+
// Learn from execution (Phase 5)
|
|
278
|
+
const learningOutcome = await this.planLearning.learnFromExecution(goapPlan, executedActions, result.success);
|
|
279
|
+
this.logger.info('[PlanExecutor] Learning from execution completed', {
|
|
280
|
+
planId: plan.planId,
|
|
281
|
+
actionsUpdated: learningOutcome.actionsUpdated,
|
|
282
|
+
qValueUpdates: learningOutcome.qValueUpdates
|
|
283
|
+
});
|
|
284
|
+
// Phase 6: Store plan signature for future reuse (only for successful executions)
|
|
285
|
+
if (result.success && !this.config.dryRun) {
|
|
286
|
+
try {
|
|
287
|
+
// Get PlanSimilarity from integration's planner
|
|
288
|
+
const planner = this.integration.getPlanner();
|
|
289
|
+
if (planner) {
|
|
290
|
+
planner.storePlanSignature(goapPlan, initialWorldState);
|
|
291
|
+
this.logger.info('[PlanExecutor] Plan signature stored for future reuse', {
|
|
292
|
+
planId: plan.planId,
|
|
293
|
+
actionCount: goapPlan.actions.length
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch (sigError) {
|
|
298
|
+
this.logger.warn('[PlanExecutor] Failed to store plan signature', {
|
|
299
|
+
error: sigError instanceof Error ? sigError.message : String(sigError)
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
// Learning failure should not fail the execution
|
|
306
|
+
this.logger.warn('[PlanExecutor] Learning from execution failed', {
|
|
307
|
+
error: error instanceof Error ? error.message : String(error)
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
207
311
|
this.logger.info('[PlanExecutor] Plan execution completed', {
|
|
208
312
|
planId: plan.planId,
|
|
209
313
|
success: result.success,
|
|
@@ -277,30 +381,61 @@ class PlanExecutor {
|
|
|
277
381
|
}
|
|
278
382
|
/**
|
|
279
383
|
* Map GOAP agent type to MCP agent type
|
|
384
|
+
*
|
|
385
|
+
* GOAP actions use 'qe-*' prefixed agent types which need to be
|
|
386
|
+
* mapped to MCP types that AgentRegistry understands.
|
|
280
387
|
*/
|
|
281
388
|
mapAgentType(goapAgentType) {
|
|
282
389
|
const mapping = {
|
|
283
|
-
//
|
|
390
|
+
// Core Testing Agents
|
|
284
391
|
'qe-test-generator': 'test-generator',
|
|
285
392
|
'qe-test-executor': 'test-executor',
|
|
393
|
+
'qe-test-writer': 'test-generator', // Test writers use test-generator agent
|
|
394
|
+
'qe-integration-tester': 'test-executor', // Integration tests use test-executor
|
|
395
|
+
// Coverage & Analysis Agents
|
|
286
396
|
'qe-coverage-analyzer': 'coverage-analyzer',
|
|
397
|
+
'qe-code-complexity': 'code-analyzer', // Maps to quality-analyzer
|
|
398
|
+
'qe-code-intelligence': 'code-analyzer', // Maps to quality-analyzer
|
|
399
|
+
// Quality & Gate Agents
|
|
287
400
|
'qe-quality-gate': 'quality-gate',
|
|
288
|
-
'qe-
|
|
401
|
+
'qe-quality-analyzer': 'code-analyzer',
|
|
402
|
+
// Security Agents
|
|
289
403
|
'qe-security-scanner': 'security-scanner',
|
|
404
|
+
// Performance Agents
|
|
405
|
+
'qe-performance-tester': 'performance-tester',
|
|
406
|
+
// Specialized Testing Agents
|
|
290
407
|
'qe-flaky-test-hunter': 'flaky-test-detector',
|
|
291
408
|
'qe-regression-risk-analyzer': 'regression-analyzer',
|
|
409
|
+
'qe-chaos-engineer': 'chaos-engineer',
|
|
410
|
+
'qe-visual-tester': 'visual-tester',
|
|
411
|
+
// Strategic Planning Agents
|
|
292
412
|
'qe-requirements-validator': 'requirements-validator',
|
|
413
|
+
'qe-deployment-readiness': 'deployment-validator',
|
|
414
|
+
'qe-production-intelligence': 'production-analyzer',
|
|
415
|
+
// Fleet & Orchestration Agents
|
|
293
416
|
'qe-fleet-commander': 'fleet-commander',
|
|
294
|
-
|
|
295
|
-
|
|
417
|
+
// Data & Contract Agents
|
|
418
|
+
'qe-test-data-architect': 'data-generator',
|
|
419
|
+
'qe-api-contract-validator': 'contract-validator',
|
|
420
|
+
// Quality Experience (QX) Agent
|
|
421
|
+
'qe-qx-partner': 'qx-partner',
|
|
422
|
+
// Accessibility Agent
|
|
423
|
+
'qe-accessibility-ally': 'accessibility-ally',
|
|
424
|
+
// Legacy/simple mappings (for backward compatibility)
|
|
296
425
|
'test-generator': 'test-generator',
|
|
297
426
|
'test-executor': 'test-executor',
|
|
298
427
|
'coverage-analyzer': 'coverage-analyzer',
|
|
299
428
|
'quality-gate': 'quality-gate',
|
|
300
429
|
'performance-tester': 'performance-tester',
|
|
301
|
-
'security-scanner': 'security-scanner'
|
|
430
|
+
'security-scanner': 'security-scanner',
|
|
431
|
+
'fleet-commander': 'fleet-commander',
|
|
432
|
+
'chaos-engineer': 'chaos-engineer'
|
|
302
433
|
};
|
|
303
|
-
|
|
434
|
+
const mcpType = mapping[goapAgentType];
|
|
435
|
+
if (!mcpType) {
|
|
436
|
+
this.logger.warn(`[PlanExecutor] Unknown GOAP agent type: ${goapAgentType}, defaulting to quality-gate`);
|
|
437
|
+
}
|
|
438
|
+
return mcpType || 'quality-gate';
|
|
304
439
|
}
|
|
305
440
|
/**
|
|
306
441
|
* Get capabilities for an action
|
|
@@ -319,12 +454,21 @@ class PlanExecutor {
|
|
|
319
454
|
}
|
|
320
455
|
/**
|
|
321
456
|
* Create a task from a remediation action
|
|
457
|
+
*
|
|
458
|
+
* Maps GOAP action categories to structured task payloads that
|
|
459
|
+
* QE agents can understand and execute properly.
|
|
322
460
|
*/
|
|
323
461
|
createTaskFromAction(action, context) {
|
|
462
|
+
const taskId = `task-${action.id}-${Date.now()}`;
|
|
463
|
+
const priority = context.criticality === 'critical' ? 1 : context.criticality === 'high' ? 3 : 5;
|
|
464
|
+
// Build category-specific payload
|
|
465
|
+
const categoryPayload = this.buildCategoryPayload(action, context);
|
|
324
466
|
return {
|
|
325
|
-
id:
|
|
326
|
-
type: action.category,
|
|
467
|
+
id: taskId,
|
|
468
|
+
type: this.mapCategoryToTaskType(action.category),
|
|
469
|
+
taskType: this.mapCategoryToTaskType(action.category),
|
|
327
470
|
payload: {
|
|
471
|
+
...categoryPayload,
|
|
328
472
|
actionId: action.id,
|
|
329
473
|
actionName: action.name,
|
|
330
474
|
description: action.description,
|
|
@@ -333,12 +477,441 @@ class PlanExecutor {
|
|
|
333
477
|
buildId: context.buildId,
|
|
334
478
|
environment: context.environment
|
|
335
479
|
},
|
|
336
|
-
priority
|
|
480
|
+
priority,
|
|
337
481
|
description: action.description || action.name,
|
|
338
482
|
context: {
|
|
339
483
|
source: 'goap-remediation',
|
|
340
|
-
planAction: action.id
|
|
484
|
+
planAction: action.id,
|
|
485
|
+
goapCategory: action.category
|
|
486
|
+
},
|
|
487
|
+
requirements: this.buildRequirements(action)
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Build category-specific payload for task execution
|
|
492
|
+
*
|
|
493
|
+
* Note: action.effects is string[] describing the state changes
|
|
494
|
+
*/
|
|
495
|
+
buildCategoryPayload(action, context) {
|
|
496
|
+
const basePayload = {
|
|
497
|
+
actionId: action.id,
|
|
498
|
+
actionName: action.name
|
|
499
|
+
};
|
|
500
|
+
// Parse coverage target from effects if present
|
|
501
|
+
const coverageTarget = this.extractCoverageTarget(action.effects);
|
|
502
|
+
switch (action.category) {
|
|
503
|
+
case 'test':
|
|
504
|
+
return {
|
|
505
|
+
...basePayload,
|
|
506
|
+
testType: this.inferTestType(action.id),
|
|
507
|
+
targetCoverage: coverageTarget,
|
|
508
|
+
runIntegration: action.id.includes('integration'),
|
|
509
|
+
runUnit: action.id.includes('unit') || !action.id.includes('integration')
|
|
510
|
+
};
|
|
511
|
+
case 'coverage':
|
|
512
|
+
return {
|
|
513
|
+
...basePayload,
|
|
514
|
+
analysisType: 'gap-detection',
|
|
515
|
+
targetCoverage: coverageTarget,
|
|
516
|
+
includeUncovered: true
|
|
517
|
+
};
|
|
518
|
+
case 'security':
|
|
519
|
+
return {
|
|
520
|
+
...basePayload,
|
|
521
|
+
scanType: this.inferSecurityScanType(action.id),
|
|
522
|
+
severityThreshold: action.id.includes('critical') ? 'critical' : 'high',
|
|
523
|
+
autoFix: action.id.includes('fix') || action.id.includes('remediate')
|
|
524
|
+
};
|
|
525
|
+
case 'performance':
|
|
526
|
+
return {
|
|
527
|
+
...basePayload,
|
|
528
|
+
testType: this.inferPerformanceTestType(action.id),
|
|
529
|
+
durationSeconds: 60,
|
|
530
|
+
targetP95Ms: 200,
|
|
531
|
+
targetErrorRate: 0.01
|
|
532
|
+
};
|
|
533
|
+
case 'analysis':
|
|
534
|
+
return {
|
|
535
|
+
...basePayload,
|
|
536
|
+
analysisDepth: 'full',
|
|
537
|
+
includeMetrics: true
|
|
538
|
+
};
|
|
539
|
+
case 'process':
|
|
540
|
+
return {
|
|
541
|
+
...basePayload,
|
|
542
|
+
processType: 'quality-gate-evaluation',
|
|
543
|
+
environment: context.environment,
|
|
544
|
+
criticality: context.criticality
|
|
545
|
+
};
|
|
546
|
+
case 'fleet':
|
|
547
|
+
return {
|
|
548
|
+
...basePayload,
|
|
549
|
+
fleetOperation: this.inferFleetOperation(action.id),
|
|
550
|
+
maxAgents: 10
|
|
551
|
+
};
|
|
552
|
+
default:
|
|
553
|
+
return basePayload;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Extract coverage target from effects array
|
|
558
|
+
*/
|
|
559
|
+
extractCoverageTarget(effects) {
|
|
560
|
+
// Effects are strings like "coverage.line >= 80" or "Increase line coverage by 10%"
|
|
561
|
+
for (const effect of effects) {
|
|
562
|
+
const match = effect.match(/coverage.*?(\d+)/i);
|
|
563
|
+
if (match) {
|
|
564
|
+
return parseInt(match[1], 10);
|
|
341
565
|
}
|
|
566
|
+
}
|
|
567
|
+
return 80; // Default target
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Map action category to task type
|
|
571
|
+
*/
|
|
572
|
+
mapCategoryToTaskType(category) {
|
|
573
|
+
const mapping = {
|
|
574
|
+
test: 'test-execution',
|
|
575
|
+
coverage: 'coverage-analysis',
|
|
576
|
+
security: 'security-scan',
|
|
577
|
+
performance: 'performance-test',
|
|
578
|
+
analysis: 'code-analysis',
|
|
579
|
+
process: 'process-evaluation',
|
|
580
|
+
fleet: 'fleet-management'
|
|
581
|
+
};
|
|
582
|
+
return mapping[category] || 'generic';
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Infer test type from action ID
|
|
586
|
+
*/
|
|
587
|
+
inferTestType(actionId) {
|
|
588
|
+
if (actionId.includes('unit'))
|
|
589
|
+
return 'unit';
|
|
590
|
+
if (actionId.includes('integration'))
|
|
591
|
+
return 'integration';
|
|
592
|
+
if (actionId.includes('e2e'))
|
|
593
|
+
return 'e2e';
|
|
594
|
+
if (actionId.includes('smoke'))
|
|
595
|
+
return 'smoke';
|
|
596
|
+
if (actionId.includes('regression'))
|
|
597
|
+
return 'regression';
|
|
598
|
+
return 'unit';
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Infer security scan type from action ID
|
|
602
|
+
*/
|
|
603
|
+
inferSecurityScanType(actionId) {
|
|
604
|
+
if (actionId.includes('sast'))
|
|
605
|
+
return 'sast';
|
|
606
|
+
if (actionId.includes('dast'))
|
|
607
|
+
return 'dast';
|
|
608
|
+
if (actionId.includes('dependency'))
|
|
609
|
+
return 'dependency';
|
|
610
|
+
if (actionId.includes('vulnerability'))
|
|
611
|
+
return 'vulnerability';
|
|
612
|
+
return 'comprehensive';
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Infer performance test type from action ID
|
|
616
|
+
*/
|
|
617
|
+
inferPerformanceTestType(actionId) {
|
|
618
|
+
if (actionId.includes('load'))
|
|
619
|
+
return 'load';
|
|
620
|
+
if (actionId.includes('stress'))
|
|
621
|
+
return 'stress';
|
|
622
|
+
if (actionId.includes('spike'))
|
|
623
|
+
return 'spike';
|
|
624
|
+
if (actionId.includes('soak'))
|
|
625
|
+
return 'soak';
|
|
626
|
+
if (actionId.includes('baseline'))
|
|
627
|
+
return 'baseline';
|
|
628
|
+
return 'load';
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Infer fleet operation from action ID
|
|
632
|
+
*/
|
|
633
|
+
inferFleetOperation(actionId) {
|
|
634
|
+
if (actionId.includes('spawn'))
|
|
635
|
+
return 'spawn';
|
|
636
|
+
if (actionId.includes('terminate'))
|
|
637
|
+
return 'terminate';
|
|
638
|
+
if (actionId.includes('scale'))
|
|
639
|
+
return 'scale';
|
|
640
|
+
if (actionId.includes('optimize'))
|
|
641
|
+
return 'optimize';
|
|
642
|
+
return 'manage';
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Build requirements for task execution
|
|
646
|
+
*/
|
|
647
|
+
buildRequirements(action) {
|
|
648
|
+
return {
|
|
649
|
+
timeout: action.estimatedDuration || 300000,
|
|
650
|
+
retries: 1,
|
|
651
|
+
expectedEffects: action.effects
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
// ============================================================================
|
|
655
|
+
// Phase 6: Live Execution with Real World State Updates
|
|
656
|
+
// ============================================================================
|
|
657
|
+
/**
|
|
658
|
+
* Parse agent output and update world state with real measurements
|
|
659
|
+
* This is called in live execution mode (not dry-run)
|
|
660
|
+
*/
|
|
661
|
+
updateWorldStateFromAgentOutput(action, output, stateBefore) {
|
|
662
|
+
const stateAfter = JSON.parse(JSON.stringify(stateBefore));
|
|
663
|
+
if (!output)
|
|
664
|
+
return stateAfter;
|
|
665
|
+
// Parse output based on action category
|
|
666
|
+
switch (action.category) {
|
|
667
|
+
case 'test':
|
|
668
|
+
this.parseTestOutput(output, stateAfter);
|
|
669
|
+
break;
|
|
670
|
+
case 'coverage':
|
|
671
|
+
this.parseCoverageOutput(output, stateAfter);
|
|
672
|
+
break;
|
|
673
|
+
case 'security':
|
|
674
|
+
this.parseSecurityOutput(output, stateAfter);
|
|
675
|
+
break;
|
|
676
|
+
case 'performance':
|
|
677
|
+
this.parsePerformanceOutput(output, stateAfter);
|
|
678
|
+
break;
|
|
679
|
+
case 'analysis':
|
|
680
|
+
this.parseAnalysisOutput(output, stateAfter);
|
|
681
|
+
break;
|
|
682
|
+
default:
|
|
683
|
+
// For other categories, use simulation as fallback
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
return stateAfter;
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Parse test execution output
|
|
690
|
+
*/
|
|
691
|
+
parseTestOutput(output, state) {
|
|
692
|
+
// Look for coverage data in output
|
|
693
|
+
if (output.coverage) {
|
|
694
|
+
state.coverage.line = output.coverage.line ?? output.coverage.linePercentage ?? state.coverage.line;
|
|
695
|
+
state.coverage.branch = output.coverage.branch ?? output.coverage.branchPercentage ?? state.coverage.branch;
|
|
696
|
+
state.coverage.function = output.coverage.function ?? output.coverage.functionPercentage ?? state.coverage.function;
|
|
697
|
+
state.coverage.measured = true;
|
|
698
|
+
}
|
|
699
|
+
// Look for test results
|
|
700
|
+
if (output.testResults || output.tests) {
|
|
701
|
+
const results = output.testResults || output.tests;
|
|
702
|
+
if (results.total && results.passed !== undefined) {
|
|
703
|
+
state.quality.testsPassing = (results.passed / results.total) * 100;
|
|
704
|
+
state.quality.testsMeasured = true;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
// Handle simple pass rate
|
|
708
|
+
if (typeof output.passRate === 'number') {
|
|
709
|
+
state.quality.testsPassing = output.passRate;
|
|
710
|
+
state.quality.testsMeasured = true;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Parse coverage analysis output
|
|
715
|
+
*/
|
|
716
|
+
parseCoverageOutput(output, state) {
|
|
717
|
+
if (output.coverage) {
|
|
718
|
+
state.coverage.line = output.coverage.line ?? state.coverage.line;
|
|
719
|
+
state.coverage.branch = output.coverage.branch ?? state.coverage.branch;
|
|
720
|
+
state.coverage.function = output.coverage.function ?? state.coverage.function;
|
|
721
|
+
state.coverage.measured = true;
|
|
722
|
+
}
|
|
723
|
+
// Handle gaps detected
|
|
724
|
+
if (output.gaps && Array.isArray(output.gaps)) {
|
|
725
|
+
// Store gap information for future actions
|
|
726
|
+
this.logger.debug('[PlanExecutor] Coverage gaps detected', { gapCount: output.gaps.length });
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Parse security scan output
|
|
731
|
+
*/
|
|
732
|
+
parseSecurityOutput(output, state) {
|
|
733
|
+
if (output.securityScore !== undefined) {
|
|
734
|
+
state.quality.securityScore = output.securityScore;
|
|
735
|
+
state.quality.securityMeasured = true;
|
|
736
|
+
}
|
|
737
|
+
// Calculate from vulnerabilities
|
|
738
|
+
if (output.vulnerabilities || output.summary) {
|
|
739
|
+
const summary = output.summary || output.vulnerabilities;
|
|
740
|
+
const critical = summary.critical ?? 0;
|
|
741
|
+
const high = summary.high ?? 0;
|
|
742
|
+
const medium = summary.medium ?? 0;
|
|
743
|
+
// Score: 100 - (critical*25 + high*10 + medium*5), clamped to 0-100
|
|
744
|
+
state.quality.securityScore = Math.max(0, Math.min(100, 100 - (critical * 25 + high * 10 + medium * 5)));
|
|
745
|
+
state.quality.securityMeasured = true;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Parse performance test output
|
|
750
|
+
*/
|
|
751
|
+
parsePerformanceOutput(output, state) {
|
|
752
|
+
if (output.performanceScore !== undefined) {
|
|
753
|
+
state.quality.performanceScore = output.performanceScore;
|
|
754
|
+
state.quality.performanceMeasured = true;
|
|
755
|
+
}
|
|
756
|
+
// Calculate from error rate and latency
|
|
757
|
+
if (output.errorRate !== undefined || output.p95 !== undefined) {
|
|
758
|
+
const errorPenalty = (output.errorRate ?? 0) * 100;
|
|
759
|
+
const latencyPenalty = output.p95 && output.targetP95
|
|
760
|
+
? Math.max(0, (output.p95 - output.targetP95) / output.targetP95 * 50)
|
|
761
|
+
: 0;
|
|
762
|
+
state.quality.performanceScore = Math.max(0, 100 - errorPenalty - latencyPenalty);
|
|
763
|
+
state.quality.performanceMeasured = true;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Parse code analysis output
|
|
768
|
+
*/
|
|
769
|
+
parseAnalysisOutput(output, state) {
|
|
770
|
+
if (output.technicalDebt !== undefined) {
|
|
771
|
+
state.quality.technicalDebt = output.technicalDebt;
|
|
772
|
+
}
|
|
773
|
+
if (output.complexity !== undefined) {
|
|
774
|
+
state.quality.complexityMeasured = true;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
// ============================================================================
|
|
778
|
+
// Phase 5: Learning Integration Helpers
|
|
779
|
+
// ============================================================================
|
|
780
|
+
/**
|
|
781
|
+
* Initialize world state from quality gate metrics
|
|
782
|
+
*/
|
|
783
|
+
initializeWorldStateFromMetrics(metrics, context) {
|
|
784
|
+
// Calculate pass rate from test results
|
|
785
|
+
const testResults = metrics.testResults;
|
|
786
|
+
const testPassRate = testResults && testResults.total
|
|
787
|
+
? ((testResults.passed ?? 0) / testResults.total) * 100
|
|
788
|
+
: 0;
|
|
789
|
+
// Calculate security score (inverse of vulnerability count)
|
|
790
|
+
const securitySummary = metrics.security?.summary;
|
|
791
|
+
const securityScore = securitySummary
|
|
792
|
+
? Math.max(0, 100 - ((securitySummary.critical ?? 0) * 25 + (securitySummary.high ?? 0) * 10))
|
|
793
|
+
: 100;
|
|
794
|
+
// Calculate performance score from error rate
|
|
795
|
+
const performanceScore = metrics.performance?.errorRate !== undefined
|
|
796
|
+
? Math.max(0, 100 - metrics.performance.errorRate * 100)
|
|
797
|
+
: 100;
|
|
798
|
+
// Map criticality to risk level
|
|
799
|
+
const riskLevelMap = {
|
|
800
|
+
low: 'low',
|
|
801
|
+
medium: 'medium',
|
|
802
|
+
high: 'high',
|
|
803
|
+
critical: 'critical'
|
|
804
|
+
};
|
|
805
|
+
const riskLevel = riskLevelMap[context.criticality || 'medium'] || 'medium';
|
|
806
|
+
// Estimate change size from changed files count
|
|
807
|
+
const changeSize = (context.changedFiles?.length ?? 0) <= 5 ? 'small' :
|
|
808
|
+
(context.changedFiles?.length ?? 0) <= 20 ? 'medium' : 'large';
|
|
809
|
+
this.currentWorldState = {
|
|
810
|
+
coverage: {
|
|
811
|
+
line: metrics.coverage?.linePercentage ?? metrics.coverage?.overallPercentage ?? 0,
|
|
812
|
+
branch: metrics.coverage?.branchPercentage ?? 0,
|
|
813
|
+
function: metrics.coverage?.functionPercentage ?? 0,
|
|
814
|
+
target: 80,
|
|
815
|
+
measured: metrics.coverage !== undefined
|
|
816
|
+
},
|
|
817
|
+
quality: {
|
|
818
|
+
testsPassing: testPassRate,
|
|
819
|
+
securityScore,
|
|
820
|
+
performanceScore,
|
|
821
|
+
technicalDebt: metrics.codeQuality?.technicalDebtDays ?? 0,
|
|
822
|
+
gateStatus: 'pending',
|
|
823
|
+
testsMeasured: testResults !== undefined,
|
|
824
|
+
securityMeasured: metrics.security !== undefined,
|
|
825
|
+
performanceMeasured: metrics.performance !== undefined
|
|
826
|
+
},
|
|
827
|
+
fleet: {
|
|
828
|
+
activeAgents: 0,
|
|
829
|
+
availableAgents: context.availableAgents || ['test-generator', 'coverage-analyzer', 'security-scanner'],
|
|
830
|
+
busyAgents: [],
|
|
831
|
+
agentTypes: {}
|
|
832
|
+
},
|
|
833
|
+
resources: {
|
|
834
|
+
timeRemaining: context.timeRemaining || 3600,
|
|
835
|
+
memoryAvailable: 4096,
|
|
836
|
+
parallelSlots: 4
|
|
837
|
+
},
|
|
838
|
+
context: {
|
|
839
|
+
environment: context.environment || 'development',
|
|
840
|
+
changeSize,
|
|
841
|
+
riskLevel,
|
|
842
|
+
previousFailures: context.previousFailures || 0
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Simulate action effects on world state
|
|
848
|
+
*/
|
|
849
|
+
simulateActionEffects(stateBefore, action, success) {
|
|
850
|
+
const stateAfter = JSON.parse(JSON.stringify(stateBefore));
|
|
851
|
+
if (!success)
|
|
852
|
+
return stateAfter;
|
|
853
|
+
// Parse effects and apply them
|
|
854
|
+
for (const effect of action.effects) {
|
|
855
|
+
// Effect format examples:
|
|
856
|
+
// "coverage.line >= 80" -> set coverage if action category is test/coverage
|
|
857
|
+
// "quality.testsPassing = true" -> set tests passing
|
|
858
|
+
// "quality.securityMeasured = true" -> mark security as measured
|
|
859
|
+
if (effect.includes('coverage') && action.category === 'test') {
|
|
860
|
+
// Simulate coverage improvement
|
|
861
|
+
stateAfter.coverage.line = Math.min(100, stateBefore.coverage.line + 10);
|
|
862
|
+
stateAfter.coverage.branch = Math.min(100, stateBefore.coverage.branch + 5);
|
|
863
|
+
stateAfter.coverage.measured = true;
|
|
864
|
+
}
|
|
865
|
+
if (effect.includes('testsPassing') || action.category === 'test') {
|
|
866
|
+
stateAfter.quality.testsPassing = Math.min(100, stateBefore.quality.testsPassing + 5);
|
|
867
|
+
stateAfter.quality.testsMeasured = true;
|
|
868
|
+
}
|
|
869
|
+
if (effect.includes('security') || action.category === 'security') {
|
|
870
|
+
stateAfter.quality.securityScore = Math.min(100, stateBefore.quality.securityScore + 10);
|
|
871
|
+
stateAfter.quality.securityMeasured = true;
|
|
872
|
+
}
|
|
873
|
+
if (effect.includes('performance') || action.category === 'performance') {
|
|
874
|
+
stateAfter.quality.performanceScore = Math.min(100, stateBefore.quality.performanceScore + 5);
|
|
875
|
+
stateAfter.quality.performanceMeasured = true;
|
|
876
|
+
}
|
|
877
|
+
if (effect.includes('gateStatus')) {
|
|
878
|
+
stateAfter.quality.gateStatus = 'passed';
|
|
879
|
+
stateAfter.quality.gateEvaluated = true;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
return stateAfter;
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Convert RemediationAction to GOAPAction for learning
|
|
886
|
+
*/
|
|
887
|
+
remediationActionToGOAPAction(action) {
|
|
888
|
+
return {
|
|
889
|
+
id: action.id,
|
|
890
|
+
name: action.name,
|
|
891
|
+
description: action.description,
|
|
892
|
+
agentType: action.agentType,
|
|
893
|
+
preconditions: {}, // Preconditions were already checked during planning
|
|
894
|
+
effects: {}, // Effects are string[] in RemediationAction
|
|
895
|
+
cost: 1.0 / (action.successRate || 1.0), // Higher cost for lower success rate
|
|
896
|
+
durationEstimate: action.estimatedDuration,
|
|
897
|
+
successRate: action.successRate,
|
|
898
|
+
category: action.category
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Convert RemediationPlan to GOAPPlan for learning
|
|
903
|
+
*/
|
|
904
|
+
remediationPlanToGOAPPlan(plan, initialState) {
|
|
905
|
+
return {
|
|
906
|
+
id: plan.planId,
|
|
907
|
+
actions: plan.actions.map(a => this.remediationActionToGOAPAction(a)),
|
|
908
|
+
totalCost: plan.totalCost,
|
|
909
|
+
estimatedDuration: plan.estimatedDuration,
|
|
910
|
+
goalConditions: {
|
|
911
|
+
'quality.gateStatus': { eq: 'passed' }
|
|
912
|
+
},
|
|
913
|
+
initialState,
|
|
914
|
+
status: 'executing'
|
|
342
915
|
};
|
|
343
916
|
}
|
|
344
917
|
/**
|