agentic-qe 2.7.1 → 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 +179 -0
- package/README.md +2 -2
- package/dist/agents/CodeIntelligenceAgent.d.ts.map +1 -1
- package/dist/agents/CodeIntelligenceAgent.js +7 -5
- package/dist/agents/CodeIntelligenceAgent.js.map +1 -1
- package/dist/cli/commands/knowledge-graph.d.ts.map +1 -1
- package/dist/cli/commands/knowledge-graph.js +4 -2
- package/dist/cli/commands/knowledge-graph.js.map +1 -1
- package/dist/cli/commands/migrate/index.d.ts +14 -0
- package/dist/cli/commands/migrate/index.d.ts.map +1 -0
- package/dist/cli/commands/migrate/index.js +283 -0
- package/dist/cli/commands/migrate/index.js.map +1 -0
- package/dist/cli/formatters/KGOutputFormatter.d.ts.map +1 -1
- package/dist/cli/formatters/KGOutputFormatter.js +15 -6
- package/dist/cli/formatters/KGOutputFormatter.js.map +1 -1
- package/dist/cli/index.js +3 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init/database-init.d.ts.map +1 -1
- package/dist/cli/init/database-init.js +105 -0
- package/dist/cli/init/database-init.js.map +1 -1
- package/dist/code-intelligence/orchestrator/CodeIntelligenceOrchestrator.d.ts +9 -3
- package/dist/code-intelligence/orchestrator/CodeIntelligenceOrchestrator.d.ts.map +1 -1
- package/dist/code-intelligence/orchestrator/CodeIntelligenceOrchestrator.js +41 -3
- package/dist/code-intelligence/orchestrator/CodeIntelligenceOrchestrator.js.map +1 -1
- package/dist/code-intelligence/service/CodeIntelligenceService.js +1 -1
- package/dist/code-intelligence/service/CodeIntelligenceService.js.map +1 -1
- package/dist/core/memory/HNSWVectorMemory.js +1 -1
- package/dist/learning/QLearning.d.ts +110 -2
- package/dist/learning/QLearning.d.ts.map +1 -1
- package/dist/learning/QLearning.js +218 -1
- package/dist/learning/QLearning.js.map +1 -1
- package/dist/learning/metrics/LearningMetrics.d.ts +37 -0
- package/dist/learning/metrics/LearningMetrics.d.ts.map +1 -1
- package/dist/learning/metrics/LearningMetrics.js +73 -0
- package/dist/learning/metrics/LearningMetrics.js.map +1 -1
- package/dist/mcp/handlers/fleet-init.d.ts +10 -0
- package/dist/mcp/handlers/fleet-init.d.ts.map +1 -1
- package/dist/mcp/handlers/fleet-init.js +61 -0
- package/dist/mcp/handlers/fleet-init.js.map +1 -1
- package/dist/mcp/handlers/learning/learning-store-pattern.d.ts +13 -0
- package/dist/mcp/handlers/learning/learning-store-pattern.d.ts.map +1 -1
- package/dist/mcp/handlers/learning/learning-store-pattern.js +38 -0
- package/dist/mcp/handlers/learning/learning-store-pattern.js.map +1 -1
- package/dist/mcp/handlers/phase3/Phase3DomainTools.d.ts +89 -0
- package/dist/mcp/handlers/phase3/Phase3DomainTools.d.ts.map +1 -1
- package/dist/mcp/handlers/phase3/Phase3DomainTools.js +110 -1
- package/dist/mcp/handlers/phase3/Phase3DomainTools.js.map +1 -1
- package/dist/mcp/handlers/task-orchestrate.d.ts +27 -1
- package/dist/mcp/handlers/task-orchestrate.d.ts.map +1 -1
- package/dist/mcp/handlers/task-orchestrate.js +188 -8
- package/dist/mcp/handlers/task-orchestrate.js.map +1 -1
- package/dist/mcp/server-instructions.d.ts +1 -1
- package/dist/mcp/server-instructions.js +1 -1
- package/dist/mcp/server.d.ts +6 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +92 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/services/AgentRegistry.d.ts +13 -0
- package/dist/mcp/services/AgentRegistry.d.ts.map +1 -1
- package/dist/mcp/services/AgentRegistry.js +66 -0
- package/dist/mcp/services/AgentRegistry.js.map +1 -1
- package/dist/mcp/tools/qe/quality-gates/evaluate-quality-gate.d.ts +55 -0
- package/dist/mcp/tools/qe/quality-gates/evaluate-quality-gate.d.ts.map +1 -1
- package/dist/mcp/tools/qe/quality-gates/evaluate-quality-gate.js +233 -0
- package/dist/mcp/tools/qe/quality-gates/evaluate-quality-gate.js.map +1 -1
- package/dist/mcp/tools/qe/quality-gates/index.d.ts +5 -2
- package/dist/mcp/tools/qe/quality-gates/index.d.ts.map +1 -1
- package/dist/mcp/tools/qe/quality-gates/index.js +10 -1
- package/dist/mcp/tools/qe/quality-gates/index.js.map +1 -1
- package/dist/mcp/tools.d.ts +1 -0
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +156 -1
- package/dist/mcp/tools.js.map +1 -1
- package/dist/persistence/migrations/all-migrations.d.ts +18 -0
- package/dist/persistence/migrations/all-migrations.d.ts.map +1 -0
- package/dist/persistence/migrations/all-migrations.js +624 -0
- package/dist/persistence/migrations/all-migrations.js.map +1 -0
- package/dist/persistence/migrations/index.d.ts +110 -0
- package/dist/persistence/migrations/index.d.ts.map +1 -0
- package/dist/persistence/migrations/index.js +303 -0
- package/dist/persistence/migrations/index.js.map +1 -0
- package/dist/planning/GOAPPlanner.d.ts +170 -0
- package/dist/planning/GOAPPlanner.d.ts.map +1 -0
- package/dist/planning/GOAPPlanner.js +781 -0
- package/dist/planning/GOAPPlanner.js.map +1 -0
- 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/WorldStateBuilder.d.ts +150 -0
- package/dist/planning/WorldStateBuilder.d.ts.map +1 -0
- package/dist/planning/WorldStateBuilder.js +267 -0
- package/dist/planning/WorldStateBuilder.js.map +1 -0
- package/dist/planning/actions/fleet-actions.d.ts +78 -0
- package/dist/planning/actions/fleet-actions.d.ts.map +1 -0
- package/dist/planning/actions/fleet-actions.js +329 -0
- package/dist/planning/actions/fleet-actions.js.map +1 -0
- package/dist/planning/actions/index.d.ts +61 -0
- package/dist/planning/actions/index.d.ts.map +1 -0
- package/dist/planning/actions/index.js +159 -0
- package/dist/planning/actions/index.js.map +1 -0
- package/dist/planning/actions/orchestration-actions.d.ts +61 -0
- package/dist/planning/actions/orchestration-actions.d.ts.map +1 -0
- package/dist/planning/actions/orchestration-actions.js +395 -0
- package/dist/planning/actions/orchestration-actions.js.map +1 -0
- package/dist/planning/actions/quality-gate-actions.d.ts +160 -0
- package/dist/planning/actions/quality-gate-actions.d.ts.map +1 -0
- package/dist/planning/actions/quality-gate-actions.js +639 -0
- package/dist/planning/actions/quality-gate-actions.js.map +1 -0
- package/dist/planning/actions/test-strategy-actions.d.ts +70 -0
- package/dist/planning/actions/test-strategy-actions.d.ts.map +1 -0
- package/dist/planning/actions/test-strategy-actions.js +278 -0
- package/dist/planning/actions/test-strategy-actions.js.map +1 -0
- package/dist/planning/execution/PlanExecutor.d.ts +223 -0
- package/dist/planning/execution/PlanExecutor.d.ts.map +1 -0
- package/dist/planning/execution/PlanExecutor.js +978 -0
- package/dist/planning/execution/PlanExecutor.js.map +1 -0
- package/dist/planning/execution/index.d.ts +12 -0
- package/dist/planning/execution/index.d.ts.map +1 -0
- package/dist/planning/execution/index.js +18 -0
- package/dist/planning/execution/index.js.map +1 -0
- package/dist/planning/goals/TaskWorkflowGoals.d.ts +67 -0
- package/dist/planning/goals/TaskWorkflowGoals.d.ts.map +1 -0
- package/dist/planning/goals/TaskWorkflowGoals.js +208 -0
- package/dist/planning/goals/TaskWorkflowGoals.js.map +1 -0
- package/dist/planning/goals/index.d.ts +10 -0
- package/dist/planning/goals/index.d.ts.map +1 -0
- package/dist/planning/goals/index.js +19 -0
- package/dist/planning/goals/index.js.map +1 -0
- package/dist/planning/index.d.ts +36 -0
- package/dist/planning/index.d.ts.map +1 -0
- package/dist/planning/index.js +187 -0
- package/dist/planning/index.js.map +1 -0
- package/dist/planning/integration/GOAPQualityGateIntegration.d.ts +235 -0
- package/dist/planning/integration/GOAPQualityGateIntegration.d.ts.map +1 -0
- package/dist/planning/integration/GOAPQualityGateIntegration.js +589 -0
- package/dist/planning/integration/GOAPQualityGateIntegration.js.map +1 -0
- package/dist/planning/integration/GOAPTaskOrchestration.d.ts +165 -0
- package/dist/planning/integration/GOAPTaskOrchestration.d.ts.map +1 -0
- package/dist/planning/integration/GOAPTaskOrchestration.js +490 -0
- package/dist/planning/integration/GOAPTaskOrchestration.js.map +1 -0
- package/dist/planning/integration/index.d.ts +14 -0
- package/dist/planning/integration/index.d.ts.map +1 -0
- package/dist/planning/integration/index.js +23 -0
- package/dist/planning/integration/index.js.map +1 -0
- package/dist/planning/types.d.ts +266 -0
- package/dist/planning/types.d.ts.map +1 -0
- package/dist/planning/types.js +63 -0
- package/dist/planning/types.js.map +1 -0
- package/dist/utils/Database.d.ts.map +1 -1
- package/dist/utils/Database.js +81 -2
- package/dist/utils/Database.js.map +1 -1
- package/package.json +6 -3
|
@@ -0,0 +1,978 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* GOAP Plan Executor
|
|
4
|
+
*
|
|
5
|
+
* Executes remediation plans generated by GOAPQualityGateIntegration:
|
|
6
|
+
* - Spawns agents for each action
|
|
7
|
+
* - Executes actions in sequence with dependency ordering
|
|
8
|
+
* - Records outcomes for learning via PlanLearning integration
|
|
9
|
+
* - Handles failures with automatic replanning
|
|
10
|
+
* - Phase 6: Live agent execution with real world state updates
|
|
11
|
+
*
|
|
12
|
+
* @module planning/execution/PlanExecutor
|
|
13
|
+
* @version 1.2.0 - Phase 6: Live Agent Execution
|
|
14
|
+
*/
|
|
15
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
18
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
19
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
20
|
+
}
|
|
21
|
+
Object.defineProperty(o, k2, desc);
|
|
22
|
+
}) : (function(o, m, k, k2) {
|
|
23
|
+
if (k2 === undefined) k2 = k;
|
|
24
|
+
o[k2] = m[k];
|
|
25
|
+
}));
|
|
26
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
27
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
28
|
+
}) : function(o, v) {
|
|
29
|
+
o["default"] = v;
|
|
30
|
+
});
|
|
31
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
32
|
+
var ownKeys = function(o) {
|
|
33
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
34
|
+
var ar = [];
|
|
35
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
36
|
+
return ar;
|
|
37
|
+
};
|
|
38
|
+
return ownKeys(o);
|
|
39
|
+
};
|
|
40
|
+
return function (mod) {
|
|
41
|
+
if (mod && mod.__esModule) return mod;
|
|
42
|
+
var result = {};
|
|
43
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
44
|
+
__setModuleDefault(result, mod);
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
47
|
+
})();
|
|
48
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
|
+
exports.PlanExecutor = void 0;
|
|
50
|
+
exports.createPlanExecutor = createPlanExecutor;
|
|
51
|
+
exports.executeQualityGateRemediation = executeQualityGateRemediation;
|
|
52
|
+
const GOAPQualityGateIntegration_1 = require("../integration/GOAPQualityGateIntegration");
|
|
53
|
+
const Logger_1 = require("../../utils/Logger");
|
|
54
|
+
const PlanLearning_1 = require("../PlanLearning");
|
|
55
|
+
const types_1 = require("../types");
|
|
56
|
+
// Lazy import to avoid circular dependencies and reduce memory in tests
|
|
57
|
+
let AgentRegistryModule = null;
|
|
58
|
+
async function getAgentRegistryModule() {
|
|
59
|
+
if (!AgentRegistryModule) {
|
|
60
|
+
AgentRegistryModule = await Promise.resolve().then(() => __importStar(require('../../mcp/services/AgentRegistry')));
|
|
61
|
+
}
|
|
62
|
+
return AgentRegistryModule;
|
|
63
|
+
}
|
|
64
|
+
const DEFAULT_CONFIG = {
|
|
65
|
+
maxRetries: 1,
|
|
66
|
+
actionTimeoutMs: 300000,
|
|
67
|
+
continueOnFailure: false,
|
|
68
|
+
maxReplanAttempts: 2,
|
|
69
|
+
dryRun: false
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* GOAP Plan Executor
|
|
73
|
+
*
|
|
74
|
+
* Bridges plan generation with actual agent execution.
|
|
75
|
+
* Implements the OODA (Observe-Orient-Decide-Act) loop for plan execution.
|
|
76
|
+
* Integrates with PlanLearning for continuous improvement.
|
|
77
|
+
*/
|
|
78
|
+
class PlanExecutor {
|
|
79
|
+
constructor(db, integration, config = {}) {
|
|
80
|
+
this.registry = null; // AgentRegistry - lazily initialized
|
|
81
|
+
this.ownsRegistry = false; // Track if we created the registry
|
|
82
|
+
this.db = db;
|
|
83
|
+
this.integration = integration;
|
|
84
|
+
this.planLearning = new PlanLearning_1.PlanLearning(db);
|
|
85
|
+
this.logger = Logger_1.Logger.getInstance();
|
|
86
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
87
|
+
this.currentWorldState = { ...types_1.DEFAULT_WORLD_STATE };
|
|
88
|
+
// Registry is lazily initialized only when needed (not in dry-run mode)
|
|
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
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Initialize registry only when needed for actual execution
|
|
118
|
+
*/
|
|
119
|
+
async ensureRegistry() {
|
|
120
|
+
if (!this.registry) {
|
|
121
|
+
const { getAgentRegistry } = await getAgentRegistryModule();
|
|
122
|
+
this.registry = getAgentRegistry();
|
|
123
|
+
this.ownsRegistry = true;
|
|
124
|
+
}
|
|
125
|
+
return this.registry;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Cleanup resources - call this when done with the executor
|
|
129
|
+
*/
|
|
130
|
+
async cleanup() {
|
|
131
|
+
// Don't cleanup singleton registry - it's shared
|
|
132
|
+
this.registry = null;
|
|
133
|
+
this.ownsRegistry = false;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Execute a remediation plan
|
|
137
|
+
*
|
|
138
|
+
* @param plan - The remediation plan to execute
|
|
139
|
+
* @param context - Quality gate context for replanning
|
|
140
|
+
* @param metrics - Current metrics for replanning
|
|
141
|
+
*/
|
|
142
|
+
async executePlan(plan, context, metrics) {
|
|
143
|
+
const startTime = Date.now();
|
|
144
|
+
const result = {
|
|
145
|
+
planId: plan.planId,
|
|
146
|
+
success: false,
|
|
147
|
+
totalDurationMs: 0,
|
|
148
|
+
actionsExecuted: 0,
|
|
149
|
+
actionsSucceeded: 0,
|
|
150
|
+
actionsFailed: 0,
|
|
151
|
+
actionResults: [],
|
|
152
|
+
replanned: false
|
|
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();
|
|
159
|
+
this.logger.info('[PlanExecutor] Starting plan execution', {
|
|
160
|
+
planId: plan.planId,
|
|
161
|
+
actionCount: plan.actions.length,
|
|
162
|
+
dryRun: this.config.dryRun
|
|
163
|
+
});
|
|
164
|
+
// Update plan status to 'executing'
|
|
165
|
+
await this.updatePlanStatus(plan.planId, 'executing');
|
|
166
|
+
let replanAttempts = 0;
|
|
167
|
+
let currentPlan = plan;
|
|
168
|
+
while (replanAttempts <= this.config.maxReplanAttempts) {
|
|
169
|
+
try {
|
|
170
|
+
// Execute each action in sequence
|
|
171
|
+
for (const action of currentPlan.actions) {
|
|
172
|
+
// Capture state before action
|
|
173
|
+
const stateBefore = this.getWorldState();
|
|
174
|
+
const actionResult = await this.executeAction(action, context);
|
|
175
|
+
result.actionResults.push(actionResult);
|
|
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);
|
|
204
|
+
if (actionResult.success) {
|
|
205
|
+
result.actionsSucceeded++;
|
|
206
|
+
// Record success for learning (legacy + new)
|
|
207
|
+
await this.integration.recordActionOutcome(action.id, true);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
result.actionsFailed++;
|
|
211
|
+
// Record failure for learning (legacy + new)
|
|
212
|
+
await this.integration.recordActionOutcome(action.id, false);
|
|
213
|
+
if (!this.config.continueOnFailure) {
|
|
214
|
+
this.logger.warn('[PlanExecutor] Action failed, attempting replan', {
|
|
215
|
+
actionId: action.id,
|
|
216
|
+
actionName: action.name,
|
|
217
|
+
error: actionResult.error
|
|
218
|
+
});
|
|
219
|
+
// Try to replan
|
|
220
|
+
replanAttempts++;
|
|
221
|
+
if (replanAttempts > this.config.maxReplanAttempts) {
|
|
222
|
+
result.error = `Max replan attempts (${this.config.maxReplanAttempts}) exceeded. Last error: ${actionResult.error}`;
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
// Try alternative path first
|
|
226
|
+
if (currentPlan.alternativePaths.length > 0) {
|
|
227
|
+
const altPath = currentPlan.alternativePaths[0];
|
|
228
|
+
this.logger.info('[PlanExecutor] Using alternative path', {
|
|
229
|
+
alternativePlanId: altPath.planId,
|
|
230
|
+
difference: altPath.differenceFromPrimary
|
|
231
|
+
});
|
|
232
|
+
// Generate new plan from alternative
|
|
233
|
+
const newPlan = await this.integration.generateRemediationPlan(metrics, context);
|
|
234
|
+
if (newPlan) {
|
|
235
|
+
currentPlan = newPlan;
|
|
236
|
+
result.replanned = true;
|
|
237
|
+
result.alternativePlanUsed = altPath.planId;
|
|
238
|
+
continue; // Restart with new plan
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// Full replan if no alternatives
|
|
242
|
+
const newPlan = await this.integration.generateRemediationPlan(metrics, context);
|
|
243
|
+
if (newPlan) {
|
|
244
|
+
currentPlan = newPlan;
|
|
245
|
+
result.replanned = true;
|
|
246
|
+
this.logger.info('[PlanExecutor] Replanned with new strategy', {
|
|
247
|
+
newPlanId: newPlan.planId
|
|
248
|
+
});
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
result.error = `Replanning failed after action ${action.id}`;
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// If we get here without breaking, we succeeded
|
|
257
|
+
if (!result.error) {
|
|
258
|
+
result.success = result.actionsFailed === 0;
|
|
259
|
+
}
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
result.error = error instanceof Error ? error.message : String(error);
|
|
264
|
+
this.logger.error('[PlanExecutor] Plan execution failed', { error: result.error });
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
result.totalDurationMs = Date.now() - startTime;
|
|
269
|
+
// Update plan status
|
|
270
|
+
await this.updatePlanStatus(plan.planId, result.success ? 'completed' : 'failed', result.success, result.error);
|
|
271
|
+
// Mark plan as completed via integration
|
|
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
|
+
}
|
|
311
|
+
this.logger.info('[PlanExecutor] Plan execution completed', {
|
|
312
|
+
planId: plan.planId,
|
|
313
|
+
success: result.success,
|
|
314
|
+
duration: `${result.totalDurationMs}ms`,
|
|
315
|
+
actionsExecuted: result.actionsExecuted,
|
|
316
|
+
actionsSucceeded: result.actionsSucceeded,
|
|
317
|
+
actionsFailed: result.actionsFailed,
|
|
318
|
+
replanned: result.replanned
|
|
319
|
+
});
|
|
320
|
+
return result;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Execute a single remediation action
|
|
324
|
+
*/
|
|
325
|
+
async executeAction(action, context) {
|
|
326
|
+
const startTime = Date.now();
|
|
327
|
+
const result = {
|
|
328
|
+
actionId: action.id,
|
|
329
|
+
actionName: action.name,
|
|
330
|
+
success: false,
|
|
331
|
+
durationMs: 0
|
|
332
|
+
};
|
|
333
|
+
if (this.config.dryRun) {
|
|
334
|
+
this.logger.info('[PlanExecutor] DRY RUN - Would execute action', {
|
|
335
|
+
actionId: action.id,
|
|
336
|
+
actionName: action.name,
|
|
337
|
+
agentType: action.agentType
|
|
338
|
+
});
|
|
339
|
+
result.success = true;
|
|
340
|
+
result.durationMs = Date.now() - startTime;
|
|
341
|
+
return result;
|
|
342
|
+
}
|
|
343
|
+
this.logger.info('[PlanExecutor] Executing action', {
|
|
344
|
+
actionId: action.id,
|
|
345
|
+
actionName: action.name,
|
|
346
|
+
agentType: action.agentType
|
|
347
|
+
});
|
|
348
|
+
try {
|
|
349
|
+
// Get registry only when actually executing (not in dry-run)
|
|
350
|
+
const registry = await this.ensureRegistry();
|
|
351
|
+
// Map GOAP agent type to MCP agent type
|
|
352
|
+
const mcpAgentType = this.mapAgentType(action.agentType);
|
|
353
|
+
// Spawn or get an agent of the required type
|
|
354
|
+
const { id: agentId } = await registry.spawnAgent(mcpAgentType, {
|
|
355
|
+
name: `${mcpAgentType}-${action.id}`,
|
|
356
|
+
description: `Spawned for action: ${action.name}`,
|
|
357
|
+
capabilities: this.getCapabilitiesForAction(action)
|
|
358
|
+
});
|
|
359
|
+
result.agentId = agentId;
|
|
360
|
+
// Create task from action
|
|
361
|
+
const task = this.createTaskFromAction(action, context);
|
|
362
|
+
// Execute with timeout
|
|
363
|
+
const executePromise = registry.executeTask(agentId, task);
|
|
364
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Action timeout')), this.config.actionTimeoutMs));
|
|
365
|
+
const output = await Promise.race([executePromise, timeoutPromise]);
|
|
366
|
+
result.success = true;
|
|
367
|
+
result.output = output;
|
|
368
|
+
// Terminate the agent after use (cleanup)
|
|
369
|
+
await registry.terminateAgent(agentId).catch((err) => this.logger.warn('[PlanExecutor] Failed to terminate agent', { agentId, error: err }));
|
|
370
|
+
}
|
|
371
|
+
catch (error) {
|
|
372
|
+
result.success = false;
|
|
373
|
+
result.error = error instanceof Error ? error.message : String(error);
|
|
374
|
+
this.logger.error('[PlanExecutor] Action execution failed', {
|
|
375
|
+
actionId: action.id,
|
|
376
|
+
error: result.error
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
result.durationMs = Date.now() - startTime;
|
|
380
|
+
return result;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
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.
|
|
387
|
+
*/
|
|
388
|
+
mapAgentType(goapAgentType) {
|
|
389
|
+
const mapping = {
|
|
390
|
+
// Core Testing Agents
|
|
391
|
+
'qe-test-generator': 'test-generator',
|
|
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
|
|
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
|
|
400
|
+
'qe-quality-gate': 'quality-gate',
|
|
401
|
+
'qe-quality-analyzer': 'code-analyzer',
|
|
402
|
+
// Security Agents
|
|
403
|
+
'qe-security-scanner': 'security-scanner',
|
|
404
|
+
// Performance Agents
|
|
405
|
+
'qe-performance-tester': 'performance-tester',
|
|
406
|
+
// Specialized Testing Agents
|
|
407
|
+
'qe-flaky-test-hunter': 'flaky-test-detector',
|
|
408
|
+
'qe-regression-risk-analyzer': 'regression-analyzer',
|
|
409
|
+
'qe-chaos-engineer': 'chaos-engineer',
|
|
410
|
+
'qe-visual-tester': 'visual-tester',
|
|
411
|
+
// Strategic Planning Agents
|
|
412
|
+
'qe-requirements-validator': 'requirements-validator',
|
|
413
|
+
'qe-deployment-readiness': 'deployment-validator',
|
|
414
|
+
'qe-production-intelligence': 'production-analyzer',
|
|
415
|
+
// Fleet & Orchestration Agents
|
|
416
|
+
'qe-fleet-commander': 'fleet-commander',
|
|
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)
|
|
425
|
+
'test-generator': 'test-generator',
|
|
426
|
+
'test-executor': 'test-executor',
|
|
427
|
+
'coverage-analyzer': 'coverage-analyzer',
|
|
428
|
+
'quality-gate': 'quality-gate',
|
|
429
|
+
'performance-tester': 'performance-tester',
|
|
430
|
+
'security-scanner': 'security-scanner',
|
|
431
|
+
'fleet-commander': 'fleet-commander',
|
|
432
|
+
'chaos-engineer': 'chaos-engineer'
|
|
433
|
+
};
|
|
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';
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Get capabilities for an action
|
|
442
|
+
*/
|
|
443
|
+
getCapabilitiesForAction(action) {
|
|
444
|
+
const categoryCapabilities = {
|
|
445
|
+
test: ['test-execution', 'test-validation'],
|
|
446
|
+
coverage: ['coverage-analysis', 'gap-detection'],
|
|
447
|
+
security: ['vulnerability-scanning', 'security-testing'],
|
|
448
|
+
performance: ['load-testing', 'performance-analysis'],
|
|
449
|
+
analysis: ['code-analysis', 'impact-analysis'],
|
|
450
|
+
process: ['decision-making', 'workflow-management'],
|
|
451
|
+
fleet: ['agent-coordination', 'resource-management']
|
|
452
|
+
};
|
|
453
|
+
return categoryCapabilities[action.category] || ['generic-execution'];
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
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.
|
|
460
|
+
*/
|
|
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);
|
|
466
|
+
return {
|
|
467
|
+
id: taskId,
|
|
468
|
+
type: this.mapCategoryToTaskType(action.category),
|
|
469
|
+
taskType: this.mapCategoryToTaskType(action.category),
|
|
470
|
+
payload: {
|
|
471
|
+
...categoryPayload,
|
|
472
|
+
actionId: action.id,
|
|
473
|
+
actionName: action.name,
|
|
474
|
+
description: action.description,
|
|
475
|
+
effects: action.effects,
|
|
476
|
+
projectId: context.projectId,
|
|
477
|
+
buildId: context.buildId,
|
|
478
|
+
environment: context.environment
|
|
479
|
+
},
|
|
480
|
+
priority,
|
|
481
|
+
description: action.description || action.name,
|
|
482
|
+
context: {
|
|
483
|
+
source: 'goap-remediation',
|
|
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);
|
|
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'
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Update plan status in database
|
|
919
|
+
*/
|
|
920
|
+
async updatePlanStatus(planId, status, success, failureReason) {
|
|
921
|
+
try {
|
|
922
|
+
if (status === 'executing') {
|
|
923
|
+
this.db.prepare(`
|
|
924
|
+
UPDATE goap_plans SET status = ?, started_at = CURRENT_TIMESTAMP WHERE id = ?
|
|
925
|
+
`).run(status, planId);
|
|
926
|
+
}
|
|
927
|
+
else {
|
|
928
|
+
this.db.prepare(`
|
|
929
|
+
UPDATE goap_plans
|
|
930
|
+
SET status = ?, success = ?, failure_reason = ?, completed_at = CURRENT_TIMESTAMP
|
|
931
|
+
WHERE id = ?
|
|
932
|
+
`).run(status, success ? 1 : 0, failureReason || null, planId);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
catch (error) {
|
|
936
|
+
this.logger.warn('[PlanExecutor] Failed to update plan status', { planId, error });
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
exports.PlanExecutor = PlanExecutor;
|
|
941
|
+
/**
|
|
942
|
+
* Factory function to create a PlanExecutor
|
|
943
|
+
*/
|
|
944
|
+
function createPlanExecutor(dbPath, config = {}) {
|
|
945
|
+
const Database = require('better-sqlite3');
|
|
946
|
+
const db = new Database(dbPath);
|
|
947
|
+
const integration = new GOAPQualityGateIntegration_1.GOAPQualityGateIntegration(db);
|
|
948
|
+
return {
|
|
949
|
+
executor: new PlanExecutor(db, integration, config),
|
|
950
|
+
integration
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Execute a quality gate remediation with GOAP planning
|
|
955
|
+
*
|
|
956
|
+
* High-level function that:
|
|
957
|
+
* 1. Generates a remediation plan
|
|
958
|
+
* 2. Executes the plan
|
|
959
|
+
* 3. Records outcomes for learning
|
|
960
|
+
*/
|
|
961
|
+
async function executeQualityGateRemediation(metrics, context, dbPath, targetGoal, config = {}) {
|
|
962
|
+
const { executor, integration } = createPlanExecutor(dbPath, config);
|
|
963
|
+
try {
|
|
964
|
+
await integration.initialize();
|
|
965
|
+
// Generate remediation plan
|
|
966
|
+
const plan = await integration.generateRemediationPlan(metrics, context, targetGoal);
|
|
967
|
+
if (!plan) {
|
|
968
|
+
Logger_1.Logger.getInstance().info('[executeQualityGateRemediation] No remediation needed - goal already satisfied');
|
|
969
|
+
return null;
|
|
970
|
+
}
|
|
971
|
+
// Execute the plan
|
|
972
|
+
return await executor.executePlan(plan, context, metrics);
|
|
973
|
+
}
|
|
974
|
+
finally {
|
|
975
|
+
integration.close();
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
//# sourceMappingURL=PlanExecutor.js.map
|