Files
whetstone_DSL/editor/src/MigrationPlanGenerator.h
Bill ad6c1eb1f4 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>
2026-02-16 19:22:15 -07:00

206 lines
6.9 KiB
C++

#pragma once
// Step 444: Migration Plan Generator — analyzes a multi-file project,
// identifies migration units, determines order, and annotates with effort/risk/routing.
#include <algorithm>
#include <string>
#include <vector>
enum class MigrationRouting { Deterministic, LLM, Human };
struct MigrationUnit {
std::string id;
std::string filePath;
std::string moduleName;
std::string sourceLanguage;
std::string targetLanguage;
int effort = 1; // 1=low, 2=medium, 3=high
int risk = 1; // 1=low, 2=medium, 3=high
MigrationRouting routing = MigrationRouting::LLM;
std::vector<std::string> dependsOn; // unit IDs this depends on
std::vector<std::string> exports; // public API symbols
std::vector<std::string> imports; // symbols imported from other units
int phase = 0; // migration phase (0 = first)
};
struct MigrationPlan {
std::string projectName;
std::string sourceLanguage;
std::string targetLanguage;
std::vector<MigrationUnit> units;
int phaseCount = 0;
bool hasUnit(const std::string& filePath) const {
for (const auto& u : units)
if (u.filePath == filePath) return true;
return false;
}
MigrationUnit unitByPath(const std::string& filePath) const {
for (const auto& u : units)
if (u.filePath == filePath) return u;
return {};
}
std::vector<MigrationUnit> unitsInPhase(int phase) const {
std::vector<MigrationUnit> out;
for (const auto& u : units)
if (u.phase == phase) out.push_back(u);
return out;
}
std::vector<MigrationUnit> orderedUnits() const {
auto sorted = units;
std::sort(sorted.begin(), sorted.end(),
[](const MigrationUnit& a, const MigrationUnit& b) {
if (a.phase != b.phase) return a.phase < b.phase;
return a.risk < b.risk; // lower risk first within phase
});
return sorted;
}
int countByRouting(MigrationRouting r) const {
int n = 0;
for (const auto& u : units)
if (u.routing == r) ++n;
return n;
}
};
struct FileInfo {
std::string filePath;
std::string source;
std::vector<std::string> exports; // public function/type names
std::vector<std::string> imports; // referenced external symbols
};
class MigrationPlanGenerator {
public:
static MigrationPlan generate(const std::string& projectName,
const std::vector<FileInfo>& files,
const std::string& sourceLanguage,
const std::string& targetLanguage) {
MigrationPlan plan;
plan.projectName = projectName;
plan.sourceLanguage = sourceLanguage;
plan.targetLanguage = targetLanguage;
// Create migration units from files
for (size_t i = 0; i < files.size(); ++i) {
MigrationUnit unit;
unit.id = "unit-" + std::to_string(i);
unit.filePath = files[i].filePath;
unit.moduleName = extractModuleName(files[i].filePath);
unit.sourceLanguage = sourceLanguage;
unit.targetLanguage = targetLanguage;
unit.exports = files[i].exports;
unit.imports = files[i].imports;
plan.units.push_back(std::move(unit));
}
// Resolve dependencies between units
resolveDependencies(plan);
// Assign phases (topological order — leaves first)
assignPhases(plan);
// Classify effort/risk/routing
for (auto& unit : plan.units)
classifyUnit(unit, files);
return plan;
}
private:
static std::string extractModuleName(const std::string& path) {
size_t slash = path.rfind('/');
size_t dot = path.rfind('.');
size_t start = (slash == std::string::npos) ? 0 : slash + 1;
size_t end = (dot == std::string::npos) ? path.size() : dot;
return path.substr(start, end - start);
}
static void resolveDependencies(MigrationPlan& plan) {
// For each unit, find which other units provide its imports
for (auto& unit : plan.units) {
for (const auto& imp : unit.imports) {
for (const auto& other : plan.units) {
if (other.id == unit.id) continue;
for (const auto& exp : other.exports) {
if (exp == imp) {
unit.dependsOn.push_back(other.id);
}
}
}
}
// Deduplicate
std::sort(unit.dependsOn.begin(), unit.dependsOn.end());
unit.dependsOn.erase(
std::unique(unit.dependsOn.begin(), unit.dependsOn.end()),
unit.dependsOn.end());
}
}
static void assignPhases(MigrationPlan& plan) {
// Leaf modules (no dependencies) go in phase 0
// Units depending on phase-N units go in phase N+1
int maxPhase = 0;
bool changed = true;
// Initialize all to phase 0
for (auto& u : plan.units) u.phase = 0;
while (changed) {
changed = false;
for (auto& unit : plan.units) {
for (const auto& depId : unit.dependsOn) {
for (const auto& dep : plan.units) {
if (dep.id == depId && dep.phase >= unit.phase) {
unit.phase = dep.phase + 1;
maxPhase = std::max(maxPhase, unit.phase);
changed = true;
}
}
}
}
}
plan.phaseCount = maxPhase + 1;
}
static void classifyUnit(MigrationUnit& unit,
const std::vector<FileInfo>& files) {
// Find corresponding file
const FileInfo* fi = nullptr;
for (const auto& f : files)
if (f.filePath == unit.filePath) { fi = &f; break; }
if (!fi) return;
int lineCount = countLines(fi->source);
int exportCount = (int)fi->exports.size();
// Effort based on size
if (lineCount > 200) unit.effort = 3;
else if (lineCount > 50) unit.effort = 2;
else unit.effort = 1;
// Risk based on API surface + dependencies
if (exportCount > 5 || !unit.dependsOn.empty()) unit.risk = 3;
else if (exportCount > 2) unit.risk = 2;
else unit.risk = 1;
// Routing
if (unit.effort == 1 && unit.risk == 1)
unit.routing = MigrationRouting::Deterministic;
else if (unit.effort == 3 || unit.risk == 3)
unit.routing = MigrationRouting::Human;
else
unit.routing = MigrationRouting::LLM;
}
static int countLines(const std::string& src) {
int n = 1;
for (char c : src) if (c == '\n') ++n;
return n;
}
};