Complete Sprint 20: Legacy Code Ingestion (steps 439-448, 124/124 tests)
Phase 20a delivers legacy analysis, safety audit with CWE mapping, modernization suggestions with effort routing, and RPC/MCP tools. Phase 20b adds multi-file migration planning, API boundary preservation, target-language test generation, and progressive migration execution. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
199
editor/src/MigrationExecutor.h
Normal file
199
editor/src/MigrationExecutor.h
Normal file
@@ -0,0 +1,199 @@
|
||||
#pragma once
|
||||
|
||||
// Step 447: Migration Execution Integration — hooks migration plans into the
|
||||
// orchestration engine with cross-unit dependencies, rollback annotations,
|
||||
// and progressive (partial) migration support.
|
||||
|
||||
#include "MigrationPlanGenerator.h"
|
||||
#include "APIBoundaryPreserver.h"
|
||||
#include "MigrationTestGenerator.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
enum class MigrationStatus { Pending, InProgress, Completed, Failed, Skipped };
|
||||
|
||||
struct MigrationWorkItem {
|
||||
std::string id;
|
||||
std::string unitId;
|
||||
std::string filePath;
|
||||
std::string title;
|
||||
MigrationRouting routing = MigrationRouting::LLM;
|
||||
MigrationStatus status = MigrationStatus::Pending;
|
||||
std::vector<std::string> dependsOn;
|
||||
bool requiresHumanReview = false;
|
||||
std::string rollbackAnnotation;
|
||||
};
|
||||
|
||||
struct PartialMigrationState {
|
||||
std::string unitId;
|
||||
std::string filePath;
|
||||
bool migrated = false;
|
||||
bool hasFfiBoundary = false;
|
||||
std::string ffiAnnotation;
|
||||
};
|
||||
|
||||
struct MigrationExecution {
|
||||
std::string projectName;
|
||||
std::string sourceLanguage;
|
||||
std::string targetLanguage;
|
||||
std::vector<MigrationWorkItem> workItems;
|
||||
std::vector<PartialMigrationState> partialState;
|
||||
int currentPhase = 0;
|
||||
|
||||
int countByStatus(MigrationStatus s) const {
|
||||
int n = 0;
|
||||
for (const auto& w : workItems)
|
||||
if (w.status == s) ++n;
|
||||
return n;
|
||||
}
|
||||
|
||||
bool isUnitMigrated(const std::string& unitId) const {
|
||||
for (const auto& p : partialState)
|
||||
if (p.unitId == unitId) return p.migrated;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<MigrationWorkItem> readyItems() const {
|
||||
std::vector<MigrationWorkItem> out;
|
||||
for (const auto& w : workItems) {
|
||||
if (w.status != MigrationStatus::Pending) continue;
|
||||
bool blocked = false;
|
||||
for (const auto& dep : w.dependsOn) {
|
||||
for (const auto& other : workItems) {
|
||||
if (other.id == dep && other.status != MigrationStatus::Completed)
|
||||
blocked = true;
|
||||
}
|
||||
}
|
||||
if (!blocked) out.push_back(w);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
int countFfiBoundaries() const {
|
||||
int n = 0;
|
||||
for (const auto& p : partialState)
|
||||
if (p.hasFfiBoundary) ++n;
|
||||
return n;
|
||||
}
|
||||
};
|
||||
|
||||
class MigrationExecutor {
|
||||
public:
|
||||
static MigrationExecution createExecution(const MigrationPlan& plan,
|
||||
const std::vector<FileInfo>& files) {
|
||||
MigrationExecution exec;
|
||||
exec.projectName = plan.projectName;
|
||||
exec.sourceLanguage = plan.sourceLanguage;
|
||||
exec.targetLanguage = plan.targetLanguage;
|
||||
exec.currentPhase = 0;
|
||||
|
||||
int idx = 0;
|
||||
auto ordered = plan.orderedUnits();
|
||||
for (const auto& unit : ordered) {
|
||||
MigrationWorkItem item;
|
||||
item.id = "mig-" + std::to_string(idx++);
|
||||
item.unitId = unit.id;
|
||||
item.filePath = unit.filePath;
|
||||
item.title = "Migrate " + unit.moduleName + " (" +
|
||||
unit.sourceLanguage + " -> " + unit.targetLanguage + ")";
|
||||
item.routing = unit.routing;
|
||||
item.requiresHumanReview = (unit.risk >= 3 || unit.routing == MigrationRouting::Human);
|
||||
item.rollbackAnnotation = "@Rollback(revert_to=\"" + unit.filePath + "\")";
|
||||
|
||||
// Map unit dependencies to work item dependencies
|
||||
for (const auto& depId : unit.dependsOn) {
|
||||
for (const auto& wi : exec.workItems) {
|
||||
if (wi.unitId == depId)
|
||||
item.dependsOn.push_back(wi.id);
|
||||
}
|
||||
}
|
||||
|
||||
exec.workItems.push_back(std::move(item));
|
||||
}
|
||||
|
||||
// Initialize partial migration state
|
||||
for (const auto& unit : plan.units) {
|
||||
PartialMigrationState ps;
|
||||
ps.unitId = unit.id;
|
||||
ps.filePath = unit.filePath;
|
||||
ps.migrated = false;
|
||||
ps.hasFfiBoundary = false;
|
||||
exec.partialState.push_back(std::move(ps));
|
||||
}
|
||||
|
||||
return exec;
|
||||
}
|
||||
|
||||
// Mark a unit as completed and update FFI boundaries
|
||||
static void completeUnit(MigrationExecution& exec, const std::string& workItemId) {
|
||||
for (auto& w : exec.workItems) {
|
||||
if (w.id == workItemId) {
|
||||
w.status = MigrationStatus::Completed;
|
||||
// Update partial state
|
||||
for (auto& ps : exec.partialState) {
|
||||
if (ps.unitId == w.unitId) {
|
||||
ps.migrated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Update FFI boundaries for mixed-language state
|
||||
updateFfiBoundaries(exec);
|
||||
}
|
||||
|
||||
// Mark a unit as failed (requires human review)
|
||||
static void failUnit(MigrationExecution& exec, const std::string& workItemId) {
|
||||
for (auto& w : exec.workItems) {
|
||||
if (w.id == workItemId) {
|
||||
w.status = MigrationStatus::Failed;
|
||||
w.requiresHumanReview = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if execution is in a partial (mixed-language) state
|
||||
static bool isPartialMigration(const MigrationExecution& exec) {
|
||||
bool hasMigrated = false, hasUnmigrated = false;
|
||||
for (const auto& ps : exec.partialState) {
|
||||
if (ps.migrated) hasMigrated = true;
|
||||
else hasUnmigrated = true;
|
||||
}
|
||||
return hasMigrated && hasUnmigrated;
|
||||
}
|
||||
|
||||
private:
|
||||
static void updateFfiBoundaries(MigrationExecution& exec) {
|
||||
for (auto& ps : exec.partialState) {
|
||||
if (!ps.migrated) {
|
||||
// Check if any migrated unit depends on this unmigrated one
|
||||
// or this unmigrated one depends on a migrated one
|
||||
bool needsFfi = false;
|
||||
for (const auto& other : exec.partialState) {
|
||||
if (other.unitId == ps.unitId) continue;
|
||||
if (other.migrated != ps.migrated) {
|
||||
// Check if they have a dependency relationship
|
||||
for (const auto& w : exec.workItems) {
|
||||
if (w.unitId == ps.unitId || w.unitId == other.unitId) {
|
||||
for (const auto& dep : w.dependsOn) {
|
||||
for (const auto& w2 : exec.workItems) {
|
||||
if (w2.id == dep &&
|
||||
(w2.unitId == ps.unitId || w2.unitId == other.unitId))
|
||||
needsFfi = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ps.hasFfiBoundary = needsFfi;
|
||||
if (needsFfi)
|
||||
ps.ffiAnnotation = "@FFI(boundary=" + exec.sourceLanguage +
|
||||
"/" + exec.targetLanguage + ")";
|
||||
} else {
|
||||
ps.hasFfiBoundary = false;
|
||||
ps.ffiAnnotation = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user