diff --git a/editor/src/MCPServer.h b/editor/src/MCPServer.h index a3e68e7..f564224 100644 --- a/editor/src/MCPServer.h +++ b/editor/src/MCPServer.h @@ -39,6 +39,9 @@ #include "ASTFeatureExtractor.h" #include "LanguageIdiomProfile.h" #include "LanguageFitnessScorer.h" +#include "TaskitemLibraryAnnotator.h" +#include "Sprint286IntegrationSummary.h" +#include "Sprint288IntegrationSummary.h" #include "PolyglotProjectSpec.h" #include "ABIBoundaryExtractor.h" #include "FFIGlueDispatcher.h" diff --git a/editor/src/mcp/RegisterArchitectIntakeTools.h b/editor/src/mcp/RegisterArchitectIntakeTools.h index 6c4111c..e382e1d 100644 --- a/editor/src/mcp/RegisterArchitectIntakeTools.h +++ b/editor/src/mcp/RegisterArchitectIntakeTools.h @@ -18,6 +18,8 @@ {"description", "Normalized requirements from whetstone_architect_intake."}}}, {"conflicts", {{"type", "array"}, {"description", "Optional requirement conflicts from whetstone_architect_intake."}}}, + {"availableLibraries", {{"type", "array"}, + {"description", "Optional candidate libraries for deterministic library/API dispatch."}}}, {"strictExecutionContract", {{"type", "boolean"}, {"description", "Require concrete execution contract metadata (stepIds/files/tools/tests)."}}} }}, {"required", json::array({"normalizedRequirements"})}} @@ -477,10 +479,50 @@ }; } + std::vector availableLibraries; + if (args.contains("availableLibraries")) { + if (!args["availableLibraries"].is_array()) { + return { + {"success", false}, + {"error", "available_libraries_not_array"} + }; + } + for (const auto& libJson : args["availableLibraries"]) { + if (!libJson.is_string()) { + return { + {"success", false}, + {"error", "available_library_entry_not_string"} + }; + } + const std::string lib = libJson.get(); + if (lib.empty()) continue; + bool seen = false; + for (const auto& existing : availableLibraries) { + if (existing == lib) { + seen = true; + break; + } + } + if (!seen) availableLibraries.push_back(lib); + } + } + const bool libraryDispatchEnabled = !availableLibraries.empty(); + auto capabilityLedger = whetstone::Sprint286IntegrationSummary::buildSeedLedger(); + auto symbolCatalog = whetstone::Sprint288IntegrationSummary::buildSeedCatalog(); + whetstone::TaskitemLibraryAnnotator libraryAnnotator; + std::string libraryDispatchContext; + for (const auto& requirement : normalized.requirements) { + if (!requirement.normalizedText.empty()) { + if (!libraryDispatchContext.empty()) libraryDispatchContext.push_back(' '); + libraryDispatchContext += requirement.normalizedText; + } + } + const bool strictExecutionContract = args.value("strictExecutionContract", false); const json executionHints = collectExecutionHints(normalized.requirements); int escalateCount = 0; int missingContractCount = 0; + int enrichedTaskCount = 0; json tasksJson = json::array(); for (const auto& task : annotated) { json taskJson = { @@ -496,6 +538,31 @@ {"reasons", task.reasons} }; json contract = buildExecutionContract(task, executionHints, strictExecutionContract); + if (libraryDispatchEnabled) { + std::string dispatchText = task.base.title; + if (!libraryDispatchContext.empty()) { + dispatchText += " "; + dispatchText += libraryDispatchContext; + } + auto enriched = libraryAnnotator.annotate(dispatchText, availableLibraries, capabilityLedger, symbolCatalog); + if (enriched.isEnriched()) { + contract["selectedLibrary"] = enriched.selectedLibrary; + contract["operationDomain"] = enriched.operationDomain; + contract["capabilityScore"] = enriched.capabilityScore; + contract["preferredAPIs"] = enriched.preferredAPIs; + contract["justification"] = enriched.justification; + json avoided = json::array(); + for (const auto& item : enriched.avoidedLibraries) { + avoided.push_back({ + {"name", item.name}, + {"reason", item.reason}, + {"score", item.score} + }); + } + contract["avoidedLibraries"] = avoided; + ++enrichedTaskCount; + } + } taskJson["executionContract"] = contract; taskJson["resourceLocks"] = inferResourceLocks(task); if (strictExecutionContract && contract.value("executionSpecificityScore", 0) < 60) { @@ -522,7 +589,12 @@ {"ambiguousRequirementCount", countAmbiguousRequirements(normalized.requirements)}, {"escalateCount", escalateCount}, {"strictExecutionContract", strictExecutionContract}, - {"missingExecutionContractCount", missingContractCount} + {"missingExecutionContractCount", missingContractCount}, + {"libraryDispatch", { + {"enabled", libraryDispatchEnabled}, + {"availableLibraries", availableLibraries}, + {"enrichedTaskCount", enrichedTaskCount} + }} }; } diff --git a/tools/mcp/run_sprint_taskitem_pipeline.sh b/tools/mcp/run_sprint_taskitem_pipeline.sh index 9dd2947..95861ff 100755 --- a/tools/mcp/run_sprint_taskitem_pipeline.sh +++ b/tools/mcp/run_sprint_taskitem_pipeline.sh @@ -279,6 +279,27 @@ if [[ "$SEMANTIC_PLANNING_BRIDGE" == "1" && "$SEMANTIC_INTAKE_AUGMENT" == "1" && else MARKDOWN_CONTENT="$(cat "$INPUT_FILE")" fi +AVAILABLE_LIBRARIES_JSON="${WSTONE_AVAILABLE_LIBRARIES_JSON:-}" +if [[ -z "$AVAILABLE_LIBRARIES_JSON" ]]; then + AVAILABLE_LIBRARIES_JSON="$(cat "$INPUT_FILE" | python3 -c ' +import json, re, sys +text = sys.stdin.read() +libs = [] +for line in text.splitlines(): + m = re.search(r"available libraries\s*:\s*(.+)$", line, re.IGNORECASE) + if not m: + continue + for part in m.group(1).split(","): + name = part.strip().strip("`").strip() + if name and name not in libs: + libs.append(name) +print(json.dumps(libs)) +')" +fi +if ! printf '%s' "$AVAILABLE_LIBRARIES_JSON" | jq -e 'type == "array"' >/dev/null 2>&1; then + echo "error: available libraries must be a JSON array (WSTONE_AVAILABLE_LIBRARIES_JSON)" >&2 + exit 21 +fi INTAKE_ARGS="$(jq -nc --arg md "$MARKDOWN_CONTENT" '{markdown:$md}')" INTAKE_RESP_RAW="$(call_tool "whetstone_architect_intake" "$INTAKE_ARGS")" printf '%s\n' "$INTAKE_RESP_RAW" > "$OUT_DIR/01_intake_raw.ndjson.json" @@ -402,8 +423,8 @@ else NATIVE_RAW_TEMPLATE_CONTROL_PACK_JSON='{"enabled":false}' fi CONFLICTS="$(printf '%s' "$INTAKE_JSON" | jq '.conflicts // []')" -GEN_ARGS="$(jq -nc --argjson nr "$NORMALIZED_REQS" --argjson cf "$CONFLICTS" --arg strict "$STRICT_EXECUTION_CONTRACT" \ - '{normalizedRequirements:$nr,conflicts:$cf,strictExecutionContract:($strict == "1")}')" +GEN_ARGS="$(jq -nc --argjson nr "$NORMALIZED_REQS" --argjson cf "$CONFLICTS" --argjson libs "$AVAILABLE_LIBRARIES_JSON" --arg strict "$STRICT_EXECUTION_CONTRACT" \ + '{normalizedRequirements:$nr,conflicts:$cf,availableLibraries:$libs,strictExecutionContract:($strict == "1")}')" GEN_RESP_RAW="$(call_tool "whetstone_generate_taskitems" "$GEN_ARGS")" printf '%s\n' "$GEN_RESP_RAW" > "$OUT_DIR/02_generate_taskitems_raw.ndjson.json" GEN_JSON="$(extract_tool_text_json "$GEN_RESP_RAW")" @@ -426,8 +447,8 @@ if [[ "$NATIVE_DECOMP_RETRY" == "1" && "$native_task_count_initial" -lt "$NATIVE ambiguous: false }] ')" - RETRY_GEN_ARGS="$(jq -nc --argjson nr "$RETRY_REQS" --argjson cf "$CONFLICTS" --arg strict "$STRICT_EXECUTION_CONTRACT" \ - '{normalizedRequirements:$nr,conflicts:$cf,strictExecutionContract:($strict == "1")}')" + RETRY_GEN_ARGS="$(jq -nc --argjson nr "$RETRY_REQS" --argjson cf "$CONFLICTS" --argjson libs "$AVAILABLE_LIBRARIES_JSON" --arg strict "$STRICT_EXECUTION_CONTRACT" \ + '{normalizedRequirements:$nr,conflicts:$cf,availableLibraries:$libs,strictExecutionContract:($strict == "1")}')" RETRY_GEN_RESP_RAW="$(call_tool "whetstone_generate_taskitems" "$RETRY_GEN_ARGS")" printf '%s\n' "$RETRY_GEN_RESP_RAW" > "$OUT_DIR/02aa_generate_taskitems_retry_raw.ndjson.json" RETRY_GEN_JSON="$(extract_tool_text_json "$RETRY_GEN_RESP_RAW")" @@ -539,8 +560,8 @@ if [[ "$NATIVE_RAW_CANDIDATE_SEARCH" == "1" ]]; then while [[ "$variant" -lt "$max_variants" && "$idx" -lt "$variant_total" ]]; do req_bundle="$(printf '%s' "$VARIANTS_JSON" | jq ".variants[$idx]")" cand_reqs="$(jq -nc --argjson base "$NORMALIZED_REQS" --argjson bundle "$req_bundle" '$base + $bundle')" - cand_args="$(jq -nc --argjson nr "$cand_reqs" --argjson cf "$CONFLICTS" --arg strict "$STRICT_EXECUTION_CONTRACT" \ - '{normalizedRequirements:$nr,conflicts:$cf,strictExecutionContract:($strict == "1")}')" + cand_args="$(jq -nc --argjson nr "$cand_reqs" --argjson cf "$CONFLICTS" --argjson libs "$AVAILABLE_LIBRARIES_JSON" --arg strict "$STRICT_EXECUTION_CONTRACT" \ + '{normalizedRequirements:$nr,conflicts:$cf,availableLibraries:$libs,strictExecutionContract:($strict == "1")}')" cand_raw="$(call_tool "whetstone_generate_taskitems" "$cand_args")" printf '%s\n' "$cand_raw" > "$OUT_DIR/02ae_candidate_${variant}_raw.ndjson.json" cand_json="$(extract_tool_text_json "$cand_raw")" @@ -603,8 +624,8 @@ if [[ "$NATIVE_RAW_CANDIDATE_SEARCH" == "1" ]]; then --out-report "$OUT_DIR/02af_raw_adaptive_retry_requirements_report.json" >/dev/null retry_top_gap_reqs="$(cat "$OUT_DIR/02af_raw_adaptive_retry_requirements.json")" retry_cand_reqs="$(jq -nc --argjson base "$NORMALIZED_REQS" --argjson extra "$retry_top_gap_reqs" '$base + $extra')" - retry_cand_args="$(jq -nc --argjson nr "$retry_cand_reqs" --argjson cf "$CONFLICTS" --arg strict "$STRICT_EXECUTION_CONTRACT" \ - '{normalizedRequirements:$nr,conflicts:$cf,strictExecutionContract:($strict == "1")}')" + retry_cand_args="$(jq -nc --argjson nr "$retry_cand_reqs" --argjson cf "$CONFLICTS" --argjson libs "$AVAILABLE_LIBRARIES_JSON" --arg strict "$STRICT_EXECUTION_CONTRACT" \ + '{normalizedRequirements:$nr,conflicts:$cf,availableLibraries:$libs,strictExecutionContract:($strict == "1")}')" retry_raw="$(call_tool "whetstone_generate_taskitems" "$retry_cand_args")" printf '%s\n' "$retry_raw" > "$OUT_DIR/02af_raw_adaptive_retry_raw.ndjson.json" retry_json="$(extract_tool_text_json "$retry_raw")" @@ -791,8 +812,8 @@ if [[ "$NATIVE_MULTISHOT_DECOMP" == "1" ]]; then '[($prof.profiles[]? | select(.id == $pid) | .required_execution_contract[]?) // empty]')" shot_reqs="$(jq -nc --argjson base "$NORMALIZED_REQS" --argjson req "$req_i" '$base + [$req]')" - shot_args="$(jq -nc --argjson nr "$shot_reqs" --argjson cf "$CONFLICTS" --arg strict "$STRICT_EXECUTION_CONTRACT" \ - '{normalizedRequirements:$nr,conflicts:$cf,strictExecutionContract:($strict == "1")}')" + shot_args="$(jq -nc --argjson nr "$shot_reqs" --argjson cf "$CONFLICTS" --argjson libs "$AVAILABLE_LIBRARIES_JSON" --arg strict "$STRICT_EXECUTION_CONTRACT" \ + '{normalizedRequirements:$nr,conflicts:$cf,availableLibraries:$libs,strictExecutionContract:($strict == "1")}')" shot_raw="$(call_tool "whetstone_generate_taskitems" "$shot_args")" printf '%s\n' "$shot_raw" > "$OUT_DIR/02ac_generate_taskitems_multishot_${i}_raw.ndjson.json" shot_json="$(extract_tool_text_json "$shot_raw")"