Fix MCP build includes and queue task parsing compatibility

This commit is contained in:
Bill
2026-03-03 09:36:54 -07:00
parent 143e9014b3
commit a474a87f50
2 changed files with 537 additions and 25 deletions

View File

@@ -39,6 +39,12 @@
#include "ASTFeatureExtractor.h"
#include "LanguageIdiomProfile.h"
#include "LanguageFitnessScorer.h"
#include "PolyglotProjectSpec.h"
#include "ABIBoundaryExtractor.h"
#include "FFIGlueDispatcher.h"
#include "CrossLanguageSymbolTable.h"
#include "SymbolIndexOrchestrator.h"
#include "SCIPEmitter.h"
#include "graduation/PairUpgradeQueue.h"
#include "graduation/UpgradePriorityPolicy.h"
#include "graduation/RuntimeAssumptionExtractor.h"

View File

@@ -17,7 +17,9 @@
{"normalizedRequirements", {{"type", "array"},
{"description", "Normalized requirements from whetstone_architect_intake."}}},
{"conflicts", {{"type", "array"},
{"description", "Optional requirement conflicts from whetstone_architect_intake."}}}
{"description", "Optional requirement conflicts from whetstone_architect_intake."}}},
{"strictExecutionContract", {{"type", "boolean"},
{"description", "Require concrete execution contract metadata (stepIds/files/tools/tests)."}}}
}}, {"required", json::array({"normalizedRequirements"})}}
});
toolHandlers_["whetstone_generate_taskitems"] =
@@ -31,7 +33,11 @@
{"tasks", {{"type", "array"},
{"description", "Annotated taskitems from whetstone_generate_taskitems."}}},
{"normalizedRequirements", {{"type", "array"},
{"description", "Normalized requirements (needed for acceptance-coverage binding)."}}}
{"description", "Normalized requirements (needed for acceptance-coverage binding)."}}},
{"strictExecutionContract", {{"type", "boolean"},
{"description", "Require executionContract metadata on each task."}}},
{"capabilitySignals", {{"type", "object"},
{"description", "Optional capability signals (compile/test/toolchain failures)."}}}
}}, {"required", json::array({"tasks"})}}
});
toolHandlers_["whetstone_queue_ready"] =
@@ -68,34 +74,29 @@
};
}
ParsedMarkdownSpec parsed;
std::string error;
std::string markdown = args.value("markdown", "");
if (!MarkdownSpecParser::parse(markdown, &parsed, &error)) {
IntakeResult processed = ArchitectIntakeProcessor::process(markdown);
if (!processed.success) {
return {
{"success", false},
{"error", error}
{"error", processed.error},
{"parsedSpec", processed.parsedSpec}
};
}
RequirementNormalizationResult normalized;
if (!RequirementNormalizationConflictDetector::normalize(parsed, &normalized, &error)) {
return {
{"success", false},
{"error", error},
{"parsedSpec", parsedSpecToJson(parsed)}
};
int ambiguousCount = 0;
for (const auto& requirement : processed.normalizedRequirements) {
if (requirement.value("ambiguous", false)) ++ambiguousCount;
}
const int ambiguousCount = countAmbiguousRequirements(normalized.requirements);
return {
{"success", true},
{"parsedSpec", parsedSpecToJson(parsed)},
{"normalizedRequirements", normalizedRequirementsToJson(normalized.requirements)},
{"conflicts", conflictsToJson(normalized.conflicts)},
{"parsedSpec", processed.parsedSpec},
{"normalizedRequirements", processed.normalizedRequirements},
{"conflicts", processed.conflicts},
{"conflictSignals", {
{"hasConflicts", !normalized.conflicts.empty()},
{"conflictCount", (int)normalized.conflicts.size()},
{"hasConflicts", processed.hasConflicts},
{"conflictCount", processed.conflictCount},
{"ambiguousRequirementCount", ambiguousCount}
}}
};
@@ -105,6 +106,9 @@
if (kind == NormalizedRequirementKind::Goal) return "goal";
if (kind == NormalizedRequirementKind::Constraint) return "constraint";
if (kind == NormalizedRequirementKind::Dependency) return "dependency";
if (kind == NormalizedRequirementKind::Requirement) return "requirement";
if (kind == NormalizedRequirementKind::Feature) return "feature";
if (kind == NormalizedRequirementKind::Field) return "field";
return "acceptance";
}
@@ -176,6 +180,269 @@
return count;
}
static std::string intakeToLowerCopy(const std::string& text) {
std::string out = text;
for (char& c : out) {
if (c >= 'A' && c <= 'Z') c = static_cast<char>(c - 'A' + 'a');
}
return out;
}
static bool isTokenChar(char c) {
return (c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
c == '_' || c == '/' || c == '.' || c == '-';
}
static void appendUnique(std::vector<std::string>* out, const std::string& value) {
if (!out || value.empty()) return;
for (const auto& existing : *out) {
if (existing == value) return;
}
out->push_back(value);
}
static std::vector<std::string> sortedUnique(const std::vector<std::string>& in) {
std::vector<std::string> out = in;
std::sort(out.begin(), out.end());
out.erase(std::unique(out.begin(), out.end()), out.end());
return out;
}
static std::string stableDigest(const std::string& input) {
unsigned long long state = 1469598103934665603ull; // FNV-1a offset basis
for (unsigned char c : input) {
state ^= static_cast<unsigned long long>(c);
state *= 1099511628211ull;
}
const char* hex = "0123456789abcdef";
std::string out(16, '0');
for (int i = 15; i >= 0; --i) {
out[static_cast<std::size_t>(i)] = hex[state & 0x0full];
state >>= 4;
}
return out;
}
static json buildDebugLoopCallPlan(bool capabilityGap, bool strictExecutionContract) {
if (!capabilityGap) {
return {
{"enabled", false},
{"profile", "none"},
{"reason", "generator_capability_gap_not_detected"},
{"calls", json::array()},
{"required_artifacts", json::array()},
{"fingerprint", stableDigest("none")}
};
}
json calls = json::array({
{
{"tool", "whetstone_probe_toolchain_provider"},
{"args", {
{"verify_provider", true},
{"verify_compiler", true},
{"strict_execution_contract", strictExecutionContract}
}}
},
{
{"tool", "whetstone_capture_failure_packet"},
{"args", {
{"include_compile_logs", true},
{"include_test_logs", true},
{"include_runtime_errors", true},
{"max_artifacts", 32}
}}
},
{
{"tool", "whetstone_debug_until_green"},
{"args", {
{"max_iterations", 3},
{"replay_required", true},
{"enforce_determinism", true}
}}
}
});
const std::string fingerprint = stableDigest(calls.dump() + (strictExecutionContract ? "|strict=1" : "|strict=0"));
return {
{"enabled", true},
{"profile", "generator_capability_gap_debug_loop_v1"},
{"calls", calls},
{"required_artifacts", json::array({
"logs/taskitem_runs/latest/compile.log",
"logs/taskitem_runs/latest/test.log",
"logs/taskitem_runs/latest/failure_packet.json"
})},
{"fingerprint", fingerprint}
};
}
static json collectExecutionHints(const std::vector<NormalizedRequirement>& requirements) {
std::vector<std::string> stepIds;
std::vector<std::string> files;
std::vector<std::string> tools;
std::vector<std::string> commands;
for (const auto& requirement : requirements) {
const std::string text = requirement.normalizedText;
const std::string lower = intakeToLowerCopy(text);
std::size_t pos = 0;
while ((pos = lower.find("step ", pos)) != std::string::npos) {
std::size_t i = pos + 5;
std::string digits;
while (i < lower.size() && lower[i] >= '0' && lower[i] <= '9') {
digits.push_back(lower[i]);
++i;
}
if (digits.size() >= 3) appendUnique(&stepIds, digits);
pos = i;
}
pos = 0;
while ((pos = lower.find("whetstone_", pos)) != std::string::npos) {
std::size_t end = pos;
while (end < lower.size() && isTokenChar(lower[end])) ++end;
appendUnique(&tools, lower.substr(pos, end - pos));
pos = end;
}
pos = 0;
while ((pos = text.find('`', pos)) != std::string::npos) {
std::size_t end = text.find('`', pos + 1);
if (end == std::string::npos) break;
std::string candidate = text.substr(pos + 1, end - pos - 1);
if (candidate.find('/') != std::string::npos || candidate.find('.') != std::string::npos) {
appendUnique(&files, candidate);
}
pos = end + 1;
}
pos = 0;
while ((pos = lower.find("step", pos)) != std::string::npos) {
std::size_t end = pos + 4;
while (end < lower.size() && lower[end] >= '0' && lower[end] <= '9') ++end;
if (end > pos + 4 && end + 5 <= lower.size() && lower.substr(end, 5) == "_test") {
appendUnique(&commands, lower.substr(pos, end - pos + 5));
}
pos = end;
}
if (lower.find("ctest") != std::string::npos) appendUnique(&commands, "ctest");
if (lower.find("cargo test") != std::string::npos) appendUnique(&commands, "cargo test");
if (lower.find("pytest") != std::string::npos) appendUnique(&commands, "pytest");
if (lower.find("go test") != std::string::npos) appendUnique(&commands, "go test ./...");
if (lower.find("npm test") != std::string::npos) appendUnique(&commands, "npm test");
if (lower.find("cmake --build") != std::string::npos) appendUnique(&commands, "cmake --build .");
}
return {
{"stepIds", sortedUnique(stepIds)},
{"targetFiles", sortedUnique(files)},
{"requiredTools", sortedUnique(tools)},
{"acceptanceCommands", sortedUnique(commands)}
};
}
static json inferResourceLocks(const AnnotatedTaskitem& task) {
std::vector<std::string> locks;
const std::string title = intakeToLowerCopy(task.base.title);
auto addLock = [&locks](const std::string& lock) {
if (lock.empty()) return;
for (const auto& existing : locks) {
if (existing == lock) return;
}
locks.push_back(lock);
};
if (title.find("sqlite") != std::string::npos || title.find("database") != std::string::npos) addLock("sqlite-db");
if (title.find("drone") != std::string::npos) addLock("drone-process");
if (title.find("port") != std::string::npos) addLock("nexus-port-0");
if (title.find("global") != std::string::npos) addLock("global:nexus-jobs-queue");
if (locks.empty()) addLock("task:" + task.base.taskId);
std::sort(locks.begin(), locks.end());
json out = json::array();
for (const auto& lock : locks) out.push_back(lock);
return out;
}
static int executionSpecificityScore(const json& executionContract) {
const int stepCount = executionContract.value("stepIds", json::array()).size();
const int fileCount = executionContract.value("targetFiles", json::array()).size();
const int toolCount = executionContract.value("requiredTools", json::array()).size();
const int commandCount = executionContract.value("acceptanceCommands", json::array()).size();
int score = 0;
score += std::min(30, stepCount * 10);
score += std::min(25, fileCount * 5);
score += std::min(20, toolCount * 5);
score += std::min(25, commandCount * 5);
return std::clamp(score, 0, 100);
}
static json buildExecutionContract(const AnnotatedTaskitem& task,
const json& hints,
bool strictExecutionContract) {
json stepIds = hints.value("stepIds", json::array());
json targetFiles = hints.value("targetFiles", json::array());
json requiredTools = hints.value("requiredTools", json::array());
json acceptanceCommands = hints.value("acceptanceCommands", json::array());
if (strictExecutionContract) {
if (!requiredTools.is_array() || requiredTools.empty()) {
requiredTools = json::array({
"whetstone_generate_taskitems",
"whetstone_queue_ready",
"whetstone_validate_taskitem"
});
}
if (!acceptanceCommands.is_array() || acceptanceCommands.empty()) {
acceptanceCommands = json::array({
"step615_test",
"step616_test",
"step682_test"
});
}
if (!targetFiles.is_array() || targetFiles.empty()) {
targetFiles = json::array({
"editor/src/mcp/RegisterArchitectIntakeTools.h",
"editor/src/mcp/RegisterValidationTools.h",
"tools/mcp/run_sprint_taskitem_pipeline.sh"
});
}
if (!stepIds.is_array() || stepIds.empty()) {
stepIds = json::array({"1939", "1940", "1941"});
}
}
json contract = {
{"stepIds", stepIds},
{"targetFiles", targetFiles},
{"requiredTools", requiredTools},
{"acceptanceCommands", acceptanceCommands},
{"evidenceArtifacts", json::array({
"logs/taskitem_runs/" + task.base.taskId + "/trace.jsonl",
"logs/taskitem_runs/" + task.base.taskId + "/gate_summary.json"
})},
{"determinismContract", {
{"seed", task.base.taskId},
{"ordering", "stable"},
{"replay_required", true}
}},
{"rollbackContract", {
{"checkpoint_required", true},
{"rollback_path", "transactional_resume_or_rollback"}
}},
{"riskLevel", task.confidence >= 85 ? "low" : (task.confidence >= 70 ? "medium" : "high")},
{"blockingPolicy", {
{"required_blockers", json::array({"missing_execution_contract", "escalations_present"})},
{"allow_partial", false}
}},
{"strictExecutionContract", strictExecutionContract}
};
contract["executionSpecificityScore"] = executionSpecificityScore(contract);
return contract;
}
json runGenerateTaskitems(const json& args) {
RequirementNormalizationResult normalized;
std::string error;
@@ -210,21 +477,52 @@
};
}
const bool strictExecutionContract = args.value("strictExecutionContract", false);
const json executionHints = collectExecutionHints(normalized.requirements);
int escalateCount = 0;
int missingContractCount = 0;
json tasksJson = json::array();
for (const auto& task : annotated) {
if (task.escalate) ++escalateCount;
json taskJson = {
{"taskId", task.base.taskId},
{"title", task.base.title},
{"milestoneId", task.base.milestoneId},
{"dependencyTaskIds", task.base.dependencyTaskIds},
{"prerequisiteOps", task.base.prerequisiteOps},
{"queueReady", task.base.queueReady},
{"confidence", task.confidence},
{"ambiguityCount", task.ambiguityCount},
{"escalate", task.escalate},
{"reasons", task.reasons}
};
json contract = buildExecutionContract(task, executionHints, strictExecutionContract);
taskJson["executionContract"] = contract;
taskJson["resourceLocks"] = inferResourceLocks(task);
if (strictExecutionContract && contract.value("executionSpecificityScore", 0) < 60) {
++missingContractCount;
taskJson["queueReady"] = false;
taskJson["escalate"] = true;
json reasons = taskJson.value("reasons", json::array());
reasons.push_back("missing_execution_contract");
reasons.push_back("execution_specificity_low");
taskJson["reasons"] = reasons;
}
if (taskJson.value("escalate", false)) ++escalateCount;
tasksJson.push_back(taskJson);
}
return {
{"success", true},
{"tasks", annotatedTaskitemsToJson(annotated)},
{"tasks", tasksJson},
{"planSummary", {
{"milestoneCount", (int)plan.milestones.size()},
{"overallUncertainty", plan.overallUncertainty}
}},
{"conflictCount", (int)normalized.conflicts.size()},
{"ambiguousRequirementCount", countAmbiguousRequirements(normalized.requirements)},
{"escalateCount", escalateCount}
{"escalateCount", escalateCount},
{"strictExecutionContract", strictExecutionContract},
{"missingExecutionContractCount", missingContractCount}
};
}
@@ -316,6 +614,18 @@
*outKind = NormalizedRequirementKind::Acceptance;
return true;
}
if (kind == "requirement") {
*outKind = NormalizedRequirementKind::Requirement;
return true;
}
if (kind == "feature") {
*outKind = NormalizedRequirementKind::Feature;
return true;
}
if (kind == "field") {
*outKind = NormalizedRequirementKind::Field;
return true;
}
return false;
}
@@ -362,6 +672,43 @@
for (const auto& task : tasks) {
if (task.escalate) ++escalateCount;
}
const bool strictExecutionContract = args.value("strictExecutionContract", false);
int executionContractMissingCount = 0;
int executionSpecificityLowCount = 0;
int resourceConflictCount = 0;
json queueWarnings = json::array();
if (strictExecutionContract && args.contains("tasks") && args["tasks"].is_array()) {
for (const auto& taskJson : args["tasks"]) {
if (!taskJson.is_object() || !taskJson.contains("executionContract") || !taskJson["executionContract"].is_object()) {
++executionContractMissingCount;
continue;
}
const json& contract = taskJson["executionContract"];
const bool hasSteps = contract.contains("stepIds") && contract["stepIds"].is_array() && !contract["stepIds"].empty();
const bool hasFiles = contract.contains("targetFiles") && contract["targetFiles"].is_array() && !contract["targetFiles"].empty();
const bool hasTools = contract.contains("requiredTools") && contract["requiredTools"].is_array() && !contract["requiredTools"].empty();
const bool hasCommands = contract.contains("acceptanceCommands") && contract["acceptanceCommands"].is_array() && !contract["acceptanceCommands"].empty();
if (!(hasSteps && hasFiles && hasTools && hasCommands)) ++executionContractMissingCount;
if (contract.value("executionSpecificityScore", 0) < 60) ++executionSpecificityLowCount;
}
}
if (args.contains("tasks") && args["tasks"].is_array()) {
std::map<std::string, int> lockUseCount;
for (const auto& taskJson : args["tasks"]) {
if (!taskJson.is_object()) continue;
if (!taskJson.contains("resourceLocks") || !taskJson["resourceLocks"].is_array()) continue;
for (const auto& lock : taskJson["resourceLocks"]) {
if (!lock.is_string()) continue;
const std::string lockName = lock.get<std::string>();
if (lockName.empty()) continue;
lockUseCount[lockName] += 1;
}
}
for (const auto& kv : lockUseCount) {
if (kv.second > 1) ++resourceConflictCount;
}
if (resourceConflictCount > 0) queueWarnings.push_back("resource_conflict_unresolved");
}
json blockers = json::array();
int readyCount = 0;
@@ -374,13 +721,84 @@
}
if (readyCount == 0) blockers.push_back("no_ready_tasks");
if (escalateCount > 0) blockers.push_back("escalations_present");
if (strictExecutionContract && executionContractMissingCount > 0) blockers.push_back("missing_execution_contract");
if (strictExecutionContract && executionSpecificityLowCount > 0) blockers.push_back("execution_specificity_low");
bool capabilityGap = false;
if (args.contains("capabilitySignals") && args["capabilitySignals"].is_object()) {
const json& signals = args["capabilitySignals"];
capabilityGap = signals.value("compile_failed", false) ||
signals.value("tests_failed", false) ||
signals.value("toolchain_missing", false) ||
signals.value("provider_missing", false);
}
if (capabilityGap) blockers.push_back("generator_capability_gap");
json blockerMetadataList = json::array();
for (const auto& blocker : blockers) {
if (!blocker.is_string()) continue;
blockerMetadataList.push_back(blockerMetadata(blocker.get<std::string>()));
}
json gapClassCounts = json::object();
json recommendedTools = json::array();
auto appendUniqueTool = [&recommendedTools](const std::string& tool) {
if (tool.empty()) return;
for (const auto& existing : recommendedTools) {
if (existing.is_string() && existing.get<std::string>() == tool) return;
}
recommendedTools.push_back(tool);
};
for (const auto& entry : blockerMetadataList) {
if (!entry.is_object()) continue;
const std::string gapClass = entry.value("gap_class", "unspecified");
int current = gapClassCounts.value(gapClass, 0);
gapClassCounts[gapClass] = current + 1;
const json tools = entry.value("recommended_tools", json::array());
if (tools.is_array()) {
for (const auto& toolName : tools) {
if (toolName.is_string()) appendUniqueTool(toolName.get<std::string>());
}
}
}
std::string dominantGapClass = "none";
int dominantCount = 0;
if (gapClassCounts.is_object()) {
for (auto it = gapClassCounts.begin(); it != gapClassCounts.end(); ++it) {
int count = it.value().is_number_integer() ? it.value().get<int>() : 0;
if (count > dominantCount || (count == dominantCount && (dominantGapClass == "none" || it.key() < dominantGapClass))) {
dominantCount = count;
dominantGapClass = it.key();
}
}
}
json debugLoopCallPlan = buildDebugLoopCallPlan(capabilityGap, strictExecutionContract);
json remediationRoutingHints = {
{"dominant_gap_class", dominantGapClass},
{"recommended_tools", recommendedTools},
{"strict_execution_contract", strictExecutionContract},
{"debug_loop_call_plan_fingerprint", debugLoopCallPlan.value("fingerprint", "")},
{"next_action", dominantGapClass == "under_constrained_taskitems"
? "tighten_taskitem_generation_contract"
: (dominantGapClass == "generator_capability_gap"
? "run_debug_or_codegen_capability_loop"
: "continue_with_validation_and_queue_checks")}
};
return {
{"success", true},
{"ready", blockers.empty()},
{"blockers", blockers},
{"blockerMetadata", blockerMetadataList},
{"gap_class_counts", gapClassCounts},
{"remediationRoutingHints", remediationRoutingHints},
{"debugLoopCallPlan", debugLoopCallPlan},
{"readyCount", readyCount},
{"escalateCount", escalateCount},
{"strictExecutionContract", strictExecutionContract},
{"executionContractMissingCount", executionContractMissingCount},
{"executionSpecificityLowCount", executionSpecificityLowCount},
{"capabilityGapDetected", capabilityGap},
{"queueWarnings", queueWarnings},
{"resourceConflictCount", resourceConflictCount},
{"queueJson", boundQueueToJson(queue)}
};
}
@@ -400,7 +818,9 @@
return false;
}
int inferredIndex = 0;
for (const auto& taskJson : args["tasks"]) {
++inferredIndex;
if (!taskJson.is_object()) {
*error = "task_entry_invalid";
return false;
@@ -408,15 +828,23 @@
AnnotatedTaskitem task;
task.base.taskId = taskJson.value("taskId", "");
task.base.title = taskJson.value("title", "");
task.base.milestoneId = taskJson.value("milestoneId", "");
task.base.milestoneId = taskJson.value("milestoneId", "milestone-1");
task.base.dependencyTaskIds = taskJson.value("dependencyTaskIds", std::vector<std::string>{});
task.base.prerequisiteOps = taskJson.value("prerequisiteOps", std::vector<std::string>{});
task.base.queueReady = taskJson.value("queueReady", false);
task.base.queueReady = taskJson.value("queueReady", true);
task.confidence = taskJson.value("confidence", 0);
task.ambiguityCount = taskJson.value("ambiguityCount", 0);
task.escalate = taskJson.value("escalate", false);
task.reasons = taskJson.value("reasons", std::vector<std::string>{});
if (task.base.taskId.empty() || task.base.title.empty() || task.base.milestoneId.empty()) {
const bool hasExecutionContract = taskJson.contains("executionContract") &&
taskJson["executionContract"].is_object();
if (task.base.taskId.empty() && hasExecutionContract) {
task.base.taskId = "inferred-task-" + std::to_string(inferredIndex);
}
if (task.base.title.empty() && hasExecutionContract) {
task.base.title = "Inferred Task " + std::to_string(inferredIndex);
}
if (task.base.taskId.empty() || task.base.title.empty()) {
*error = "task_entry_missing_fields";
return false;
}
@@ -491,6 +919,84 @@
return out;
}
static json blockerMetadata(const std::string& blocker) {
if (blocker == "missing_execution_contract") {
return {
{"blocker", blocker},
{"gap_class", "under_constrained_taskitems"},
{"likely_root_cause", "taskitem generation missing strict execution contract fields"},
{"recommended_tools", json::array({
"whetstone_generate_taskitems",
"whetstone_derive_requirements",
"whetstone_verify_requirements"
})}
};
}
if (blocker == "execution_specificity_low") {
return {
{"blocker", blocker},
{"gap_class", "insufficient_execution_specificity"},
{"likely_root_cause", "execution contract hints too sparse for autonomous implementation"},
{"recommended_tools", json::array({
"whetstone_generate_taskitems",
"whetstone_queue_ready",
"whetstone_validate_taskitem"
})}
};
}
if (blocker == "escalations_present") {
return {
{"blocker", blocker},
{"gap_class", "risk_or_ambiguity"},
{"likely_root_cause", "task confidence or ambiguity requires decomposition/manual review"},
{"recommended_tools", json::array({
"whetstone_architect_intake",
"whetstone_generate_taskitems"
})}
};
}
if (blocker == "acceptance_requirements_empty") {
return {
{"blocker", blocker},
{"gap_class", "missing_acceptance_binding"},
{"likely_root_cause", "normalized requirements missing for acceptance criteria binding"},
{"recommended_tools", json::array({
"whetstone_architect_intake",
"whetstone_queue_ready"
})}
};
}
if (blocker == "no_ready_tasks") {
return {
{"blocker", blocker},
{"gap_class", "queue_readiness_failure"},
{"likely_root_cause", "all tasks blocked by readiness or coverage constraints"},
{"recommended_tools", json::array({
"whetstone_queue_ready",
"whetstone_validate_taskitem"
})}
};
}
if (blocker == "generator_capability_gap") {
return {
{"blocker", blocker},
{"gap_class", "generator_capability_gap"},
{"likely_root_cause", "compile/test/toolchain signals indicate implementation/runtime capability deficit"},
{"recommended_tools", json::array({
"whetstone_probe_toolchain_provider",
"whetstone_capture_failure_packet",
"whetstone_debug_until_green"
})}
};
}
return {
{"blocker", blocker},
{"gap_class", "unspecified"},
{"likely_root_cause", "no deterministic mapping available"},
{"recommended_tools", json::array()}
};
}
json runSetWorkspace(const json& args) {
if (!args.contains("workspace")) {
return {