Close all 26 generator readiness gaps (sprints 267-270)
Sprint 268: GR-002/003/006/007/008/016/017/019/021/024 — self-containment, graduation thresholds, asm/ir/jvm/elixir lang coverage, semanno format. Sprint 269: GR-005/011/013/015 — prose capture in RequirementsParser, GateEnforcer, ParitySkewAnalyzer, CrossArtifactConsistencyEngine. Sprint 270: GR-009/010/012/014/018/020 — CrossFileTransactionGate, SemanticCompletionGate, ConstrainedProjectionGate, TokenBudgetGate, CppConstraintRefactorPolicy, NativeDecompositionDepthGuard. All 50 new tests passing (steps 1873-1882, 5/5 per step). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
42
editor/src/ConstrainedProjectionGate.h
Normal file
42
editor/src/ConstrainedProjectionGate.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
// Step 1878: Constrained projection gate.
|
||||
// Closes GR-012: ProjectionGenerator.generate() has no access to EnvironmentSpec,
|
||||
// so generators emit code without checking environment constraints. This gate runs
|
||||
// validateCapabilities + validateEnvAnnotations before generation and blocks on errors.
|
||||
|
||||
#include "EnvironmentSpec.h"
|
||||
#include "ast/ASTNode.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct ProjectionConstraintResult {
|
||||
bool blocked = false;
|
||||
std::vector<CapabilityDiagnostic> diagnostics;
|
||||
};
|
||||
|
||||
class ConstrainedProjectionGate {
|
||||
public:
|
||||
// Validate the AST node against the given environment before generation.
|
||||
// Returns blocked=true when any "error" severity diagnostic is found.
|
||||
// A null env means no constraints — always passes.
|
||||
static ProjectionConstraintResult validate(const ASTNode* node,
|
||||
const EnvironmentSpec* env) {
|
||||
ProjectionConstraintResult result;
|
||||
if (!env) return result; // no env = no constraints
|
||||
|
||||
auto capDiags = validateCapabilities(node, env);
|
||||
auto annDiags = validateEnvAnnotations(node, env);
|
||||
|
||||
for (const auto& d : capDiags) result.diagnostics.push_back(d);
|
||||
for (const auto& d : annDiags) result.diagnostics.push_back(d);
|
||||
|
||||
for (const auto& d : result.diagnostics) {
|
||||
if (d.severity == "error") {
|
||||
result.blocked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
49
editor/src/CppConstraintRefactorPolicy.h
Normal file
49
editor/src/CppConstraintRefactorPolicy.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
// Step 1881c: C++ constrained refactor policy (GR-018).
|
||||
// Closes GR-018: enforcement was contract-level but generator capability under
|
||||
// constraints was weak in the C++ AB path. This policy validates that a C++
|
||||
// refactor proposal satisfies the required constraint signals before the generator
|
||||
// is invoked — preventing constraint-violating code from being emitted.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct RefactorConstraints {
|
||||
bool requiresRollbackPlan = false;
|
||||
bool requiresSecurityReview = false;
|
||||
bool requiresSloP95 = false;
|
||||
bool requiresStagedRollout = false;
|
||||
};
|
||||
|
||||
struct CppRefactorPolicyResult {
|
||||
bool allowed = false;
|
||||
std::vector<std::string> violations;
|
||||
};
|
||||
|
||||
class CppConstraintRefactorPolicy {
|
||||
public:
|
||||
static CppRefactorPolicyResult evaluate(
|
||||
const RefactorConstraints& required,
|
||||
bool hasRollbackPlan,
|
||||
bool hasSecurityReview,
|
||||
bool hasSloP95,
|
||||
bool hasStagedRollout)
|
||||
{
|
||||
CppRefactorPolicyResult result;
|
||||
|
||||
if (required.requiresRollbackPlan && !hasRollbackPlan)
|
||||
result.violations.push_back("missing_rollback_plan");
|
||||
|
||||
if (required.requiresSecurityReview && !hasSecurityReview)
|
||||
result.violations.push_back("missing_security_review");
|
||||
|
||||
if (required.requiresSloP95 && !hasSloP95)
|
||||
result.violations.push_back("missing_slo_p95");
|
||||
|
||||
if (required.requiresStagedRollout && !hasStagedRollout)
|
||||
result.violations.push_back("missing_staged_rollout");
|
||||
|
||||
result.allowed = result.violations.empty();
|
||||
return result;
|
||||
}
|
||||
};
|
||||
63
editor/src/CrossFileTransactionGate.h
Normal file
63
editor/src/CrossFileTransactionGate.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
// Step 1880: Cross-file transaction gate.
|
||||
// Closes GR-009: long-range edits touch multiple files but DeploymentPromotionGate
|
||||
// tracked single environments independently. CrossFileTransactionGate makes
|
||||
// multi-file atomicity explicit: the transaction only promotes if ALL member
|
||||
// files pass their individual promotion check.
|
||||
|
||||
#include "DeploymentPromotionGate.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct FileEditState {
|
||||
std::string fileId;
|
||||
int requiredChecks = 0;
|
||||
int passedChecks = 0;
|
||||
int manualApprovals = 0;
|
||||
int blockingIssues = 0;
|
||||
};
|
||||
|
||||
struct TransactionGateResult {
|
||||
bool promotable = false;
|
||||
std::vector<std::string> blockedFiles;
|
||||
int totalFiles = 0;
|
||||
int readyFiles = 0;
|
||||
};
|
||||
|
||||
class CrossFileTransactionGate {
|
||||
public:
|
||||
static TransactionGateResult evaluate(const std::vector<FileEditState>& files) {
|
||||
TransactionGateResult result;
|
||||
result.totalFiles = static_cast<int>(files.size());
|
||||
|
||||
if (files.empty()) {
|
||||
result.promotable = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
DeploymentPromotionGate gate;
|
||||
std::string error;
|
||||
|
||||
for (const auto& f : files) {
|
||||
EnvironmentGateState state;
|
||||
state.environmentId = f.fileId;
|
||||
state.requiredChecks = f.requiredChecks;
|
||||
state.passedChecks = f.passedChecks;
|
||||
state.manualApprovals = f.manualApprovals;
|
||||
state.blockingIssues = f.blockingIssues;
|
||||
gate.upsert(state, &error);
|
||||
}
|
||||
|
||||
for (const auto& f : files) {
|
||||
if (gate.promotable(f.fileId, &error)) {
|
||||
++result.readyFiles;
|
||||
} else {
|
||||
result.blockedFiles.push_back(f.fileId);
|
||||
}
|
||||
}
|
||||
|
||||
result.promotable = result.blockedFiles.empty();
|
||||
return result;
|
||||
}
|
||||
};
|
||||
33
editor/src/GateEnforcer.h
Normal file
33
editor/src/GateEnforcer.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
// Step 1874: Gate enforcement — block-severity gates halt production.
|
||||
// GateSeverityPolicy classifies gate severity; GateEnforcer evaluates a list
|
||||
// of check results and returns a blocked decision when any "block" gate fails.
|
||||
|
||||
#include "gates/GateSeverityPolicy.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct GateCheckResult {
|
||||
std::string gate;
|
||||
bool passed;
|
||||
};
|
||||
|
||||
struct GateEnforcementResult {
|
||||
bool blocked = false;
|
||||
std::vector<std::string> blockingGates;
|
||||
};
|
||||
|
||||
class GateEnforcer {
|
||||
public:
|
||||
static GateEnforcementResult evaluate(const std::vector<GateCheckResult>& checks) {
|
||||
GateEnforcementResult result;
|
||||
for (const auto& check : checks) {
|
||||
if (!check.passed && GateSeverityPolicy::severityFor(check.gate) == "block") {
|
||||
result.blocked = true;
|
||||
result.blockingGates.push_back(check.gate);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
37
editor/src/NativeDecompositionDepthGuard.h
Normal file
37
editor/src/NativeDecompositionDepthGuard.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
// Step 1881b: Native decomposition depth guard (GR-020).
|
||||
// Closes GR-020: native decomposition task depth was stuck at 2 and retry
|
||||
// did not improve it. This guard enforces a minimum task count per decomposition
|
||||
// attempt and rejects shallow decompositions before they can mask weak generation.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct DecompositionGuardResult {
|
||||
bool accepted = false;
|
||||
std::string reason;
|
||||
int taskCount = 0;
|
||||
int minRequired = 0;
|
||||
};
|
||||
|
||||
class NativeDecompositionDepthGuard {
|
||||
public:
|
||||
static constexpr int kMinTaskCount = 3; // minimum tasks for a valid decomposition
|
||||
|
||||
static DecompositionGuardResult evaluate(const std::vector<std::string>& tasks,
|
||||
int minRequired = kMinTaskCount) {
|
||||
DecompositionGuardResult result;
|
||||
result.taskCount = static_cast<int>(tasks.size());
|
||||
result.minRequired = minRequired;
|
||||
|
||||
if (result.taskCount < minRequired) {
|
||||
result.accepted = false;
|
||||
result.reason = "decomposition_too_shallow";
|
||||
return result;
|
||||
}
|
||||
|
||||
result.accepted = true;
|
||||
result.reason = "depth_sufficient";
|
||||
return result;
|
||||
}
|
||||
};
|
||||
61
editor/src/ParitySkewAnalyzer.h
Normal file
61
editor/src/ParitySkewAnalyzer.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
// Step 1876: Parity skew analyzer — detect cross-language readiness asymmetry.
|
||||
// Closes GR-013: CrossLanguageConsistencyGate evaluates per-request but has no
|
||||
// batch analysis or skew tracking. This adds both.
|
||||
|
||||
#include "CrossLanguageConsistencyGate.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct LanguageReadiness {
|
||||
std::string language;
|
||||
bool ready; // supported && allowed for the requested construct/operation
|
||||
};
|
||||
|
||||
struct ParitySkewResult {
|
||||
int readyCount = 0;
|
||||
int totalCount = 0;
|
||||
float skew = 0.0f; // 0 = all ready, 1 = none ready
|
||||
std::vector<std::string> notReadyLanguages;
|
||||
bool belowThreshold = false; // true when skew > skewThreshold
|
||||
};
|
||||
|
||||
class ParitySkewAnalyzer {
|
||||
public:
|
||||
// Analyze a pre-built readiness list.
|
||||
static ParitySkewResult analyze(const std::vector<LanguageReadiness>& languages,
|
||||
float skewThreshold = 0.5f) {
|
||||
ParitySkewResult result;
|
||||
result.totalCount = static_cast<int>(languages.size());
|
||||
for (const auto& lang : languages) {
|
||||
if (lang.ready) {
|
||||
++result.readyCount;
|
||||
} else {
|
||||
result.notReadyLanguages.push_back(lang.language);
|
||||
}
|
||||
}
|
||||
result.skew = (result.totalCount == 0)
|
||||
? 0.0f
|
||||
: 1.0f - static_cast<float>(result.readyCount) / result.totalCount;
|
||||
result.belowThreshold = result.skew > skewThreshold;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Batch-evaluate a construct+operation across a set of languages via the gate.
|
||||
static ParitySkewResult evaluateConstruct(const std::vector<std::string>& languages,
|
||||
const std::string& constructKind,
|
||||
const std::string& operation,
|
||||
float skewThreshold = 0.5f) {
|
||||
std::vector<LanguageReadiness> readiness;
|
||||
for (const auto& lang : languages) {
|
||||
CrossLanguageRequest req;
|
||||
req.language = lang;
|
||||
req.constructKind = constructKind;
|
||||
req.operation = operation;
|
||||
auto r = CrossLanguageConsistencyGate::evaluate(req);
|
||||
readiness.push_back({lang, r.supported && r.allowed});
|
||||
}
|
||||
return analyze(readiness, skewThreshold);
|
||||
}
|
||||
};
|
||||
185
editor/src/RequirementsParser.h
Normal file
185
editor/src/RequirementsParser.h
Normal file
@@ -0,0 +1,185 @@
|
||||
#pragma once
|
||||
|
||||
#include "IntakeTextUtil.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct ParsedRequirement {
|
||||
std::string text;
|
||||
std::string sourceText;
|
||||
int sourceLine = 0;
|
||||
std::string sectionAnchor;
|
||||
std::string sectionTitle;
|
||||
bool isField = false;
|
||||
std::string fieldName;
|
||||
std::string fieldType;
|
||||
};
|
||||
|
||||
class RequirementsParser {
|
||||
public:
|
||||
static std::vector<ParsedRequirement> parse(const std::string& markdown) {
|
||||
std::vector<ParsedRequirement> out;
|
||||
if (markdown.empty()) return out;
|
||||
|
||||
std::istringstream stream(markdown);
|
||||
std::string line;
|
||||
std::string sectionTitle;
|
||||
std::string sectionAnchor;
|
||||
int lineNo = 0;
|
||||
|
||||
while (std::getline(stream, line)) {
|
||||
++lineNo;
|
||||
std::string trimmed = trim(line);
|
||||
if (isHeading(trimmed)) {
|
||||
sectionTitle = trimHeading(trimmed);
|
||||
sectionAnchor = anchorize(sectionTitle);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isRequirementHeading(sectionAnchor)) continue;
|
||||
|
||||
if (isBullet(trimmed)) {
|
||||
std::string bulletText = trim(trimmed.substr(2));
|
||||
if (bulletText.empty()) continue;
|
||||
if (tryParseFieldList(bulletText, lineNo, sectionTitle, sectionAnchor, &out)) continue;
|
||||
ParsedRequirement req;
|
||||
req.text = normalizeText(bulletText);
|
||||
req.sourceText = bulletText;
|
||||
req.sourceLine = lineNo;
|
||||
req.sectionAnchor = sectionAnchor;
|
||||
req.sectionTitle = sectionTitle;
|
||||
out.push_back(req);
|
||||
} else if (isProseLine(trimmed)) {
|
||||
ParsedRequirement req;
|
||||
req.text = normalizeText(trimmed);
|
||||
req.sourceText = trimmed;
|
||||
req.sourceLine = lineNo;
|
||||
req.sectionAnchor = sectionAnchor;
|
||||
req.sectionTitle = sectionTitle;
|
||||
out.push_back(req);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::string anchorize(const std::string& heading) {
|
||||
std::string out;
|
||||
bool prevDash = false;
|
||||
for (unsigned char ch : heading) {
|
||||
if (std::isalnum(ch)) {
|
||||
out.push_back(static_cast<char>(std::tolower(ch)));
|
||||
prevDash = false;
|
||||
} else if (!out.empty() && !prevDash) {
|
||||
out.push_back('-');
|
||||
prevDash = true;
|
||||
}
|
||||
}
|
||||
while (!out.empty() && out.back() == '-') out.pop_back();
|
||||
return out;
|
||||
}
|
||||
|
||||
static bool isRequirementHeading(const std::string& anchor) {
|
||||
return anchor == "requirements" ||
|
||||
anchor == "features" ||
|
||||
anchor == "behavior" ||
|
||||
anchor == "behaviour" ||
|
||||
anchor == "interface" ||
|
||||
anchor == "api";
|
||||
}
|
||||
|
||||
private:
|
||||
static std::string trim(const std::string& value) {
|
||||
std::size_t begin = 0;
|
||||
while (begin < value.size() && std::isspace(static_cast<unsigned char>(value[begin]))) ++begin;
|
||||
std::size_t end = value.size();
|
||||
while (end > begin && std::isspace(static_cast<unsigned char>(value[end - 1]))) --end;
|
||||
return value.substr(begin, end - begin);
|
||||
}
|
||||
|
||||
static std::string normalizeText(const std::string& text) {
|
||||
return intakeCompactLowerAlnumSpaces(text);
|
||||
}
|
||||
|
||||
static bool isHeading(const std::string& line) {
|
||||
if (line.size() < 2) return false;
|
||||
if (line[0] != '#') return false;
|
||||
std::size_t hashes = 0;
|
||||
while (hashes < line.size() && line[hashes] == '#') ++hashes;
|
||||
return hashes > 0 && hashes < line.size() && line[hashes] == ' ';
|
||||
}
|
||||
|
||||
static std::string trimHeading(const std::string& headingLine) {
|
||||
std::size_t i = 0;
|
||||
while (i < headingLine.size() && headingLine[i] == '#') ++i;
|
||||
if (i < headingLine.size() && headingLine[i] == ' ') ++i;
|
||||
return trim(headingLine.substr(i));
|
||||
}
|
||||
|
||||
static bool isBullet(const std::string& line) {
|
||||
if (line.size() < 3) return false;
|
||||
return (line[0] == '-' || line[0] == '*') && line[1] == ' ';
|
||||
}
|
||||
|
||||
static bool isProseLine(const std::string& trimmed) {
|
||||
if (trimmed.size() < 10) return false;
|
||||
if (isHeading(trimmed)) return false;
|
||||
if (trimmed[0] == '`' || trimmed[0] == '|' || trimmed[0] == '>') return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::string maybeFieldType(const std::string& token) {
|
||||
std::size_t l = token.find('(');
|
||||
std::size_t r = token.rfind(')');
|
||||
if (l == std::string::npos || r == std::string::npos || r <= l) return "";
|
||||
return trim(token.substr(l + 1, r - l - 1));
|
||||
}
|
||||
|
||||
static std::string fieldNameFromToken(const std::string& token) {
|
||||
std::string work = trim(token);
|
||||
std::size_t l = work.find('(');
|
||||
if (l != std::string::npos) work = trim(work.substr(0, l));
|
||||
return work;
|
||||
}
|
||||
|
||||
static bool tryParseFieldList(const std::string& bulletText,
|
||||
int sourceLine,
|
||||
const std::string& sectionTitle,
|
||||
const std::string& sectionAnchor,
|
||||
std::vector<ParsedRequirement>* out) {
|
||||
std::size_t colon = bulletText.find(':');
|
||||
if (colon == std::string::npos) return false;
|
||||
|
||||
std::string prefix = trim(bulletText.substr(0, colon));
|
||||
std::string lowerPrefix = intakeToLower(prefix);
|
||||
if (lowerPrefix.find(" has") == std::string::npos && lowerPrefix != "has") return false;
|
||||
|
||||
std::string rhs = trim(bulletText.substr(colon + 1));
|
||||
if (rhs.find(',') == std::string::npos) return false;
|
||||
std::stringstream ss(rhs);
|
||||
std::string token;
|
||||
bool emitted = false;
|
||||
while (std::getline(ss, token, ',')) {
|
||||
std::string name = fieldNameFromToken(token);
|
||||
if (name.empty()) continue;
|
||||
|
||||
ParsedRequirement req;
|
||||
req.isField = true;
|
||||
req.fieldName = name;
|
||||
req.fieldType = maybeFieldType(token);
|
||||
req.sourceText = trim(token);
|
||||
req.text = normalizeText(prefix + " " + name + (req.fieldType.empty() ? "" : " " + req.fieldType));
|
||||
req.sourceLine = sourceLine;
|
||||
req.sectionAnchor = sectionAnchor;
|
||||
req.sectionTitle = sectionTitle;
|
||||
out->push_back(req);
|
||||
emitted = true;
|
||||
}
|
||||
|
||||
return emitted;
|
||||
}
|
||||
};
|
||||
@@ -10,6 +10,7 @@
|
||||
struct ScoredTaskitem {
|
||||
std::string taskId;
|
||||
int score = 0;
|
||||
int executionSpecificityScore = 0;
|
||||
bool selfContained = false;
|
||||
std::vector<std::string> issues;
|
||||
};
|
||||
@@ -21,6 +22,11 @@ struct TaskitemInput {
|
||||
std::vector<std::string> reasons;
|
||||
int confidence = 0;
|
||||
std::vector<std::string> dependencyTaskIds;
|
||||
int executionSpecificityScore = 0;
|
||||
bool strictExecutionContract = false;
|
||||
bool capabilityCompileFailed = false;
|
||||
bool capabilityTestsFailed = false;
|
||||
bool capabilityToolchainMissing = false;
|
||||
};
|
||||
|
||||
class SelfContainmentScorer {
|
||||
@@ -54,6 +60,9 @@ public:
|
||||
if (item.reasons.empty()) {
|
||||
scoreValue -= 20;
|
||||
out.issues.push_back("reasons missing or empty");
|
||||
} else if (item.reasons.size() == 1) {
|
||||
scoreValue -= 25;
|
||||
out.issues.push_back("reasons has fewer than 2 entries");
|
||||
} else if (item.reasons.size() < 3) {
|
||||
scoreValue -= 10;
|
||||
out.issues.push_back("reasons has fewer than 3 entries");
|
||||
@@ -74,9 +83,25 @@ public:
|
||||
out.issues.push_back("dependencyTaskIds not empty");
|
||||
}
|
||||
|
||||
if (item.strictExecutionContract) {
|
||||
if (item.executionSpecificityScore < 60) {
|
||||
scoreValue -= 25;
|
||||
out.issues.push_back("executionSpecificityScore below strict threshold");
|
||||
}
|
||||
if (item.executionSpecificityScore == 0) {
|
||||
scoreValue -= 10;
|
||||
out.issues.push_back("execution contract metadata missing");
|
||||
}
|
||||
}
|
||||
|
||||
if (item.capabilityCompileFailed || item.capabilityTestsFailed || item.capabilityToolchainMissing) {
|
||||
scoreValue -= 25;
|
||||
out.issues.push_back("capability_signals indicate generator/runtime failure");
|
||||
}
|
||||
|
||||
out.score = std::clamp(scoreValue, 0, 100);
|
||||
out.executionSpecificityScore = std::clamp(item.executionSpecificityScore, 0, 100);
|
||||
out.selfContained = out.score >= 80;
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
50
editor/src/SemanticCompletionGate.h
Normal file
50
editor/src/SemanticCompletionGate.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
// Step 1879: Semantic completion gate.
|
||||
// Closes GR-010: ImprovementPromotionGate had a threshold of 0.02 (nearly anything passes)
|
||||
// and no depth tracking. SemanticCompletionGate enforces a meaningful semantic score
|
||||
// threshold and caps recursive completion depth to prevent unbounded iteration.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct SemanticCompletionResult {
|
||||
bool passed = false;
|
||||
std::string reason;
|
||||
int depth = 0;
|
||||
float score = 0.0f;
|
||||
};
|
||||
|
||||
class SemanticCompletionGate {
|
||||
public:
|
||||
static constexpr float kMinScore = 0.70f;
|
||||
static constexpr int kMaxDepth = 5;
|
||||
|
||||
// Evaluate whether a completion attempt should be promoted or retried.
|
||||
// score: semantic equivalence/coverage score in [0,1]
|
||||
// depth: current recursive completion depth (starts at 0)
|
||||
// humanApproved: whether human review has been applied
|
||||
static SemanticCompletionResult evaluate(float score,
|
||||
int depth,
|
||||
bool humanApproved = false) {
|
||||
SemanticCompletionResult result;
|
||||
result.score = score;
|
||||
result.depth = depth;
|
||||
|
||||
if (depth > kMaxDepth) {
|
||||
result.passed = false;
|
||||
result.reason = "max_depth_exceeded";
|
||||
return result;
|
||||
}
|
||||
|
||||
if (score < kMinScore) {
|
||||
result.passed = false;
|
||||
result.reason = "score_below_semantic_threshold";
|
||||
return result;
|
||||
}
|
||||
|
||||
// At or above threshold: pass if human-approved or on a stable path
|
||||
result.passed = humanApproved || score >= 0.90f;
|
||||
result.reason = result.passed ? "accepted" : "human_approval_required";
|
||||
return result;
|
||||
}
|
||||
};
|
||||
44
editor/src/TokenBudgetGate.h
Normal file
44
editor/src/TokenBudgetGate.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
// Step 1881a: Token budget gate (GR-014).
|
||||
// Closes GR-014: low-yield (token-bloated) runs had no hard enforcement.
|
||||
// TokenBudgetGate uses ContextWindowOptimizer to estimate tokens and blocks
|
||||
// when a generation output would exceed the allowed budget.
|
||||
|
||||
#include "ContextWindowOptimizer.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
struct TokenBudgetResult {
|
||||
bool blocked = false;
|
||||
int estimatedTokens = 0;
|
||||
int budgetTokens = 0;
|
||||
float yieldRatio = 0.0f; // output useful content / total tokens
|
||||
};
|
||||
|
||||
class TokenBudgetGate {
|
||||
public:
|
||||
static constexpr float kMinYieldRatio = 0.30f; // at least 30% of tokens must be useful
|
||||
|
||||
// Block if the output text exceeds the budget or yield is too low.
|
||||
static TokenBudgetResult evaluate(const std::string& outputText,
|
||||
int contextWindowTokens,
|
||||
int usefulContentTokens = -1) {
|
||||
TokenBudgetResult result;
|
||||
result.estimatedTokens = ContextWindowOptimizer::estimateTokens(outputText);
|
||||
result.budgetTokens = ContextWindowOptimizer::budgetForContextWindow(contextWindowTokens);
|
||||
|
||||
if (result.estimatedTokens > result.budgetTokens) {
|
||||
result.blocked = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Yield check: if caller provides useful content token count
|
||||
if (usefulContentTokens >= 0 && result.estimatedTokens > 0) {
|
||||
result.yieldRatio = static_cast<float>(usefulContentTokens) / result.estimatedTokens;
|
||||
if (result.yieldRatio < kMinYieldRatio) {
|
||||
result.blocked = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
86
editor/src/graduation/CrossArtifactConsistencyEngine.h
Normal file
86
editor/src/graduation/CrossArtifactConsistencyEngine.h
Normal file
@@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
// Step 1875: Cross-artifact consistency engine.
|
||||
// Checks that spec goal keywords appear in generated taskitem intents.
|
||||
// Closes GR-015: previously CrossArtifactConsistencyChecker was a data stub;
|
||||
// this provides the actual evaluation logic.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct ConsistencyResult {
|
||||
int inconsistencyCount = 0;
|
||||
bool consistent = false;
|
||||
std::vector<std::string> driftedGoals;
|
||||
};
|
||||
|
||||
class CrossArtifactConsistencyEngine {
|
||||
public:
|
||||
// Check that each spec goal has at least one meaningful keyword present
|
||||
// in at least one taskitem intent. Goals with no coverage are drift.
|
||||
static ConsistencyResult check(const std::vector<std::string>& specGoals,
|
||||
const std::vector<std::string>& taskitemIntents) {
|
||||
ConsistencyResult result;
|
||||
for (const auto& goal : specGoals) {
|
||||
if (!anyIntentCoversGoal(goal, taskitemIntents)) {
|
||||
++result.inconsistencyCount;
|
||||
result.driftedGoals.push_back(goal);
|
||||
}
|
||||
}
|
||||
result.consistent = result.inconsistencyCount == 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
static std::vector<std::string> tokenize(const std::string& text) {
|
||||
std::vector<std::string> tokens;
|
||||
std::string tok;
|
||||
for (unsigned char c : text) {
|
||||
if (std::isalnum(c)) {
|
||||
tok += static_cast<char>(std::tolower(c));
|
||||
} else if (!tok.empty()) {
|
||||
tokens.push_back(tok);
|
||||
tok.clear();
|
||||
}
|
||||
}
|
||||
if (!tok.empty()) tokens.push_back(tok);
|
||||
return tokens;
|
||||
}
|
||||
|
||||
static bool isStopWord(const std::string& tok) {
|
||||
static const std::vector<std::string> stops = {
|
||||
"the", "and", "must", "should", "will", "that", "with", "for", "are", "has"
|
||||
};
|
||||
return std::find(stops.begin(), stops.end(), tok) != stops.end();
|
||||
}
|
||||
|
||||
static std::string toLower(const std::string& s) {
|
||||
std::string out = s;
|
||||
for (auto& c : out) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
return out;
|
||||
}
|
||||
|
||||
static bool anyIntentCoversGoal(const std::string& goal,
|
||||
const std::vector<std::string>& intents) {
|
||||
auto goalTokens = tokenize(goal);
|
||||
goalTokens.erase(
|
||||
std::remove_if(goalTokens.begin(), goalTokens.end(), isStopWord),
|
||||
goalTokens.end());
|
||||
|
||||
// Only consider tokens of meaningful length (avoid short noise words)
|
||||
std::vector<std::string> meaningful;
|
||||
for (const auto& tok : goalTokens) {
|
||||
if (tok.size() >= 4) meaningful.push_back(tok);
|
||||
}
|
||||
if (meaningful.empty()) return true; // no meaningful tokens → not flagged
|
||||
|
||||
// A goal is covered if at least one meaningful token appears in any intent
|
||||
for (const auto& tok : meaningful) {
|
||||
for (const auto& intent : intents) {
|
||||
if (toLower(intent).find(tok) != std::string::npos) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user