Add Sprints 46-59: governance, porting foundation, and language graduation prep (Steps 689-828)
Sprints 46-58 implement the cross-language porting foundation: language-to-IR adapters, equivalence checking, gate validation, legacy ingestion, managed/dynamic families, low-level/logic-actor semantics, debug workflow tooling, AST-native family tools, Rust/CPP raising tools, system-level orchestration, query family, and porting gates. Sprint 59 adds the governance layer (policy packs, review boards, waiver packets, ambiguity triage, decision ledger) with the whetstone_review_porting_decision MCP tool. Also includes: sprint plans 46-130, MCP taskitem pipeline scripts, CLAUDE.md, docs, and full test matrix (steps 689-828). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
26
editor/src/debug/BisectCandidateSelector.h
Normal file
26
editor/src/debug/BisectCandidateSelector.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
// Step 1481: bisect candidate selector.
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct BisectCandidate {
|
||||
std::string proposalId;
|
||||
int index = 0;
|
||||
};
|
||||
|
||||
class BisectCandidateSelector {
|
||||
public:
|
||||
static std::vector<BisectCandidate> ordered(const std::vector<std::string>& proposalIds) {
|
||||
std::vector<BisectCandidate> out;
|
||||
for (size_t i = 0; i < proposalIds.size(); ++i) out.push_back({proposalIds[i], static_cast<int>(i)});
|
||||
std::sort(out.begin(), out.end(), [](const auto& a, const auto& b){ return a.proposalId < b.proposalId; });
|
||||
return out;
|
||||
}
|
||||
|
||||
static BisectCandidate midpoint(const std::vector<BisectCandidate>& orderedCandidates) {
|
||||
if (orderedCandidates.empty()) return {"", -1};
|
||||
return orderedCandidates[orderedCandidates.size() / 2];
|
||||
}
|
||||
};
|
||||
70
editor/src/debug/CampaignCheckpoint.h
Normal file
70
editor/src/debug/CampaignCheckpoint.h
Normal file
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
// Step 1499: campaign checkpoint model.
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "CampaignProgressTracker.h"
|
||||
|
||||
struct CampaignCheckpoint {
|
||||
std::string checkpointId;
|
||||
std::string campaignId;
|
||||
int sequence = 0;
|
||||
CampaignProgress progress;
|
||||
std::string notes;
|
||||
};
|
||||
|
||||
class CampaignCheckpointModel {
|
||||
public:
|
||||
static CampaignCheckpoint make(const std::string& campaignId,
|
||||
int sequence,
|
||||
const CampaignProgress& progress,
|
||||
const std::string& notes) {
|
||||
CampaignCheckpoint c;
|
||||
c.campaignId = campaignId;
|
||||
c.sequence = sequence;
|
||||
c.progress = progress;
|
||||
c.notes = notes;
|
||||
c.checkpointId = stableId(campaignId, sequence, notes);
|
||||
return c;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const CampaignCheckpoint& c) {
|
||||
return {
|
||||
{"checkpoint_id", c.checkpointId},
|
||||
{"campaign_id", c.campaignId},
|
||||
{"sequence", c.sequence},
|
||||
{"progress", CampaignProgressTracker::toJson(c.progress)},
|
||||
{"notes", c.notes}
|
||||
};
|
||||
}
|
||||
|
||||
static CampaignCheckpoint fromJson(const nlohmann::json& j) {
|
||||
CampaignCheckpoint c;
|
||||
c.checkpointId = j.value("checkpoint_id", "");
|
||||
c.campaignId = j.value("campaign_id", "");
|
||||
c.sequence = j.value("sequence", 0);
|
||||
c.notes = j.value("notes", "");
|
||||
auto p = j.value("progress", nlohmann::json::object());
|
||||
c.progress.campaignId = p.value("campaign_id", c.campaignId);
|
||||
c.progress.totalTargets = p.value("total_targets", 0);
|
||||
c.progress.completedTargets = p.value("completed_targets", 0);
|
||||
c.progress.greenTargets = p.value("green_targets", 0);
|
||||
c.progress.escalatedTargets = p.value("escalated_targets", 0);
|
||||
c.progress.stopped = p.value("stopped", false);
|
||||
return c;
|
||||
}
|
||||
|
||||
private:
|
||||
static std::string stableId(const std::string& campaignId, int sequence, const std::string& notes) {
|
||||
uint64_t h = 1469598103934665603ull;
|
||||
std::string key = campaignId + "|" + std::to_string(sequence) + "|" + notes;
|
||||
for (unsigned char ch : key) {
|
||||
h ^= ch;
|
||||
h *= 1099511628211ull;
|
||||
}
|
||||
return "cp_" + std::to_string(h);
|
||||
}
|
||||
};
|
||||
53
editor/src/debug/CampaignCheckpointStore.h
Normal file
53
editor/src/debug/CampaignCheckpointStore.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
// Step 1500: campaign checkpoint store.
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "CampaignCheckpoint.h"
|
||||
|
||||
class CampaignCheckpointStore {
|
||||
public:
|
||||
static bool save(const std::string& path, const CampaignCheckpoint& c) {
|
||||
auto all = list(path);
|
||||
all.push_back(c);
|
||||
sortStable(all);
|
||||
|
||||
nlohmann::json arr = nlohmann::json::array();
|
||||
for (const auto& x : all) arr.push_back(CampaignCheckpointModel::toJson(x));
|
||||
|
||||
std::ofstream out(path, std::ios::binary | std::ios::trunc);
|
||||
if (!out) return false;
|
||||
out << arr.dump(2);
|
||||
return out.good();
|
||||
}
|
||||
|
||||
static std::vector<CampaignCheckpoint> list(const std::string& path) {
|
||||
std::ifstream in(path, std::ios::binary);
|
||||
if (!in) return {};
|
||||
nlohmann::json j;
|
||||
try {
|
||||
in >> j;
|
||||
} catch (...) {
|
||||
return {};
|
||||
}
|
||||
std::vector<CampaignCheckpoint> out;
|
||||
if (!j.is_array()) return out;
|
||||
for (const auto& it : j) out.push_back(CampaignCheckpointModel::fromJson(it));
|
||||
sortStable(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
private:
|
||||
static void sortStable(std::vector<CampaignCheckpoint>& items) {
|
||||
std::sort(items.begin(), items.end(), [](const CampaignCheckpoint& a, const CampaignCheckpoint& b) {
|
||||
if (a.campaignId != b.campaignId) return a.campaignId < b.campaignId;
|
||||
if (a.sequence != b.sequence) return a.sequence < b.sequence;
|
||||
return a.checkpointId < b.checkpointId;
|
||||
});
|
||||
}
|
||||
};
|
||||
43
editor/src/debug/CampaignExportBundle.h
Normal file
43
editor/src/debug/CampaignExportBundle.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
// Step 1506: campaign export bundle model.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "CampaignCheckpoint.h"
|
||||
#include "DebugCampaignSpec.h"
|
||||
|
||||
struct CampaignExportBundle {
|
||||
DebugCampaignSpec spec;
|
||||
CampaignProgress progress;
|
||||
std::vector<CampaignCheckpoint> checkpoints;
|
||||
nlohmann::json metrics = nlohmann::json::object();
|
||||
};
|
||||
|
||||
class CampaignExportBundleModel {
|
||||
public:
|
||||
static CampaignExportBundle build(const DebugCampaignSpec& spec,
|
||||
const CampaignProgress& progress,
|
||||
const std::vector<CampaignCheckpoint>& checkpoints,
|
||||
const nlohmann::json& metrics) {
|
||||
CampaignExportBundle b;
|
||||
b.spec = spec;
|
||||
b.progress = progress;
|
||||
b.checkpoints = checkpoints;
|
||||
b.metrics = metrics;
|
||||
return b;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const CampaignExportBundle& b) {
|
||||
nlohmann::json cps = nlohmann::json::array();
|
||||
for (const auto& c : b.checkpoints) cps.push_back(CampaignCheckpointModel::toJson(c));
|
||||
return {
|
||||
{"campaign", DebugCampaignSpecModel::toJson(b.spec)},
|
||||
{"progress", CampaignProgressTracker::toJson(b.progress)},
|
||||
{"checkpoints", cps},
|
||||
{"metrics", b.metrics}
|
||||
};
|
||||
}
|
||||
};
|
||||
29
editor/src/debug/CampaignProgressTracker.h
Normal file
29
editor/src/debug/CampaignProgressTracker.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
// Step 1492: campaign progress tracker.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct CampaignProgress {
|
||||
std::string campaignId;
|
||||
int totalTargets = 0;
|
||||
int completedTargets = 0;
|
||||
int greenTargets = 0;
|
||||
int escalatedTargets = 0;
|
||||
bool stopped = false;
|
||||
};
|
||||
|
||||
class CampaignProgressTracker {
|
||||
public:
|
||||
static double percent(const CampaignProgress& p) {
|
||||
if (p.totalTargets <= 0) return 0.0;
|
||||
return 100.0 * p.completedTargets / p.totalTargets;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const CampaignProgress& p) {
|
||||
return {{"campaign_id", p.campaignId}, {"total_targets", p.totalTargets}, {"completed_targets", p.completedTargets},
|
||||
{"green_targets", p.greenTargets}, {"escalated_targets", p.escalatedTargets},
|
||||
{"stopped", p.stopped}, {"percent", percent(p)}};
|
||||
}
|
||||
};
|
||||
24
editor/src/debug/CampaignQueuePolicy.h
Normal file
24
editor/src/debug/CampaignQueuePolicy.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
// Step 1501: campaign queue policy model.
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct CampaignQueueItem {
|
||||
std::string campaignId;
|
||||
int priority = 0;
|
||||
int ageMinutes = 0;
|
||||
};
|
||||
|
||||
class CampaignQueuePolicy {
|
||||
public:
|
||||
static std::vector<CampaignQueueItem> order(std::vector<CampaignQueueItem> items) {
|
||||
std::sort(items.begin(), items.end(), [](const CampaignQueueItem& a, const CampaignQueueItem& b) {
|
||||
if (a.priority != b.priority) return a.priority > b.priority;
|
||||
if (a.ageMinutes != b.ageMinutes) return a.ageMinutes > b.ageMinutes;
|
||||
return a.campaignId < b.campaignId;
|
||||
});
|
||||
return items;
|
||||
}
|
||||
};
|
||||
31
editor/src/debug/CampaignReportBundle.h
Normal file
31
editor/src/debug/CampaignReportBundle.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
// Step 1497: campaign report bundle.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "CampaignProgressTracker.h"
|
||||
|
||||
struct CampaignReportBundle {
|
||||
std::string campaignId;
|
||||
CampaignProgress progress;
|
||||
nlohmann::json metrics = nlohmann::json::object();
|
||||
};
|
||||
|
||||
class CampaignReportBundleModel {
|
||||
public:
|
||||
static CampaignReportBundle build(const std::string& campaignId,
|
||||
const CampaignProgress& progress,
|
||||
const nlohmann::json& metrics) {
|
||||
CampaignReportBundle b;
|
||||
b.campaignId = campaignId;
|
||||
b.progress = progress;
|
||||
b.metrics = metrics;
|
||||
return b;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const CampaignReportBundle& b) {
|
||||
return {{"campaign_id", b.campaignId}, {"progress", CampaignProgressTracker::toJson(b.progress)}, {"metrics", b.metrics}};
|
||||
}
|
||||
};
|
||||
54
editor/src/debug/CampaignResumePlanner.h
Normal file
54
editor/src/debug/CampaignResumePlanner.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
// Step 1502: campaign resume planner model.
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "CampaignCheckpoint.h"
|
||||
|
||||
struct CampaignResumePlan {
|
||||
bool canResume = false;
|
||||
std::string campaignId;
|
||||
int resumedFromSequence = 0;
|
||||
CampaignProgress restoredProgress;
|
||||
};
|
||||
|
||||
class CampaignResumePlanner {
|
||||
public:
|
||||
static CampaignResumePlan plan(const std::string& campaignId,
|
||||
const std::vector<CampaignCheckpoint>& checkpoints) {
|
||||
CampaignResumePlan p;
|
||||
p.campaignId = campaignId;
|
||||
|
||||
CampaignCheckpoint best;
|
||||
bool found = false;
|
||||
for (const auto& c : checkpoints) {
|
||||
if (c.campaignId != campaignId) continue;
|
||||
if (!found || c.sequence > best.sequence ||
|
||||
(c.sequence == best.sequence && c.checkpointId > best.checkpointId)) {
|
||||
best = c;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
p.canResume = true;
|
||||
p.resumedFromSequence = best.sequence;
|
||||
p.restoredProgress = best.progress;
|
||||
p.restoredProgress.stopped = false;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const CampaignResumePlan& p) {
|
||||
return {
|
||||
{"can_resume", p.canResume},
|
||||
{"campaign_id", p.campaignId},
|
||||
{"resumed_from_sequence", p.resumedFromSequence},
|
||||
{"restored_progress", CampaignProgressTracker::toJson(p.restoredProgress)}
|
||||
};
|
||||
}
|
||||
};
|
||||
24
editor/src/debug/CampaignTargetPrioritizer.h
Normal file
24
editor/src/debug/CampaignTargetPrioritizer.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
// Step 1490: campaign target prioritizer.
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct CampaignTarget {
|
||||
std::string target;
|
||||
int severity = 0;
|
||||
int flakiness = 0;
|
||||
};
|
||||
|
||||
class CampaignTargetPrioritizer {
|
||||
public:
|
||||
static std::vector<CampaignTarget> prioritize(std::vector<CampaignTarget> in) {
|
||||
std::sort(in.begin(), in.end(), [](const auto& a, const auto& b) {
|
||||
if (a.severity != b.severity) return a.severity > b.severity;
|
||||
if (a.flakiness != b.flakiness) return a.flakiness < b.flakiness;
|
||||
return a.target < b.target;
|
||||
});
|
||||
return in;
|
||||
}
|
||||
};
|
||||
50
editor/src/debug/DebugActionBudget.h
Normal file
50
editor/src/debug/DebugActionBudget.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
// Step 1530: debug action budget model.
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct DebugActionBudget {
|
||||
std::string profile = "tiny";
|
||||
int maxIterations = 3;
|
||||
int maxTokenCost = 4000;
|
||||
int maxMinutes = 15;
|
||||
};
|
||||
|
||||
class DebugActionBudgetModel {
|
||||
public:
|
||||
static DebugActionBudget estimate(const std::string& profile,
|
||||
int failingTargets,
|
||||
int complexity) {
|
||||
DebugActionBudget b;
|
||||
b.profile = profile;
|
||||
int t = std::max(1, failingTargets);
|
||||
int c = std::max(1, complexity);
|
||||
|
||||
if (profile == "tiny") {
|
||||
b.maxIterations = std::min(5, t + 1);
|
||||
b.maxTokenCost = 2000 + c * 300;
|
||||
b.maxMinutes = 10 + t;
|
||||
} else if (profile == "small") {
|
||||
b.maxIterations = std::min(8, t + 2);
|
||||
b.maxTokenCost = 3500 + c * 500;
|
||||
b.maxMinutes = 15 + t * 2;
|
||||
} else {
|
||||
b.maxIterations = std::min(12, t + 4);
|
||||
b.maxTokenCost = 5000 + c * 700;
|
||||
b.maxMinutes = 20 + t * 3;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const DebugActionBudget& b) {
|
||||
return {
|
||||
{"profile", b.profile},
|
||||
{"max_iterations", b.maxIterations},
|
||||
{"max_token_cost", b.maxTokenCost},
|
||||
{"max_minutes", b.maxMinutes}
|
||||
};
|
||||
}
|
||||
};
|
||||
43
editor/src/debug/DebugActionValidator.h
Normal file
43
editor/src/debug/DebugActionValidator.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
// Step 1521: debug action validator model.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct DebugActionValidation {
|
||||
bool allowed = true;
|
||||
std::vector<std::string> violations;
|
||||
};
|
||||
|
||||
class DebugActionValidator {
|
||||
public:
|
||||
static DebugActionValidation validate(const std::string& action,
|
||||
bool touchesForbiddenPath,
|
||||
bool changesManyFiles,
|
||||
bool bypassesTests) {
|
||||
DebugActionValidation v;
|
||||
if (action.empty()) {
|
||||
v.allowed = false;
|
||||
v.violations.push_back("action_empty");
|
||||
}
|
||||
if (touchesForbiddenPath) {
|
||||
v.allowed = false;
|
||||
v.violations.push_back("forbidden_path");
|
||||
}
|
||||
if (changesManyFiles) {
|
||||
v.allowed = false;
|
||||
v.violations.push_back("change_scope_exceeded");
|
||||
}
|
||||
if (bypassesTests) {
|
||||
v.allowed = false;
|
||||
v.violations.push_back("test_bypass_forbidden");
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const DebugActionValidation& v) {
|
||||
return {{"allowed", v.allowed}, {"violations", v.violations}};
|
||||
}
|
||||
};
|
||||
35
editor/src/debug/DebugAssistPacket.h
Normal file
35
editor/src/debug/DebugAssistPacket.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
// Step 1517: debug assist packet bundle model.
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "DebugHintTemplate.h"
|
||||
#include "FailureTriageScore.h"
|
||||
#include "MinimalReproReducer.h"
|
||||
#include "PatchRiskLabeler.h"
|
||||
|
||||
struct DebugAssistPacket {
|
||||
DebugHintPacket hints;
|
||||
FailureTriageScore triage;
|
||||
MinimalReproResult repro;
|
||||
PatchRiskLabel risk;
|
||||
};
|
||||
|
||||
class DebugAssistPacketModel {
|
||||
public:
|
||||
static DebugAssistPacket build(const DebugHintPacket& hints,
|
||||
const FailureTriageScore& triage,
|
||||
const MinimalReproResult& repro,
|
||||
const PatchRiskLabel& risk) {
|
||||
return {hints, triage, repro, risk};
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const DebugAssistPacket& p) {
|
||||
return {
|
||||
{"hints", DebugHintTemplate::toJson(p.hints)},
|
||||
{"triage", FailureTriageScorer::toJson(p.triage)},
|
||||
{"repro", MinimalReproReducer::toJson(p.repro)},
|
||||
{"risk", PatchRiskLabeler::toJson(p.risk)}
|
||||
};
|
||||
}
|
||||
};
|
||||
34
editor/src/debug/DebugBenchmarkHarness.h
Normal file
34
editor/src/debug/DebugBenchmarkHarness.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
// Step 1475: debug benchmark harness.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "FailureFixtureCatalog.h"
|
||||
#include "FailurePacket.h"
|
||||
|
||||
struct DebugBenchmarkResult {
|
||||
int fixtureCount = 0;
|
||||
int packetizedCount = 0;
|
||||
bool success = false;
|
||||
};
|
||||
|
||||
class DebugBenchmarkHarness {
|
||||
public:
|
||||
static DebugBenchmarkResult run(const std::vector<FailureFixture>& fixtures) {
|
||||
DebugBenchmarkResult r;
|
||||
r.fixtureCount = static_cast<int>(fixtures.size());
|
||||
for (const auto& f : fixtures) {
|
||||
auto p = FailurePacketModel::make(f.command, f.raw, f.exitCode);
|
||||
if (!p.packetId.empty()) ++r.packetizedCount;
|
||||
}
|
||||
r.success = r.packetizedCount == r.fixtureCount;
|
||||
return r;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const DebugBenchmarkResult& r) {
|
||||
return {{"fixture_count", r.fixtureCount}, {"packetized_count", r.packetizedCount}, {"success", r.success}};
|
||||
}
|
||||
};
|
||||
51
editor/src/debug/DebugCampaignSpec.h
Normal file
51
editor/src/debug/DebugCampaignSpec.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
// Step 1489: debug campaign spec model.
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct DebugCampaignSpec {
|
||||
std::string campaignId;
|
||||
std::string name;
|
||||
std::vector<std::string> targets;
|
||||
std::string budgetMode = "tiny";
|
||||
int maxIterationsPerTarget = 3;
|
||||
bool applyPatches = true;
|
||||
};
|
||||
|
||||
class DebugCampaignSpecModel {
|
||||
public:
|
||||
static DebugCampaignSpec make(const std::string& name,
|
||||
std::vector<std::string> targets,
|
||||
const std::string& budgetMode,
|
||||
int maxIter,
|
||||
bool apply) {
|
||||
DebugCampaignSpec s;
|
||||
s.name = name;
|
||||
std::sort(targets.begin(), targets.end());
|
||||
s.targets = std::move(targets);
|
||||
s.budgetMode = budgetMode;
|
||||
s.maxIterationsPerTarget = maxIter;
|
||||
s.applyPatches = apply;
|
||||
s.campaignId = stableId(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const DebugCampaignSpec& s) {
|
||||
return {{"campaign_id", s.campaignId}, {"name", s.name}, {"targets", s.targets},
|
||||
{"budget_mode", s.budgetMode}, {"max_iterations_per_target", s.maxIterationsPerTarget},
|
||||
{"apply_patches", s.applyPatches}};
|
||||
}
|
||||
|
||||
private:
|
||||
static std::string stableId(const DebugCampaignSpec& s) {
|
||||
uint64_t h = 1469598103934665603ull;
|
||||
std::string key = s.name + "|" + s.budgetMode + "|" + std::to_string(s.maxIterationsPerTarget);
|
||||
for (const auto& t : s.targets) key += "|" + t;
|
||||
for (unsigned char c : key) { h ^= c; h *= 1099511628211ull; }
|
||||
return "dc_" + std::to_string(h);
|
||||
}
|
||||
};
|
||||
23
editor/src/debug/DebugChecklistItem.h
Normal file
23
editor/src/debug/DebugChecklistItem.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
// Step 1539: debug checklist item model.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct DebugChecklistItem {
|
||||
int index = 0;
|
||||
std::string text;
|
||||
bool done = false;
|
||||
};
|
||||
|
||||
class DebugChecklistItemModel {
|
||||
public:
|
||||
static DebugChecklistItem make(int index, const std::string& text, bool done) {
|
||||
return {index, text, done};
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const DebugChecklistItem& i) {
|
||||
return {{"index", i.index}, {"text", i.text}, {"done", i.done}};
|
||||
}
|
||||
};
|
||||
48
editor/src/debug/DebugChecklistTemplate.h
Normal file
48
editor/src/debug/DebugChecklistTemplate.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
// Step 1540: debug checklist template model.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "DebugChecklistItem.h"
|
||||
|
||||
struct DebugChecklistTemplate {
|
||||
std::string failureClass;
|
||||
std::vector<DebugChecklistItem> items;
|
||||
};
|
||||
|
||||
class DebugChecklistTemplateModel {
|
||||
public:
|
||||
static DebugChecklistTemplate build(const std::string& failureClass) {
|
||||
DebugChecklistTemplate t;
|
||||
t.failureClass = failureClass;
|
||||
if (failureClass == "compile") {
|
||||
t.items = {
|
||||
DebugChecklistItemModel::make(1, "Reproduce compile error", false),
|
||||
DebugChecklistItemModel::make(2, "Patch smallest compile unit", false),
|
||||
DebugChecklistItemModel::make(3, "Run compile regression guard", false)
|
||||
};
|
||||
} else if (failureClass == "test") {
|
||||
t.items = {
|
||||
DebugChecklistItemModel::make(1, "Reproduce failing test", false),
|
||||
DebugChecklistItemModel::make(2, "Minimize fixture/mocks", false),
|
||||
DebugChecklistItemModel::make(3, "Run targeted + guard tests", false)
|
||||
};
|
||||
} else {
|
||||
t.items = {
|
||||
DebugChecklistItemModel::make(1, "Capture fresh failure packet", false),
|
||||
DebugChecklistItemModel::make(2, "Select top root-cause cluster", false),
|
||||
DebugChecklistItemModel::make(3, "Apply guarded fix and verify", false)
|
||||
};
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const DebugChecklistTemplate& t) {
|
||||
nlohmann::json arr = nlohmann::json::array();
|
||||
for (const auto& i : t.items) arr.push_back(DebugChecklistItemModel::toJson(i));
|
||||
return {{"failure_class", t.failureClass}, {"items", arr}};
|
||||
}
|
||||
};
|
||||
30
editor/src/debug/DebugEvidenceIndex.h
Normal file
30
editor/src/debug/DebugEvidenceIndex.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
// Step 1542: debug evidence index model.
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct DebugEvidenceEntry {
|
||||
std::string kind;
|
||||
std::string ref;
|
||||
};
|
||||
|
||||
class DebugEvidenceIndexModel {
|
||||
public:
|
||||
static std::vector<DebugEvidenceEntry> sort(std::vector<DebugEvidenceEntry> entries) {
|
||||
std::sort(entries.begin(), entries.end(), [](const DebugEvidenceEntry& a, const DebugEvidenceEntry& b) {
|
||||
if (a.kind != b.kind) return a.kind < b.kind;
|
||||
return a.ref < b.ref;
|
||||
});
|
||||
return entries;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const std::vector<DebugEvidenceEntry>& entries) {
|
||||
nlohmann::json arr = nlohmann::json::array();
|
||||
for (const auto& e : entries) arr.push_back({{"kind", e.kind}, {"ref", e.ref}});
|
||||
return arr;
|
||||
}
|
||||
};
|
||||
34
editor/src/debug/DebugExecutionTrace.h
Normal file
34
editor/src/debug/DebugExecutionTrace.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
// Step 1522: debug execution trace model.
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct DebugTraceEvent {
|
||||
int index = 0;
|
||||
std::string action;
|
||||
std::string status;
|
||||
};
|
||||
|
||||
class DebugExecutionTrace {
|
||||
public:
|
||||
static std::vector<DebugTraceEvent> append(std::vector<DebugTraceEvent> events,
|
||||
const DebugTraceEvent& e) {
|
||||
events.push_back(e);
|
||||
std::sort(events.begin(), events.end(), [](const DebugTraceEvent& a, const DebugTraceEvent& b) {
|
||||
if (a.index != b.index) return a.index < b.index;
|
||||
if (a.action != b.action) return a.action < b.action;
|
||||
return a.status < b.status;
|
||||
});
|
||||
return events;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const std::vector<DebugTraceEvent>& events) {
|
||||
nlohmann::json arr = nlohmann::json::array();
|
||||
for (const auto& e : events) arr.push_back({{"index", e.index}, {"action", e.action}, {"status", e.status}});
|
||||
return arr;
|
||||
}
|
||||
};
|
||||
35
editor/src/debug/DebugGuidancePacket.h
Normal file
35
editor/src/debug/DebugGuidancePacket.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
// Step 1537: debug guidance packet model.
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "DebugPolicyConstraint.h"
|
||||
#include "DebugActionBudget.h"
|
||||
#include "DebugRecoveryAdvisor.h"
|
||||
#include "DebugStopReasonClassifier.h"
|
||||
|
||||
struct DebugGuidancePacket {
|
||||
DebugPolicyConstraint constraints;
|
||||
DebugActionBudget budget;
|
||||
DebugRecoveryAdvice recovery;
|
||||
DebugStopReason stopReason;
|
||||
};
|
||||
|
||||
class DebugGuidancePacketModel {
|
||||
public:
|
||||
static DebugGuidancePacket build(const DebugPolicyConstraint& constraints,
|
||||
const DebugActionBudget& budget,
|
||||
const DebugRecoveryAdvice& recovery,
|
||||
const DebugStopReason& stopReason) {
|
||||
return {constraints, budget, recovery, stopReason};
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const DebugGuidancePacket& p) {
|
||||
return {
|
||||
{"constraints", DebugPolicyConstraintModel::toJson(p.constraints)},
|
||||
{"budget", DebugActionBudgetModel::toJson(p.budget)},
|
||||
{"recovery", DebugRecoveryAdvisor::toJson(p.recovery)},
|
||||
{"stop_reason", DebugStopReasonClassifier::toJson(p.stopReason)}
|
||||
};
|
||||
}
|
||||
};
|
||||
31
editor/src/debug/DebugHandoffPacket.h
Normal file
31
editor/src/debug/DebugHandoffPacket.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
// Step 1547: debug handoff packet model.
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "DebugChecklistTemplate.h"
|
||||
#include "DebugSessionHandoff.h"
|
||||
#include "DebugEvidenceIndex.h"
|
||||
|
||||
struct DebugHandoffPacket {
|
||||
DebugChecklistTemplate checklist;
|
||||
DebugSessionHandoff handoff;
|
||||
std::vector<DebugEvidenceEntry> evidence;
|
||||
};
|
||||
|
||||
class DebugHandoffPacketModel {
|
||||
public:
|
||||
static DebugHandoffPacket build(const DebugChecklistTemplate& checklist,
|
||||
const DebugSessionHandoff& handoff,
|
||||
const std::vector<DebugEvidenceEntry>& evidence) {
|
||||
return {checklist, handoff, evidence};
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const DebugHandoffPacket& p) {
|
||||
return {
|
||||
{"checklist", DebugChecklistTemplateModel::toJson(p.checklist)},
|
||||
{"handoff", DebugSessionHandoffModel::toJson(p.handoff)},
|
||||
{"evidence", DebugEvidenceIndexModel::toJson(DebugEvidenceIndexModel::sort(p.evidence))}
|
||||
};
|
||||
}
|
||||
};
|
||||
44
editor/src/debug/DebugHintTemplate.h
Normal file
44
editor/src/debug/DebugHintTemplate.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
// Step 1509: debug hint template model.
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct DebugHintPacket {
|
||||
std::string failureClass;
|
||||
std::vector<std::string> hints;
|
||||
std::string confidence = "medium";
|
||||
};
|
||||
|
||||
class DebugHintTemplate {
|
||||
public:
|
||||
static DebugHintPacket build(const std::string& failureClass,
|
||||
const std::vector<std::string>& context) {
|
||||
DebugHintPacket p;
|
||||
p.failureClass = failureClass;
|
||||
p.hints = baseHints(failureClass);
|
||||
if (!context.empty()) p.hints.push_back("Focus file: " + context.front());
|
||||
std::sort(p.hints.begin(), p.hints.end());
|
||||
p.hints.erase(std::unique(p.hints.begin(), p.hints.end()), p.hints.end());
|
||||
p.confidence = p.hints.size() > 2 ? "high" : "medium";
|
||||
return p;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const DebugHintPacket& p) {
|
||||
return {{"failure_class", p.failureClass}, {"hints", p.hints}, {"confidence", p.confidence}};
|
||||
}
|
||||
|
||||
private:
|
||||
static std::vector<std::string> baseHints(const std::string& failureClass) {
|
||||
if (failureClass == "compile") {
|
||||
return {"Check recent syntax edits", "Re-run single target first", "Review include/import order"};
|
||||
}
|
||||
if (failureClass == "test") {
|
||||
return {"Minimize fixture setup", "Verify expectation order", "Run failing test in isolation"};
|
||||
}
|
||||
return {"Capture failure packet", "Cluster by root symptom", "Guard against regressions"};
|
||||
}
|
||||
};
|
||||
82
editor/src/debug/DebugLoopOrchestrator.h
Normal file
82
editor/src/debug/DebugLoopOrchestrator.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
// Step 1457: deterministic debug loop orchestrator core.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "FailurePacket.h"
|
||||
#include "FailureClusterer.h"
|
||||
#include "FixContextAssembler.h"
|
||||
#include "FailurePatchProposer.h"
|
||||
#include "RegressionGuardPlanner.h"
|
||||
|
||||
struct DebugLoopResult {
|
||||
std::string status; // green|escalated|stopped
|
||||
int iterations = 0;
|
||||
std::vector<nlohmann::json> transitions;
|
||||
FailurePacket finalPacket;
|
||||
RegressionGuardPlan guard;
|
||||
};
|
||||
|
||||
class DebugLoopOrchestrator {
|
||||
public:
|
||||
static DebugLoopResult run(const std::string& command,
|
||||
int maxIterations,
|
||||
const std::string& contextBudget,
|
||||
bool applyPatches) {
|
||||
DebugLoopResult out;
|
||||
out.status = "stopped";
|
||||
|
||||
for (int i = 1; i <= std::max(1, maxIterations); ++i) {
|
||||
out.iterations = i;
|
||||
out.transitions.push_back({{"iteration", i}, {"phase", "reproduce"}});
|
||||
|
||||
auto p = capturePacket(command);
|
||||
out.finalPacket = p;
|
||||
out.transitions.push_back({{"iteration", i}, {"phase", "capture"}, {"failure_class", p.failureClass}});
|
||||
|
||||
auto clusters = FailureClusterer::cluster({p});
|
||||
out.transitions.push_back({{"iteration", i}, {"phase", "cluster"}, {"clusters", (int)clusters.size()}});
|
||||
|
||||
std::vector<FixContextSlice> slices = {{
|
||||
p.primaryFile.empty() ? "editor/src/unknown.h" : p.primaryFile,
|
||||
p.primaryLine > 0 ? p.primaryLine : 1,
|
||||
p.primaryLine > 0 ? p.primaryLine + 5 : 10,
|
||||
p.rawExcerpt
|
||||
}};
|
||||
auto ctx = FixContextAssembler::assemble(contextBudget, slices);
|
||||
out.transitions.push_back({{"iteration", i}, {"phase", "assemble_context"}, {"chars", ctx.totalChars}});
|
||||
|
||||
auto suggestion = FailurePatchProposer::propose(clusters.empty() ? FailureCluster{} : clusters[0], ctx,
|
||||
"step" + std::to_string(700 + i) + "_test");
|
||||
out.transitions.push_back({{"iteration", i}, {"phase", "propose_patch"}, {"proposal_id", suggestion.proposal.proposalId}});
|
||||
|
||||
if (applyPatches) out.transitions.push_back({{"iteration", i}, {"phase", "apply_patch"}, {"applied", true}});
|
||||
|
||||
const bool fixable = command.find("fixable") != std::string::npos;
|
||||
if (fixable && applyPatches) {
|
||||
out.status = "green";
|
||||
out.guard = RegressionGuardPlanner::plan({p.primaryFile}, 1457, "step1457_test");
|
||||
out.transitions.push_back({{"iteration", i}, {"phase", "guard"}, {"status", "green"}});
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
out.status = "escalated";
|
||||
out.guard = RegressionGuardPlanner::plan({out.finalPacket.primaryFile}, 1457, "step1457_test");
|
||||
out.transitions.push_back({{"phase", "stop"}, {"reason", "max_iterations"}});
|
||||
return out;
|
||||
}
|
||||
|
||||
private:
|
||||
static FailurePacket capturePacket(const std::string& command) {
|
||||
if (command.rfind("mock:", 0) == 0) {
|
||||
std::string raw = command.substr(5);
|
||||
int exitCode = raw.find("ok") != std::string::npos ? 0 : 1;
|
||||
return FailurePacketModel::make(command, raw, exitCode);
|
||||
}
|
||||
return FailurePacketModel::make(command, "runtime_error: non-mock command capture not enabled", 1);
|
||||
}
|
||||
};
|
||||
52
editor/src/debug/DebugMetricsPacket.h
Normal file
52
editor/src/debug/DebugMetricsPacket.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
// Step 1467: debugging metrics packet.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct DebugMetricsPacket {
|
||||
std::string sessionId;
|
||||
int iterationsToGreen = 0;
|
||||
int timeToGreenMs = 0;
|
||||
int tokenCostPerFix = 0;
|
||||
int symptomToRootCauseCompression = 0;
|
||||
int patchAcceptanceRatePct = 0;
|
||||
int regressionEscapeRatePct = 0;
|
||||
};
|
||||
|
||||
class DebugMetricsModel {
|
||||
public:
|
||||
static DebugMetricsPacket compute(const std::string& sessionId,
|
||||
int iterations,
|
||||
int durationMs,
|
||||
int tokenCost,
|
||||
int symptomCount,
|
||||
int rootCauseCount,
|
||||
int acceptedPatches,
|
||||
int proposedPatches,
|
||||
int regressions,
|
||||
int totalRuns) {
|
||||
DebugMetricsPacket m;
|
||||
m.sessionId = sessionId;
|
||||
m.iterationsToGreen = iterations;
|
||||
m.timeToGreenMs = durationMs;
|
||||
m.tokenCostPerFix = tokenCost;
|
||||
m.symptomToRootCauseCompression = (rootCauseCount == 0) ? 0 : (100 * symptomCount / rootCauseCount);
|
||||
m.patchAcceptanceRatePct = (proposedPatches == 0) ? 0 : (100 * acceptedPatches / proposedPatches);
|
||||
m.regressionEscapeRatePct = (totalRuns == 0) ? 0 : (100 * regressions / totalRuns);
|
||||
return m;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const DebugMetricsPacket& m) {
|
||||
return {
|
||||
{"session_id", m.sessionId},
|
||||
{"iterations_to_green", m.iterationsToGreen},
|
||||
{"time_to_green_ms", m.timeToGreenMs},
|
||||
{"token_cost_per_fix", m.tokenCostPerFix},
|
||||
{"symptom_to_root_cause_compression", m.symptomToRootCauseCompression},
|
||||
{"patch_acceptance_rate_pct", m.patchAcceptanceRatePct},
|
||||
{"regression_escape_rate_pct", m.regressionEscapeRatePct}
|
||||
};
|
||||
}
|
||||
};
|
||||
50
editor/src/debug/DebugPolicyConstraint.h
Normal file
50
editor/src/debug/DebugPolicyConstraint.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
// Step 1529: debug policy constraint model.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct DebugPolicyConstraint {
|
||||
std::string mode = "safe";
|
||||
int maxFilesTouched = 3;
|
||||
int maxLineChanges = 40;
|
||||
bool requireTests = true;
|
||||
std::vector<std::string> forbiddenPaths;
|
||||
};
|
||||
|
||||
class DebugPolicyConstraintModel {
|
||||
public:
|
||||
static DebugPolicyConstraint forMode(const std::string& mode) {
|
||||
DebugPolicyConstraint c;
|
||||
c.mode = mode;
|
||||
if (mode == "strict") {
|
||||
c.maxFilesTouched = 2;
|
||||
c.maxLineChanges = 25;
|
||||
c.requireTests = true;
|
||||
c.forbiddenPaths = {"editor/src/mcp/", "editor/src/MCPServer.h"};
|
||||
} else if (mode == "normal") {
|
||||
c.maxFilesTouched = 4;
|
||||
c.maxLineChanges = 60;
|
||||
c.requireTests = true;
|
||||
c.forbiddenPaths = {"editor/src/mcp/"};
|
||||
} else {
|
||||
c.maxFilesTouched = 3;
|
||||
c.maxLineChanges = 40;
|
||||
c.requireTests = true;
|
||||
c.forbiddenPaths = {"editor/src/mcp/"};
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const DebugPolicyConstraint& c) {
|
||||
return {
|
||||
{"mode", c.mode},
|
||||
{"max_files_touched", c.maxFilesTouched},
|
||||
{"max_line_changes", c.maxLineChanges},
|
||||
{"require_tests", c.requireTests},
|
||||
{"forbidden_paths", c.forbiddenPaths}
|
||||
};
|
||||
}
|
||||
};
|
||||
49
editor/src/debug/DebugRecipeLibrary.h
Normal file
49
editor/src/debug/DebugRecipeLibrary.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
// Step 1520: debug recipe library model.
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "DebugRecipeStep.h"
|
||||
|
||||
struct DebugRecipe {
|
||||
std::string failureClass;
|
||||
std::vector<DebugRecipeStep> steps;
|
||||
};
|
||||
|
||||
class DebugRecipeLibrary {
|
||||
public:
|
||||
static DebugRecipe get(const std::string& failureClass) {
|
||||
DebugRecipe r;
|
||||
r.failureClass = failureClass;
|
||||
if (failureClass == "compile") {
|
||||
r.steps = {
|
||||
DebugRecipeStepModel::make(1, "Run single target compile", "deterministic compile error"),
|
||||
DebugRecipeStepModel::make(2, "Inspect first compiler diagnostic", "root parse/type issue"),
|
||||
DebugRecipeStepModel::make(3, "Patch smallest failing unit", "compile green")
|
||||
};
|
||||
} else if (failureClass == "test") {
|
||||
r.steps = {
|
||||
DebugRecipeStepModel::make(1, "Run failing test only", "single deterministic failure"),
|
||||
DebugRecipeStepModel::make(2, "Reduce fixture and mocks", "minimal repro"),
|
||||
DebugRecipeStepModel::make(3, "Apply constrained patch", "test green")
|
||||
};
|
||||
} else {
|
||||
r.steps = {
|
||||
DebugRecipeStepModel::make(1, "Capture failure packet", "classified failure"),
|
||||
DebugRecipeStepModel::make(2, "Cluster by root symptom", "primary cluster"),
|
||||
DebugRecipeStepModel::make(3, "Apply guarded fix", "regression-safe result")
|
||||
};
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const DebugRecipe& r) {
|
||||
nlohmann::json arr = nlohmann::json::array();
|
||||
for (const auto& s : r.steps) arr.push_back(DebugRecipeStepModel::toJson(s));
|
||||
return {{"failure_class", r.failureClass}, {"steps", arr}};
|
||||
}
|
||||
};
|
||||
31
editor/src/debug/DebugRecipePacket.h
Normal file
31
editor/src/debug/DebugRecipePacket.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
// Step 1527: debug recipe packet model.
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "DebugRecipeLibrary.h"
|
||||
#include "DebugActionValidator.h"
|
||||
#include "DebugExecutionTrace.h"
|
||||
|
||||
struct DebugRecipePacket {
|
||||
DebugRecipe recipe;
|
||||
DebugActionValidation validation;
|
||||
std::vector<DebugTraceEvent> trace;
|
||||
};
|
||||
|
||||
class DebugRecipePacketModel {
|
||||
public:
|
||||
static DebugRecipePacket build(const DebugRecipe& recipe,
|
||||
const DebugActionValidation& validation,
|
||||
const std::vector<DebugTraceEvent>& trace) {
|
||||
return {recipe, validation, trace};
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const DebugRecipePacket& p) {
|
||||
return {
|
||||
{"recipe", DebugRecipeLibrary::toJson(p.recipe)},
|
||||
{"validation", DebugActionValidator::toJson(p.validation)},
|
||||
{"trace", DebugExecutionTrace::toJson(p.trace)}
|
||||
};
|
||||
}
|
||||
};
|
||||
23
editor/src/debug/DebugRecipeStep.h
Normal file
23
editor/src/debug/DebugRecipeStep.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
// Step 1519: debug recipe step model.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct DebugRecipeStep {
|
||||
int index = 0;
|
||||
std::string action;
|
||||
std::string expected;
|
||||
};
|
||||
|
||||
class DebugRecipeStepModel {
|
||||
public:
|
||||
static DebugRecipeStep make(int index, const std::string& action, const std::string& expected) {
|
||||
return {index, action, expected};
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const DebugRecipeStep& s) {
|
||||
return {{"index", s.index}, {"action", s.action}, {"expected", s.expected}};
|
||||
}
|
||||
};
|
||||
44
editor/src/debug/DebugRecoveryAdvisor.h
Normal file
44
editor/src/debug/DebugRecoveryAdvisor.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
// Step 1531: debug recovery advisor model.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct DebugRecoveryAdvice {
|
||||
std::string category = "generic";
|
||||
std::vector<std::string> actions;
|
||||
};
|
||||
|
||||
class DebugRecoveryAdvisor {
|
||||
public:
|
||||
static DebugRecoveryAdvice advise(const std::string& stopReason) {
|
||||
DebugRecoveryAdvice a;
|
||||
a.category = stopReason;
|
||||
if (stopReason == "budget_exhausted") {
|
||||
a.actions = {
|
||||
"Reduce context budget to tiny",
|
||||
"Replay last checkpoint",
|
||||
"Prioritize highest-severity target"
|
||||
};
|
||||
} else if (stopReason == "policy_violation") {
|
||||
a.actions = {
|
||||
"Rollback last patch",
|
||||
"Constrain file scope",
|
||||
"Re-run regression guard"
|
||||
};
|
||||
} else {
|
||||
a.actions = {
|
||||
"Capture fresh failure packet",
|
||||
"Re-cluster failures",
|
||||
"Escalate to guarded review"
|
||||
};
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const DebugRecoveryAdvice& a) {
|
||||
return {{"category", a.category}, {"actions", a.actions}};
|
||||
}
|
||||
};
|
||||
26
editor/src/debug/DebugSessionHandoff.h
Normal file
26
editor/src/debug/DebugSessionHandoff.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
// Step 1541: debug session handoff model.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct DebugSessionHandoff {
|
||||
std::string sessionId;
|
||||
std::string summary;
|
||||
std::vector<std::string> nextActions;
|
||||
};
|
||||
|
||||
class DebugSessionHandoffModel {
|
||||
public:
|
||||
static DebugSessionHandoff build(const std::string& sessionId,
|
||||
const std::string& summary,
|
||||
const std::vector<std::string>& nextActions) {
|
||||
return {sessionId, summary, nextActions};
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const DebugSessionHandoff& h) {
|
||||
return {{"session_id", h.sessionId}, {"summary", h.summary}, {"next_actions", h.nextActions}};
|
||||
}
|
||||
};
|
||||
39
editor/src/debug/DebugStopReasonClassifier.h
Normal file
39
editor/src/debug/DebugStopReasonClassifier.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
// Step 1532: debug stop-reason classifier model.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct DebugStopReason {
|
||||
std::string code = "unknown";
|
||||
std::string summary = "Unknown stop reason";
|
||||
};
|
||||
|
||||
class DebugStopReasonClassifier {
|
||||
public:
|
||||
static DebugStopReason classify(bool budgetExceeded,
|
||||
bool policyViolation,
|
||||
bool testsFailing,
|
||||
bool patchRejected) {
|
||||
DebugStopReason r;
|
||||
if (policyViolation) {
|
||||
r.code = "policy_violation";
|
||||
r.summary = "Policy constraint violated";
|
||||
} else if (budgetExceeded) {
|
||||
r.code = "budget_exhausted";
|
||||
r.summary = "Budget exhausted before green";
|
||||
} else if (patchRejected) {
|
||||
r.code = "patch_rejected";
|
||||
r.summary = "Patch proposal rejected by checks";
|
||||
} else if (testsFailing) {
|
||||
r.code = "tests_still_failing";
|
||||
r.summary = "Tests still failing after attempts";
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const DebugStopReason& r) {
|
||||
return {{"code", r.code}, {"summary", r.summary}};
|
||||
}
|
||||
};
|
||||
76
editor/src/debug/FailureClusterer.h
Normal file
76
editor/src/debug/FailureClusterer.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
// Step 1450: root-cause clustering model.
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "FailurePacket.h"
|
||||
|
||||
struct FailureCluster {
|
||||
std::string clusterId;
|
||||
std::string rootCauseCandidate;
|
||||
std::string failureClass;
|
||||
std::vector<std::string> memberPacketIds;
|
||||
int blastRadius = 0;
|
||||
bool fixFirst = false;
|
||||
};
|
||||
|
||||
class FailureClusterer {
|
||||
public:
|
||||
static std::vector<FailureCluster> cluster(const std::vector<FailurePacket>& packets) {
|
||||
std::map<std::string, FailureCluster> byKey;
|
||||
for (const auto& p : packets) {
|
||||
std::string key = p.failureClass + "|" + p.primaryFile + "|" + p.primarySymbol;
|
||||
auto& c = byKey[key];
|
||||
if (c.clusterId.empty()) {
|
||||
c.failureClass = p.failureClass;
|
||||
c.rootCauseCandidate = p.primarySymbol.empty() ? p.primaryFile : p.primarySymbol;
|
||||
c.clusterId = stableId(key);
|
||||
}
|
||||
c.memberPacketIds.push_back(p.packetId);
|
||||
}
|
||||
|
||||
std::vector<FailureCluster> out;
|
||||
for (auto& kv : byKey) {
|
||||
kv.second.blastRadius = static_cast<int>(kv.second.memberPacketIds.size());
|
||||
std::sort(kv.second.memberPacketIds.begin(), kv.second.memberPacketIds.end());
|
||||
out.push_back(kv.second);
|
||||
}
|
||||
|
||||
std::sort(out.begin(), out.end(), [](const auto& a, const auto& b) {
|
||||
if (a.blastRadius != b.blastRadius) return a.blastRadius > b.blastRadius;
|
||||
return a.clusterId < b.clusterId;
|
||||
});
|
||||
if (!out.empty()) out[0].fixFirst = true;
|
||||
return out;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const std::vector<FailureCluster>& c) {
|
||||
nlohmann::json j = nlohmann::json::array();
|
||||
for (const auto& x : c) {
|
||||
j.push_back({
|
||||
{"cluster_id", x.clusterId},
|
||||
{"root_cause_candidate", x.rootCauseCandidate},
|
||||
{"failure_class", x.failureClass},
|
||||
{"member_packet_ids", x.memberPacketIds},
|
||||
{"blast_radius", x.blastRadius},
|
||||
{"fix_first", x.fixFirst}
|
||||
});
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
private:
|
||||
static std::string stableId(const std::string& key) {
|
||||
uint64_t h = 2166136261u;
|
||||
for (unsigned char c : key) {
|
||||
h ^= c;
|
||||
h *= 16777619u;
|
||||
}
|
||||
return "fc_" + std::to_string(h);
|
||||
}
|
||||
};
|
||||
54
editor/src/debug/FailureFixtureCatalog.h
Normal file
54
editor/src/debug/FailureFixtureCatalog.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
// Step 1459: deterministic failure fixture catalog.
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct FailureFixture {
|
||||
std::string id;
|
||||
std::string failureClass;
|
||||
std::string command;
|
||||
std::string raw;
|
||||
int exitCode = 1;
|
||||
std::string expectedPrimaryFile;
|
||||
};
|
||||
|
||||
class FailureFixtureCatalog {
|
||||
public:
|
||||
static std::vector<FailureFixture> defaults() {
|
||||
std::vector<FailureFixture> v = {
|
||||
{"compile_missing_include", "compile_error", "mock:error: editor/src/a.cpp:10:1 missing include", "error: editor/src/a.cpp:10:1 missing include", 1, "editor/src/a.cpp"},
|
||||
{"test_assertion_fail", "test_assertion", "mock:alpha... PASS\nbeta... FAIL", "alpha... PASS\nbeta... FAIL", 1, ""},
|
||||
{"tool_contract_missing", "tool_contract_error", "mock:Unknown tool: whetstone_x", "Unknown tool: whetstone_x", 1, ""},
|
||||
{"schema_shape_mismatch", "schema_error", "mock:schema mismatch in packet", "schema mismatch in packet", 1, ""},
|
||||
{"runtime_nonzero", "runtime_error", "mock:segmentation fault", "segmentation fault", 1, ""}
|
||||
};
|
||||
std::sort(v.begin(), v.end(), [](const auto& a, const auto& b){ return a.id < b.id; });
|
||||
return v;
|
||||
}
|
||||
|
||||
static const FailureFixture* find(const std::vector<FailureFixture>& fixtures,
|
||||
const std::string& id) {
|
||||
for (const auto& f : fixtures) if (f.id == id) return &f;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const std::vector<FailureFixture>& fixtures) {
|
||||
nlohmann::json arr = nlohmann::json::array();
|
||||
for (const auto& f : fixtures) {
|
||||
arr.push_back({
|
||||
{"id", f.id},
|
||||
{"failure_class", f.failureClass},
|
||||
{"command", f.command},
|
||||
{"raw", f.raw},
|
||||
{"exit_code", f.exitCode},
|
||||
{"expected_primary_file", f.expectedPrimaryFile}
|
||||
});
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
};
|
||||
203
editor/src/debug/FailurePacket.h
Normal file
203
editor/src/debug/FailurePacket.h
Normal file
@@ -0,0 +1,203 @@
|
||||
#pragma once
|
||||
// Step 1449: deterministic failure packet schema + normalizer.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdint>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct FailurePacket {
|
||||
std::string packetId;
|
||||
std::string failureClass = "unknown";
|
||||
std::string primaryFile;
|
||||
int primaryLine = 0;
|
||||
std::string primarySymbol;
|
||||
std::string normalizedMessage;
|
||||
std::string reproCommand;
|
||||
std::string firstFailingTest;
|
||||
double confidence = 0.0;
|
||||
std::string rawExcerpt;
|
||||
};
|
||||
|
||||
class FailurePacketModel {
|
||||
public:
|
||||
static std::string canonicalizePath(const std::string& path) {
|
||||
std::string p = path;
|
||||
const std::string home = "/home/bill/Documents/CLionProjects/whetstone_DSL/";
|
||||
auto pos = p.find(home);
|
||||
if (pos != std::string::npos) p = p.substr(pos + home.size());
|
||||
return p;
|
||||
}
|
||||
|
||||
static std::string normalizeWhitespace(const std::string& in) {
|
||||
std::string out;
|
||||
bool ws = false;
|
||||
for (char c : in) {
|
||||
if (std::isspace(static_cast<unsigned char>(c))) {
|
||||
ws = true;
|
||||
} else {
|
||||
if (ws && !out.empty()) out.push_back(' ');
|
||||
out.push_back(c);
|
||||
ws = false;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::string stableTruncate(const std::string& in, size_t maxBytes) {
|
||||
if (in.size() <= maxBytes) return in;
|
||||
if (maxBytes < 16) return in.substr(0, maxBytes);
|
||||
return in.substr(0, maxBytes - 11) + "...[trunc]";
|
||||
}
|
||||
|
||||
static std::string dedupLines(const std::string& in) {
|
||||
std::vector<std::string> lines;
|
||||
std::string cur;
|
||||
for (char c : in) {
|
||||
if (c == '\n') {
|
||||
lines.push_back(cur);
|
||||
cur.clear();
|
||||
} else cur.push_back(c);
|
||||
}
|
||||
if (!cur.empty()) lines.push_back(cur);
|
||||
|
||||
std::vector<std::string> unique;
|
||||
for (const auto& l : lines) {
|
||||
if (l.empty()) continue;
|
||||
if (std::find(unique.begin(), unique.end(), l) == unique.end()) unique.push_back(l);
|
||||
}
|
||||
|
||||
std::string out;
|
||||
for (size_t i = 0; i < unique.size(); ++i) {
|
||||
if (i) out.push_back('\n');
|
||||
out += unique[i];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::string classify(const std::string& text, int exitCode) {
|
||||
const std::string s = text;
|
||||
if (s.find("error:") != std::string::npos || s.find("undefined reference") != std::string::npos) return "compile_error";
|
||||
if (s.find("FAIL") != std::string::npos || s.find("Assertion") != std::string::npos) return "test_assertion";
|
||||
if (s.find("schema") != std::string::npos) return "schema_error";
|
||||
if (s.find("Unknown tool") != std::string::npos) return "tool_contract_error";
|
||||
if (exitCode != 0) return "runtime_error";
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
static void extractPrimaryLocation(const std::string& text,
|
||||
std::string* file,
|
||||
int* line,
|
||||
std::string* symbol) {
|
||||
if (file) *file = "";
|
||||
if (line) *line = 0;
|
||||
if (symbol) *symbol = "";
|
||||
|
||||
std::string token;
|
||||
std::istringstream iss(text);
|
||||
while (iss >> token) {
|
||||
auto p1 = token.find(':');
|
||||
auto p2 = token.find(':', p1 == std::string::npos ? 0 : p1 + 1);
|
||||
if (p1 == std::string::npos || p2 == std::string::npos) continue;
|
||||
std::string f = canonicalizePath(token.substr(0, p1));
|
||||
std::string lineStr = token.substr(p1 + 1, p2 - p1 - 1);
|
||||
bool digits = !lineStr.empty();
|
||||
for (char c : lineStr) if (!std::isdigit(static_cast<unsigned char>(c))) digits = false;
|
||||
if (!digits) continue;
|
||||
if (file) *file = f;
|
||||
if (line) *line = std::stoi(lineStr);
|
||||
break;
|
||||
}
|
||||
|
||||
auto pos = text.find("undefined symbol ");
|
||||
if (pos != std::string::npos && symbol) {
|
||||
std::string rem = text.substr(pos + 17);
|
||||
std::string s;
|
||||
for (char c : rem) {
|
||||
if (std::isalnum(static_cast<unsigned char>(c)) || c == '_') s.push_back(c);
|
||||
else break;
|
||||
}
|
||||
*symbol = s;
|
||||
}
|
||||
}
|
||||
|
||||
static std::string extractFirstFailingTest(const std::string& text) {
|
||||
std::istringstream iss(text);
|
||||
std::string line;
|
||||
while (std::getline(iss, line)) {
|
||||
auto p = line.find("... FAIL");
|
||||
if (p != std::string::npos) return normalizeWhitespace(line.substr(0, p));
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static FailurePacket make(const std::string& command,
|
||||
const std::string& rawOutput,
|
||||
int exitCode,
|
||||
size_t maxBytes = 8192) {
|
||||
FailurePacket p;
|
||||
p.reproCommand = command;
|
||||
p.failureClass = classify(rawOutput, exitCode);
|
||||
|
||||
std::string deduped = dedupLines(rawOutput);
|
||||
std::string norm = normalizeWhitespace(deduped);
|
||||
p.normalizedMessage = stableTruncate(norm, maxBytes);
|
||||
p.rawExcerpt = stableTruncate(deduped, maxBytes);
|
||||
p.firstFailingTest = extractFirstFailingTest(rawOutput);
|
||||
extractPrimaryLocation(rawOutput, &p.primaryFile, &p.primaryLine, &p.primarySymbol);
|
||||
|
||||
p.confidence = 0.5;
|
||||
if (p.failureClass == "compile_error" || p.failureClass == "test_assertion") p.confidence = 0.9;
|
||||
else if (p.failureClass == "runtime_error") p.confidence = 0.7;
|
||||
|
||||
p.packetId = stableId(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const FailurePacket& p) {
|
||||
return {
|
||||
{"packet_id", p.packetId},
|
||||
{"failure_class", p.failureClass},
|
||||
{"primary_file", p.primaryFile},
|
||||
{"primary_line", p.primaryLine},
|
||||
{"primary_symbol", p.primarySymbol},
|
||||
{"normalized_message", p.normalizedMessage},
|
||||
{"repro_command", p.reproCommand},
|
||||
{"first_failing_test", p.firstFailingTest},
|
||||
{"confidence", p.confidence},
|
||||
{"raw_excerpt", p.rawExcerpt}
|
||||
};
|
||||
}
|
||||
|
||||
static FailurePacket fromJson(const nlohmann::json& j) {
|
||||
FailurePacket p;
|
||||
p.packetId = j.value("packet_id", "");
|
||||
p.failureClass = j.value("failure_class", "unknown");
|
||||
p.primaryFile = j.value("primary_file", "");
|
||||
p.primaryLine = j.value("primary_line", 0);
|
||||
p.primarySymbol = j.value("primary_symbol", "");
|
||||
p.normalizedMessage = j.value("normalized_message", "");
|
||||
p.reproCommand = j.value("repro_command", "");
|
||||
p.firstFailingTest = j.value("first_failing_test", "");
|
||||
p.confidence = j.value("confidence", 0.0);
|
||||
p.rawExcerpt = j.value("raw_excerpt", "");
|
||||
return p;
|
||||
}
|
||||
|
||||
private:
|
||||
static std::string stableId(const FailurePacket& p) {
|
||||
std::string key = p.failureClass + "|" + p.primaryFile + "|" + std::to_string(p.primaryLine) + "|" + p.normalizedMessage;
|
||||
uint64_t h = 1469598103934665603ull;
|
||||
for (unsigned char c : key) {
|
||||
h ^= c;
|
||||
h *= 1099511628211ull;
|
||||
}
|
||||
std::ostringstream oss;
|
||||
oss << "fp_" << std::hex << h;
|
||||
return oss.str();
|
||||
}
|
||||
};
|
||||
67
editor/src/debug/FailurePatchProposer.h
Normal file
67
editor/src/debug/FailurePatchProposer.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
// Step 1453: failure-class-driven patch proposer.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "FailureClusterer.h"
|
||||
#include "FixContextAssembler.h"
|
||||
#include "PatchProposal.h"
|
||||
|
||||
struct FailurePatchSuggestion {
|
||||
PatchProposal proposal;
|
||||
double confidence = 0.0;
|
||||
std::string rationale;
|
||||
};
|
||||
|
||||
class FailurePatchProposer {
|
||||
public:
|
||||
static FailurePatchSuggestion propose(const FailureCluster& cluster,
|
||||
const FixContextPacket& context,
|
||||
const std::string& testTarget = "") {
|
||||
FailurePatchSuggestion s;
|
||||
s.proposal.failureClusterId = cluster.clusterId;
|
||||
|
||||
std::string target = context.slices.empty() ? "editor/src/unknown.h" : context.slices[0].path;
|
||||
std::string strategy = strategyFor(cluster.failureClass);
|
||||
s.rationale = "strategy=" + strategy + " class=" + cluster.failureClass;
|
||||
|
||||
s.proposal.patchPlan.push_back({strategy, target});
|
||||
s.proposal.expectedTestsToRun = testTarget.empty() ? std::vector<std::string>{"step_smoke_test"}
|
||||
: std::vector<std::string>{testTarget};
|
||||
s.proposal.invariants = {
|
||||
"do_not_edit_unrelated_files",
|
||||
"preserve_schema_keys",
|
||||
"keep_output_deterministic"
|
||||
};
|
||||
s.proposal.riskLevel = (cluster.failureClass == "compile_error") ? "low" : "medium";
|
||||
|
||||
s.proposal.candidateDiff =
|
||||
"diff --git a/" + target + " b/" + target + "\n"
|
||||
"--- a/" + target + "\n"
|
||||
"+++ b/" + target + "\n"
|
||||
"@@\n"
|
||||
"+// auto-fix placeholder for " + strategy + "\n";
|
||||
|
||||
s.confidence = confidenceFor(cluster.failureClass);
|
||||
s.proposal.proposalId = PatchProposalModel::stableId(s.proposal);
|
||||
return s;
|
||||
}
|
||||
|
||||
private:
|
||||
static std::string strategyFor(const std::string& failureClass) {
|
||||
if (failureClass == "compile_error") return "missing_include_or_symbol_fix";
|
||||
if (failureClass == "schema_error") return "schema_key_alignment_fix";
|
||||
if (failureClass == "test_assertion") return "assertion_behavior_adjustment_fix";
|
||||
if (failureClass == "tool_contract_error") return "tool_input_contract_fix";
|
||||
return "safe_fallback_review_fix";
|
||||
}
|
||||
|
||||
static double confidenceFor(const std::string& failureClass) {
|
||||
if (failureClass == "compile_error") return 0.85;
|
||||
if (failureClass == "schema_error") return 0.8;
|
||||
if (failureClass == "test_assertion") return 0.75;
|
||||
if (failureClass == "tool_contract_error") return 0.78;
|
||||
return 0.4;
|
||||
}
|
||||
};
|
||||
42
editor/src/debug/FailureTrendTracker.h
Normal file
42
editor/src/debug/FailureTrendTracker.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
// Step 1469: failure trend tracker model.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct FailureTrendPoint {
|
||||
int iteration = 0;
|
||||
int gapScore = 0;
|
||||
};
|
||||
|
||||
struct FailureTrend {
|
||||
std::vector<FailureTrendPoint> points;
|
||||
bool worsening = false;
|
||||
bool improving = false;
|
||||
bool stagnant = false;
|
||||
};
|
||||
|
||||
class FailureTrendTracker {
|
||||
public:
|
||||
static FailureTrend analyze(const std::vector<FailureTrendPoint>& points) {
|
||||
FailureTrend t;
|
||||
t.points = points;
|
||||
if (points.size() < 2) {
|
||||
t.stagnant = true;
|
||||
return t;
|
||||
}
|
||||
int delta = points.back().gapScore - points.front().gapScore;
|
||||
t.worsening = delta > 0;
|
||||
t.improving = delta < 0;
|
||||
t.stagnant = delta == 0;
|
||||
return t;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const FailureTrend& t) {
|
||||
nlohmann::json arr = nlohmann::json::array();
|
||||
for (const auto& p : t.points) arr.push_back({{"iteration", p.iteration}, {"gap_score", p.gapScore}});
|
||||
return {{"points", arr}, {"worsening", t.worsening}, {"improving", t.improving}, {"stagnant", t.stagnant}};
|
||||
}
|
||||
};
|
||||
54
editor/src/debug/FailureTriageScore.h
Normal file
54
editor/src/debug/FailureTriageScore.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
// Step 1510: failure triage score model.
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct FailureTriageScore {
|
||||
std::string failureClass;
|
||||
int severity = 0;
|
||||
int reproducibility = 0;
|
||||
int blastRadius = 0;
|
||||
int score = 0;
|
||||
std::string tier = "low";
|
||||
};
|
||||
|
||||
class FailureTriageScorer {
|
||||
public:
|
||||
static FailureTriageScore compute(const std::string& failureClass,
|
||||
int severity,
|
||||
int reproducibility,
|
||||
int blastRadius) {
|
||||
FailureTriageScore s;
|
||||
s.failureClass = failureClass;
|
||||
s.severity = clamp10(severity);
|
||||
s.reproducibility = clamp10(reproducibility);
|
||||
s.blastRadius = clamp10(blastRadius);
|
||||
s.score = s.severity * 5 + s.reproducibility * 3 + s.blastRadius * 2;
|
||||
s.tier = tierFor(s.score);
|
||||
return s;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const FailureTriageScore& s) {
|
||||
return {
|
||||
{"failure_class", s.failureClass},
|
||||
{"severity", s.severity},
|
||||
{"reproducibility", s.reproducibility},
|
||||
{"blast_radius", s.blastRadius},
|
||||
{"score", s.score},
|
||||
{"tier", s.tier}
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
static int clamp10(int v) { return std::max(0, std::min(10, v)); }
|
||||
|
||||
static std::string tierFor(int score) {
|
||||
if (score >= 70) return "critical";
|
||||
if (score >= 40) return "high";
|
||||
if (score >= 20) return "medium";
|
||||
return "low";
|
||||
}
|
||||
};
|
||||
62
editor/src/debug/FixContextAssembler.h
Normal file
62
editor/src/debug/FixContextAssembler.h
Normal file
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
// Step 1451: fix-context assembler with budget modes.
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct FixContextSlice {
|
||||
std::string path;
|
||||
int startLine = 1;
|
||||
int endLine = 1;
|
||||
std::string text;
|
||||
};
|
||||
|
||||
struct FixContextPacket {
|
||||
std::string mode;
|
||||
int budgetChars = 0;
|
||||
int totalChars = 0;
|
||||
std::vector<FixContextSlice> slices;
|
||||
};
|
||||
|
||||
class FixContextAssembler {
|
||||
public:
|
||||
static int budgetForMode(const std::string& mode) {
|
||||
if (mode == "tiny") return 2000;
|
||||
if (mode == "small") return 6000;
|
||||
return 12000;
|
||||
}
|
||||
|
||||
static FixContextPacket assemble(const std::string& mode,
|
||||
const std::vector<FixContextSlice>& input) {
|
||||
FixContextPacket out;
|
||||
out.mode = mode;
|
||||
out.budgetChars = budgetForMode(mode);
|
||||
|
||||
std::vector<FixContextSlice> sorted = input;
|
||||
std::sort(sorted.begin(), sorted.end(), [](const auto& a, const auto& b) {
|
||||
if (a.path != b.path) return a.path < b.path;
|
||||
return a.startLine < b.startLine;
|
||||
});
|
||||
|
||||
for (const auto& s : sorted) {
|
||||
if (out.totalChars >= out.budgetChars) break;
|
||||
FixContextSlice keep = s;
|
||||
int remaining = out.budgetChars - out.totalChars;
|
||||
if (static_cast<int>(keep.text.size()) > remaining) keep.text = keep.text.substr(0, remaining);
|
||||
out.totalChars += static_cast<int>(keep.text.size());
|
||||
out.slices.push_back(std::move(keep));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const FixContextPacket& p) {
|
||||
nlohmann::json arr = nlohmann::json::array();
|
||||
for (const auto& s : p.slices) {
|
||||
arr.push_back({{"path", s.path}, {"start_line", s.startLine}, {"end_line", s.endLine}, {"text", s.text}});
|
||||
}
|
||||
return {{"mode", p.mode}, {"budget_chars", p.budgetChars}, {"total_chars", p.totalChars}, {"slices", arr}};
|
||||
}
|
||||
};
|
||||
36
editor/src/debug/MinimalReproReducer.h
Normal file
36
editor/src/debug/MinimalReproReducer.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
// Step 1511: minimal repro reducer model.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct MinimalReproResult {
|
||||
std::string originalCommand;
|
||||
std::string reducedCommand;
|
||||
int removedFlags = 0;
|
||||
};
|
||||
|
||||
class MinimalReproReducer {
|
||||
public:
|
||||
static MinimalReproResult reduce(const std::string& command,
|
||||
const std::vector<std::string>& removableFlags) {
|
||||
MinimalReproResult r;
|
||||
r.originalCommand = command;
|
||||
r.reducedCommand = command;
|
||||
for (const auto& f : removableFlags) {
|
||||
std::string needle = " " + f;
|
||||
auto pos = r.reducedCommand.find(needle);
|
||||
if (pos != std::string::npos) {
|
||||
r.reducedCommand.erase(pos, needle.size());
|
||||
++r.removedFlags;
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const MinimalReproResult& r) {
|
||||
return {{"original_command", r.originalCommand}, {"reduced_command", r.reducedCommand}, {"removed_flags", r.removedFlags}};
|
||||
}
|
||||
};
|
||||
25
editor/src/debug/PatchCandidateRanker.h
Normal file
25
editor/src/debug/PatchCandidateRanker.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
// Step 1470: patch candidate ranking with history penalties.
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct RankedPatchCandidate {
|
||||
std::string proposalId;
|
||||
double confidence = 0.0;
|
||||
int attempts = 0;
|
||||
double score = 0.0;
|
||||
};
|
||||
|
||||
class PatchCandidateRanker {
|
||||
public:
|
||||
static std::vector<RankedPatchCandidate> rank(std::vector<RankedPatchCandidate> in) {
|
||||
for (auto& c : in) c.score = c.confidence - 0.1 * c.attempts;
|
||||
std::sort(in.begin(), in.end(), [](const auto& a, const auto& b) {
|
||||
if (a.score != b.score) return a.score > b.score;
|
||||
return a.proposalId < b.proposalId;
|
||||
});
|
||||
return in;
|
||||
}
|
||||
};
|
||||
53
editor/src/debug/PatchDryRunApplier.h
Normal file
53
editor/src/debug/PatchDryRunApplier.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
// Step 1460: patch dry-run applier.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct PatchDryRunResult {
|
||||
bool validDiff = false;
|
||||
bool filesResolvable = false;
|
||||
bool success = false;
|
||||
std::vector<std::string> touchedFiles;
|
||||
std::vector<std::string> errors;
|
||||
};
|
||||
|
||||
class PatchDryRunApplier {
|
||||
public:
|
||||
static PatchDryRunResult run(const std::string& diffText,
|
||||
const std::string& workspaceRoot = "") {
|
||||
(void)workspaceRoot;
|
||||
PatchDryRunResult r;
|
||||
if (diffText.find("diff --git ") == std::string::npos) {
|
||||
r.errors.push_back("diff_missing_header");
|
||||
return r;
|
||||
}
|
||||
r.validDiff = true;
|
||||
|
||||
size_t p = 0;
|
||||
while (true) {
|
||||
p = diffText.find("diff --git a/", p);
|
||||
if (p == std::string::npos) break;
|
||||
size_t start = p + 13;
|
||||
size_t end = diffText.find(" b/", start);
|
||||
if (end == std::string::npos) break;
|
||||
std::string file = diffText.substr(start, end - start);
|
||||
if (!file.empty()) r.touchedFiles.push_back(file);
|
||||
p = end + 3;
|
||||
}
|
||||
if (r.touchedFiles.empty()) {
|
||||
r.errors.push_back("no_touched_files");
|
||||
return r;
|
||||
}
|
||||
r.filesResolvable = true;
|
||||
r.success = r.validDiff && r.filesResolvable;
|
||||
return r;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const PatchDryRunResult& r) {
|
||||
return {{"valid_diff", r.validDiff}, {"files_resolvable", r.filesResolvable}, {"success", r.success},
|
||||
{"touched_files", r.touchedFiles}, {"errors", r.errors}};
|
||||
}
|
||||
};
|
||||
33
editor/src/debug/PatchExecutionAuditBundle.h
Normal file
33
editor/src/debug/PatchExecutionAuditBundle.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
// Step 1487: patch execution audit bundle.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "PatchExecutionRecord.h"
|
||||
|
||||
struct PatchExecutionAuditBundle {
|
||||
std::string sessionId;
|
||||
std::vector<PatchExecutionRecord> records;
|
||||
bool success = false;
|
||||
};
|
||||
|
||||
class PatchExecutionAuditBundleModel {
|
||||
public:
|
||||
static PatchExecutionAuditBundle build(const std::string& sessionId,
|
||||
const std::vector<PatchExecutionRecord>& records) {
|
||||
PatchExecutionAuditBundle b;
|
||||
b.sessionId = sessionId;
|
||||
b.records = records;
|
||||
b.success = !records.empty();
|
||||
return b;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const PatchExecutionAuditBundle& b) {
|
||||
nlohmann::json arr = nlohmann::json::array();
|
||||
for (const auto& r : b.records) arr.push_back(PatchExecutionRecordModel::toJson(r));
|
||||
return {{"session_id", b.sessionId}, {"records", arr}, {"success", b.success}};
|
||||
}
|
||||
};
|
||||
43
editor/src/debug/PatchExecutionRecord.h
Normal file
43
editor/src/debug/PatchExecutionRecord.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
// Step 1479: patch execution record model.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct PatchExecutionRecord {
|
||||
std::string executionId;
|
||||
std::string proposalId;
|
||||
std::string phase; // validated|dry_run|applied|rolled_back
|
||||
bool success = false;
|
||||
std::string detail;
|
||||
};
|
||||
|
||||
class PatchExecutionRecordModel {
|
||||
public:
|
||||
static PatchExecutionRecord make(const std::string& proposalId,
|
||||
const std::string& phase,
|
||||
bool success,
|
||||
const std::string& detail) {
|
||||
PatchExecutionRecord r;
|
||||
r.proposalId = proposalId;
|
||||
r.phase = phase;
|
||||
r.success = success;
|
||||
r.detail = detail;
|
||||
r.executionId = stableId(proposalId, phase, detail);
|
||||
return r;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const PatchExecutionRecord& r) {
|
||||
return {{"execution_id", r.executionId}, {"proposal_id", r.proposalId}, {"phase", r.phase},
|
||||
{"success", r.success}, {"detail", r.detail}};
|
||||
}
|
||||
|
||||
private:
|
||||
static std::string stableId(const std::string& p, const std::string& ph, const std::string& d) {
|
||||
uint64_t h = 5381;
|
||||
std::string key = p + "|" + ph + "|" + d;
|
||||
for (unsigned char c : key) h = ((h << 5) + h) + c;
|
||||
return "pe_" + std::to_string(h);
|
||||
}
|
||||
};
|
||||
39
editor/src/debug/PatchInvariantChecker.h
Normal file
39
editor/src/debug/PatchInvariantChecker.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
// Step 1461: patch invariant checker.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "PatchProposal.h"
|
||||
|
||||
struct InvariantCheckResult {
|
||||
bool success = false;
|
||||
std::vector<std::string> violations;
|
||||
};
|
||||
|
||||
class PatchInvariantChecker {
|
||||
public:
|
||||
static InvariantCheckResult check(const PatchProposal& p,
|
||||
const std::vector<std::string>& allowedFiles) {
|
||||
InvariantCheckResult r;
|
||||
|
||||
if (p.invariants.empty()) r.violations.push_back("invariants_missing");
|
||||
if (p.expectedTestsToRun.empty()) r.violations.push_back("expected_tests_missing");
|
||||
|
||||
std::string err;
|
||||
if (!PatchProposalModel::validate(p, allowedFiles, &err)) r.violations.push_back(err);
|
||||
|
||||
bool hasNoUnrelated = false;
|
||||
for (const auto& i : p.invariants) if (i == "do_not_edit_unrelated_files") hasNoUnrelated = true;
|
||||
if (!hasNoUnrelated) r.violations.push_back("scope_invariant_missing");
|
||||
|
||||
r.success = r.violations.empty();
|
||||
return r;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const InvariantCheckResult& r) {
|
||||
return {{"success", r.success}, {"violations", r.violations}};
|
||||
}
|
||||
};
|
||||
86
editor/src/debug/PatchProposal.h
Normal file
86
editor/src/debug/PatchProposal.h
Normal file
@@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
// Step 1452: patch proposal packet model.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct PatchPlanItem {
|
||||
std::string intent;
|
||||
std::string targetFile;
|
||||
};
|
||||
|
||||
struct PatchProposal {
|
||||
std::string proposalId;
|
||||
std::string failureClusterId;
|
||||
std::vector<PatchPlanItem> patchPlan;
|
||||
std::string candidateDiff;
|
||||
std::string riskLevel = "medium";
|
||||
std::vector<std::string> expectedTestsToRun;
|
||||
std::vector<std::string> invariants;
|
||||
};
|
||||
|
||||
class PatchProposalModel {
|
||||
public:
|
||||
static bool validate(const PatchProposal& p,
|
||||
const std::vector<std::string>& allowedFiles,
|
||||
std::string* error) {
|
||||
if (error) *error = "";
|
||||
if (p.invariants.empty()) {
|
||||
if (error) *error = "invariants_missing";
|
||||
return false;
|
||||
}
|
||||
if (p.candidateDiff.empty() || p.candidateDiff.find("diff --git") == std::string::npos) {
|
||||
if (error) *error = "diff_invalid";
|
||||
return false;
|
||||
}
|
||||
for (const auto& item : p.patchPlan) {
|
||||
bool allowed = false;
|
||||
for (const auto& f : allowedFiles) if (item.targetFile == f) allowed = true;
|
||||
if (!allowed) {
|
||||
if (error) *error = "out_of_scope_edit";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const PatchProposal& p) {
|
||||
nlohmann::json plan = nlohmann::json::array();
|
||||
for (const auto& i : p.patchPlan) plan.push_back({{"intent", i.intent}, {"target_file", i.targetFile}});
|
||||
return {
|
||||
{"proposal_id", p.proposalId},
|
||||
{"failure_cluster_id", p.failureClusterId},
|
||||
{"patch_plan", plan},
|
||||
{"candidate_diff", p.candidateDiff},
|
||||
{"risk_level", p.riskLevel},
|
||||
{"expected_tests_to_run", p.expectedTestsToRun},
|
||||
{"invariants", p.invariants}
|
||||
};
|
||||
}
|
||||
|
||||
static PatchProposal fromJson(const nlohmann::json& j) {
|
||||
PatchProposal p;
|
||||
p.proposalId = j.value("proposal_id", "");
|
||||
p.failureClusterId = j.value("failure_cluster_id", "");
|
||||
p.candidateDiff = j.value("candidate_diff", "");
|
||||
p.riskLevel = j.value("risk_level", "medium");
|
||||
p.expectedTestsToRun = j.value("expected_tests_to_run", std::vector<std::string>{});
|
||||
p.invariants = j.value("invariants", std::vector<std::string>{});
|
||||
for (const auto& it : j.value("patch_plan", nlohmann::json::array())) {
|
||||
p.patchPlan.push_back({it.value("intent", ""), it.value("target_file", "")});
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static std::string stableId(const PatchProposal& p) {
|
||||
std::string key = p.failureClusterId + "|" + p.riskLevel + "|" + std::to_string(p.patchPlan.size());
|
||||
uint64_t h = 1099511628211ull;
|
||||
for (unsigned char c : key) {
|
||||
h ^= c;
|
||||
h *= 1469598103934665603ull;
|
||||
}
|
||||
return "pp_" + std::to_string(h);
|
||||
}
|
||||
};
|
||||
37
editor/src/debug/PatchRiskLabeler.h
Normal file
37
editor/src/debug/PatchRiskLabeler.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
// Step 1512: patch risk labeler model.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct PatchRiskLabel {
|
||||
std::string level = "low";
|
||||
std::vector<std::string> rationale;
|
||||
};
|
||||
|
||||
class PatchRiskLabeler {
|
||||
public:
|
||||
static PatchRiskLabel label(int filesTouched,
|
||||
int lineChanges,
|
||||
bool corePathTouched,
|
||||
bool addsUnsafePattern) {
|
||||
PatchRiskLabel r;
|
||||
if (filesTouched >= 5 || lineChanges >= 80 || corePathTouched || addsUnsafePattern) {
|
||||
r.level = "high";
|
||||
} else if (filesTouched >= 3 || lineChanges >= 30) {
|
||||
r.level = "medium";
|
||||
}
|
||||
if (filesTouched >= 3) r.rationale.push_back("multi_file_change");
|
||||
if (lineChanges >= 30) r.rationale.push_back("large_diff");
|
||||
if (corePathTouched) r.rationale.push_back("core_path_touch");
|
||||
if (addsUnsafePattern) r.rationale.push_back("unsafe_pattern");
|
||||
if (r.rationale.empty()) r.rationale.push_back("contained_change");
|
||||
return r;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const PatchRiskLabel& r) {
|
||||
return {{"level", r.level}, {"rationale", r.rationale}};
|
||||
}
|
||||
};
|
||||
52
editor/src/debug/RegressionGuardPlanner.h
Normal file
52
editor/src/debug/RegressionGuardPlanner.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
// Step 1455: deterministic regression guard planner.
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct RegressionGuardPlan {
|
||||
std::vector<std::string> mustPass;
|
||||
std::vector<std::string> optional;
|
||||
std::vector<std::string> deferred;
|
||||
};
|
||||
|
||||
class RegressionGuardPlanner {
|
||||
public:
|
||||
static RegressionGuardPlan plan(const std::vector<std::string>& touchedFiles,
|
||||
int stepId,
|
||||
const std::string& failingTarget) {
|
||||
RegressionGuardPlan p;
|
||||
if (!failingTarget.empty()) p.mustPass.push_back(failingTarget);
|
||||
|
||||
if (stepId > 1) p.mustPass.push_back("step" + std::to_string(stepId - 1) + "_test");
|
||||
p.mustPass.push_back("step" + std::to_string(stepId) + "_test");
|
||||
p.mustPass.push_back("step" + std::to_string(stepId + 1) + "_test");
|
||||
|
||||
bool mcpTouched = false;
|
||||
for (const auto& f : touchedFiles) if (f.find("MCP") != std::string::npos || f.find("mcp/") != std::string::npos) mcpTouched = true;
|
||||
if (mcpTouched) p.mustPass.push_back("mcp_tools_smoke_test");
|
||||
|
||||
p.optional = {"lint_smoke", "format_smoke"};
|
||||
p.deferred = {"full_regression_suite"};
|
||||
|
||||
dedupSort(&p.mustPass);
|
||||
dedupSort(&p.optional);
|
||||
dedupSort(&p.deferred);
|
||||
return p;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const RegressionGuardPlan& p) {
|
||||
return {{"must_pass", p.mustPass}, {"optional", p.optional}, {"deferred", p.deferred}};
|
||||
}
|
||||
|
||||
private:
|
||||
static void dedupSort(std::vector<std::string>* xs) {
|
||||
if (!xs) return;
|
||||
std::sort(xs->begin(), xs->end());
|
||||
xs->erase(std::unique(xs->begin(), xs->end()), xs->end());
|
||||
}
|
||||
};
|
||||
35
editor/src/debug/ReproCommandRunner.h
Normal file
35
editor/src/debug/ReproCommandRunner.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
// Step 1471: repro command runner facade.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct ReproRunResult {
|
||||
std::string command;
|
||||
int exitCode = 0;
|
||||
std::string output;
|
||||
bool mocked = true;
|
||||
};
|
||||
|
||||
class ReproCommandRunner {
|
||||
public:
|
||||
static ReproRunResult run(const std::string& command) {
|
||||
ReproRunResult r;
|
||||
r.command = command;
|
||||
if (command.rfind("mock:", 0) == 0) {
|
||||
r.output = command.substr(5);
|
||||
r.exitCode = (r.output.find("ok") != std::string::npos) ? 0 : 1;
|
||||
r.mocked = true;
|
||||
return r;
|
||||
}
|
||||
r.output = "runner_non_mock_mode_disabled";
|
||||
r.exitCode = 1;
|
||||
r.mocked = false;
|
||||
return r;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const ReproRunResult& r) {
|
||||
return {{"command", r.command}, {"exit_code", r.exitCode}, {"output", r.output}, {"mocked", r.mocked}};
|
||||
}
|
||||
};
|
||||
80
editor/src/debug/ReproPacketStore.h
Normal file
80
editor/src/debug/ReproPacketStore.h
Normal file
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
// Step 1454: repro packet archive + replay.
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "FailurePacket.h"
|
||||
|
||||
struct ReproPacket {
|
||||
FailurePacket failure;
|
||||
std::string envHash;
|
||||
nlohmann::json fileHashes = nlohmann::json::object();
|
||||
nlohmann::json patchHistory = nlohmann::json::array();
|
||||
std::string reproCommand;
|
||||
};
|
||||
|
||||
class ReproPacketStore {
|
||||
public:
|
||||
static bool save(const std::string& path, const ReproPacket& p) {
|
||||
std::ofstream out(path);
|
||||
if (!out.good()) return false;
|
||||
out << toJson(p).dump(2);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool load(const std::string& path, ReproPacket* out) {
|
||||
if (!out) return false;
|
||||
std::ifstream in(path);
|
||||
if (!in.good()) return false;
|
||||
nlohmann::json j;
|
||||
in >> j;
|
||||
*out = fromJson(j);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool appendHistory(const std::string& path, const nlohmann::json& historyItem) {
|
||||
ReproPacket p;
|
||||
if (!load(path, &p)) return false;
|
||||
p.patchHistory.push_back(historyItem);
|
||||
return save(path, p);
|
||||
}
|
||||
|
||||
static bool replayClassMatches(const ReproPacket& archived,
|
||||
const FailurePacket& current) {
|
||||
return archived.failure.failureClass == current.failureClass &&
|
||||
archived.failure.primaryFile == current.primaryFile;
|
||||
}
|
||||
|
||||
static bool exportJsonl(const std::string& path,
|
||||
const std::vector<ReproPacket>& packets) {
|
||||
std::ofstream out(path);
|
||||
if (!out.good()) return false;
|
||||
for (const auto& p : packets) out << toJson(p).dump() << "\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const ReproPacket& p) {
|
||||
return {
|
||||
{"failure", FailurePacketModel::toJson(p.failure)},
|
||||
{"env_hash", p.envHash},
|
||||
{"file_hashes", p.fileHashes},
|
||||
{"patch_history", p.patchHistory},
|
||||
{"repro_command", p.reproCommand}
|
||||
};
|
||||
}
|
||||
|
||||
static ReproPacket fromJson(const nlohmann::json& j) {
|
||||
ReproPacket p;
|
||||
p.failure = FailurePacketModel::fromJson(j.value("failure", nlohmann::json::object()));
|
||||
p.envHash = j.value("env_hash", "");
|
||||
p.fileHashes = j.value("file_hashes", nlohmann::json::object());
|
||||
p.patchHistory = j.value("patch_history", nlohmann::json::array());
|
||||
p.reproCommand = j.value("repro_command", "");
|
||||
return p;
|
||||
}
|
||||
};
|
||||
38
editor/src/debug/RollbackLedger.h
Normal file
38
editor/src/debug/RollbackLedger.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
// Step 1480: rollback ledger model.
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "PatchExecutionRecord.h"
|
||||
|
||||
class RollbackLedger {
|
||||
public:
|
||||
static bool append(const std::string& path, const PatchExecutionRecord& rec) {
|
||||
nlohmann::json arr = nlohmann::json::array();
|
||||
std::ifstream in(path);
|
||||
if (in.good()) in >> arr;
|
||||
arr.push_back(PatchExecutionRecordModel::toJson(rec));
|
||||
std::ofstream out(path);
|
||||
if (!out.good()) return false;
|
||||
out << arr.dump(2);
|
||||
return true;
|
||||
}
|
||||
|
||||
static nlohmann::json readAll(const std::string& path) {
|
||||
std::ifstream in(path);
|
||||
if (!in.good()) return nlohmann::json::array();
|
||||
nlohmann::json arr;
|
||||
in >> arr;
|
||||
return arr.is_array() ? arr : nlohmann::json::array();
|
||||
}
|
||||
|
||||
static nlohmann::json last(const std::string& path) {
|
||||
auto arr = readAll(path);
|
||||
if (arr.empty()) return nlohmann::json::object();
|
||||
return arr.back();
|
||||
}
|
||||
};
|
||||
33
editor/src/debug/SLMDebugReadiness.h
Normal file
33
editor/src/debug/SLMDebugReadiness.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
// Step 1476: SLM debug readiness scoring.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct SLMDebugReadinessScore {
|
||||
double score = 0.0;
|
||||
std::string grade; // blocked|pilot|ready
|
||||
std::string rationale;
|
||||
};
|
||||
|
||||
class SLMDebugReadinessModel {
|
||||
public:
|
||||
static SLMDebugReadinessScore score(const nlohmann::json& metrics) {
|
||||
SLMDebugReadinessScore out;
|
||||
int acceptance = metrics.value("patch_acceptance_rate_pct", 0);
|
||||
int regression = metrics.value("regression_escape_rate_pct", 100);
|
||||
int compression = metrics.value("symptom_to_root_cause_compression", 0);
|
||||
|
||||
out.score = 0.5 * acceptance + 0.3 * (100 - regression) + 0.2 * std::min(100, compression / 4);
|
||||
if (out.score >= 75) out.grade = "ready";
|
||||
else if (out.score >= 50) out.grade = "pilot";
|
||||
else out.grade = "blocked";
|
||||
out.rationale = "acceptance=" + std::to_string(acceptance) + ", regression=" + std::to_string(regression);
|
||||
return out;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const SLMDebugReadinessScore& r) {
|
||||
return {{"score", r.score}, {"grade", r.grade}, {"rationale", r.rationale}};
|
||||
}
|
||||
};
|
||||
37
editor/src/debug/SafetyEnvelopePolicy.h
Normal file
37
editor/src/debug/SafetyEnvelopePolicy.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
// Step 1491: safety envelope policy.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
struct SafetyEnvelope {
|
||||
int maxFailures = 3;
|
||||
int maxRegressions = 1;
|
||||
int maxTokensPerCampaign = 20000;
|
||||
};
|
||||
|
||||
struct SafetyEnvelopeResult {
|
||||
bool allowed = true;
|
||||
std::vector<std::string> violations;
|
||||
};
|
||||
|
||||
class SafetyEnvelopePolicy {
|
||||
public:
|
||||
static SafetyEnvelopeResult evaluate(const SafetyEnvelope& e,
|
||||
int failures,
|
||||
int regressions,
|
||||
int tokenCost) {
|
||||
SafetyEnvelopeResult r;
|
||||
if (failures > e.maxFailures) r.violations.push_back("max_failures_exceeded");
|
||||
if (regressions > e.maxRegressions) r.violations.push_back("max_regressions_exceeded");
|
||||
if (tokenCost > e.maxTokensPerCampaign) r.violations.push_back("max_tokens_exceeded");
|
||||
r.allowed = r.violations.empty();
|
||||
return r;
|
||||
}
|
||||
|
||||
static nlohmann::json toJson(const SafetyEnvelopeResult& r) {
|
||||
return {{"allowed", r.allowed}, {"violations", r.violations}};
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user