From a474a87f50be51ee4493f74c2d853d77e3d9c9c1 Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 3 Mar 2026 09:36:54 -0700 Subject: [PATCH] Fix MCP build includes and queue task parsing compatibility --- editor/src/MCPServer.h | 6 + editor/src/mcp/RegisterArchitectIntakeTools.h | 556 +++++++++++++++++- 2 files changed, 537 insertions(+), 25 deletions(-) diff --git a/editor/src/MCPServer.h b/editor/src/MCPServer.h index 185ac53..a3e68e7 100644 --- a/editor/src/MCPServer.h +++ b/editor/src/MCPServer.h @@ -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" diff --git a/editor/src/mcp/RegisterArchitectIntakeTools.h b/editor/src/mcp/RegisterArchitectIntakeTools.h index 392c9ba..6c4111c 100644 --- a/editor/src/mcp/RegisterArchitectIntakeTools.h +++ b/editor/src/mcp/RegisterArchitectIntakeTools.h @@ -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(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* 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 sortedUnique(const std::vector& in) { + std::vector 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(c); + state *= 1099511628211ull; + } + const char* hex = "0123456789abcdef"; + std::string out(16, '0'); + for (int i = 15; i >= 0; --i) { + out[static_cast(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& requirements) { + std::vector stepIds; + std::vector files; + std::vector tools; + std::vector 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 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 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(); + 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())); + } + + 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() == 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 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() : 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{}); task.base.prerequisiteOps = taskJson.value("prerequisiteOps", std::vector{}); - 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{}); - 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 {