From 452603ae7f31811d86d39ffe1ea9392359ff89a6 Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 3 Mar 2026 10:22:05 -0700 Subject: [PATCH] Add strict/hybrid library policy with auto/ask/options modes --- editor/src/OperationTaxonomy.h | 24 +++-- editor/src/Sprint286IntegrationSummary.h | 62 +++++++++++ editor/src/Sprint288IntegrationSummary.h | 87 +++++++++++++++ editor/src/mcp/RegisterArchitectIntakeTools.h | 100 +++++++++++++++++- tools/mcp/run_sprint_taskitem_pipeline.sh | 18 +++- 5 files changed, 278 insertions(+), 13 deletions(-) diff --git a/editor/src/OperationTaxonomy.h b/editor/src/OperationTaxonomy.h index 0aa7a3c..42e6109 100644 --- a/editor/src/OperationTaxonomy.h +++ b/editor/src/OperationTaxonomy.h @@ -40,9 +40,12 @@ public: // network.*.* t.add("network.http.client", "HTTP Client", - {{"http", "client"}, {"rest", "client"}, {"curl"}}); + {{"http", "client"}, {"rest", "client"}, {"curl"}, {"api", "client"}, {"http request"}, + {"requests"}, {"axios"}}); t.add("network.http.server", "HTTP Server", - {{"http", "server"}, {"web server"}, {"rest", "server"}, {"http", "listen"}}); + {{"http", "server"}, {"web server"}, {"rest", "server"}, {"http", "listen"}, + {"api", "endpoint"}, {"api", "route"}, {"backend", "api"}, + {"fastapi"}, {"express"}, {"gin"}, {"axum"}, {"spring-boot"}}); // io.*.* t.add("io.stream.audio", "Audio Stream IO", @@ -88,7 +91,7 @@ public: // network.* t.add("network.websocket", "WebSocket", - {{"websocket"}, {"ws://"}}); + {{"websocket"}, {"ws://"}, {"realtime", "socket"}, {"live", "notifications"}}); t.add("network.async", "Async Network IO", {{"async", "io"}, {"async", "network"}, {"event loop"}, {"non-blocking", "io"}}); t.add("network.grpc", "gRPC", @@ -124,7 +127,8 @@ public: t.add("db.transaction", "Database Transaction", {{"transaction"}, {"commit"}, {"rollback"}}); t.add("db.orm", "ORM", - {{"orm"}, {"object relational"}, {"entity", "mapping"}}); + {{"orm"}, {"object relational"}, {"entity", "mapping"}, {"data model"}, {"schema", "model"}, + {"sqlalchemy"}, {"prisma"}, {"jooq"}, {"sqlx"}}); t.add("db.migration", "Database Migration", {{"migration"}, {"schema change"}, {"db upgrade"}, {"db", "migrate"}}); @@ -132,7 +136,7 @@ public: t.add("auth.oauth", "OAuth", {{"oauth"}}); t.add("auth.jwt", "JWT", - {{"jwt"}, {"json web token"}}); + {{"jwt"}, {"json web token"}, {"auth", "token"}}); t.add("auth.session", "Session Auth", {{"session"}, {"cookie", "auth"}}); t.add("auth.mfa", "Multi-Factor Auth", @@ -142,9 +146,15 @@ public: t.add("storage.object", "Object Storage", {{"object storage"}, {"s3"}, {"blob storage"}}); t.add("storage.cache", "Cache Storage", - {{"cache"}, {"redis"}, {"memcache"}}); + {{"cache"}, {"redis"}, {"memcache"}, {"rate limit"}}); t.add("storage.queue", "Message Queue", - {{"kafka"}, {"rabbitmq"}, {"message queue"}, {"message broker"}}); + {{"kafka"}, {"rabbitmq"}, {"message queue"}, {"message broker"}, + {"job queue"}, {"background worker"}, {"queue worker"}, {"bullmq"}, {"kafka-go"}}); + + // validation.* + t.add("validation.schema", "Schema Validation", + {{"schema", "validation"}, {"validate", "input"}, {"request", "validation"}, + {"type validation"}, {"runtime validation"}, {"zod"}, {"pydantic"}}); // --- 0-dot (top-level, checked last) --- t.add("serialization", "Serialization", diff --git a/editor/src/Sprint286IntegrationSummary.h b/editor/src/Sprint286IntegrationSummary.h index 3737458..e52c1d3 100644 --- a/editor/src/Sprint286IntegrationSummary.h +++ b/editor/src/Sprint286IntegrationSummary.h @@ -88,6 +88,68 @@ struct Sprint286IntegrationSummary { {"verbose-api", "requires-beast-for-http"}, false, "2026-03-02", "api-analysis"}); + // Modern web/data/realtime stacks + ledger.set({"express", "network.http.server", 0.93f, + {"express()", "app.get()", "app.post()"}, + {}, + false, "2026-03-03", "web-stack-seed"}); + ledger.set({"fastapi", "network.http.server", 0.95f, + {"FastAPI()", "@app.get", "@app.post"}, + {}, + false, "2026-03-03", "web-stack-seed"}); + ledger.set({"gin", "network.http.server", 0.92f, + {"gin.Default()", "router.GET()", "router.POST()"}, + {}, + false, "2026-03-03", "web-stack-seed"}); + ledger.set({"axum", "network.http.server", 0.91f, + {"Router::new()", "routing::get()", "routing::post()"}, + {}, + false, "2026-03-03", "web-stack-seed"}); + ledger.set({"spring-boot", "network.http.server", 0.94f, + {"@RestController", "@GetMapping", "@PostMapping"}, + {}, + false, "2026-03-03", "web-stack-seed"}); + ledger.set({"requests", "network.http.client", 0.93f, + {"requests.get()", "requests.post()"}, + {}, + false, "2026-03-03", "web-stack-seed"}); + ledger.set({"axios", "network.http.client", 0.92f, + {"axios.get()", "axios.post()"}, + {}, + false, "2026-03-03", "web-stack-seed"}); + ledger.set({"sqlalchemy", "db.orm", 0.94f, + {"Session()", "select()", "mapped_column()"}, + {}, + false, "2026-03-03", "web-stack-seed"}); + ledger.set({"prisma", "db.orm", 0.93f, + {"prisma.user.findMany()", "prisma.$transaction()"}, + {}, + false, "2026-03-03", "web-stack-seed"}); + ledger.set({"jooq", "db.orm", 0.90f, + {"DSLContext.selectFrom()", "DSLContext.transaction()"}, + {}, + false, "2026-03-03", "web-stack-seed"}); + ledger.set({"flyway", "db.migration", 0.95f, + {"migrate()", "baseline()"}, + {}, + false, "2026-03-03", "web-stack-seed"}); + ledger.set({"zod", "validation.schema", 0.94f, + {"z.object()", "schema.parse()"}, + {}, + false, "2026-03-03", "web-stack-seed"}); + ledger.set({"pydantic", "validation.schema", 0.95f, + {"BaseModel", "model_validate()"}, + {}, + false, "2026-03-03", "web-stack-seed"}); + ledger.set({"bullmq", "storage.queue", 0.93f, + {"new Queue()", "new Worker()", "queue.add()"}, + {}, + false, "2026-03-03", "web-stack-seed"}); + ledger.set({"redis", "storage.cache", 0.94f, + {"set()", "get()", "hset()"}, + {}, + false, "2026-03-03", "web-stack-seed"}); + return ledger; } diff --git a/editor/src/Sprint288IntegrationSummary.h b/editor/src/Sprint288IntegrationSummary.h index 11e4fe2..25f58ed 100644 --- a/editor/src/Sprint288IntegrationSummary.h +++ b/editor/src/Sprint288IntegrationSummary.h @@ -52,6 +52,93 @@ struct Sprint288IntegrationSummary { "auto j = nlohmann::json::parse(input_string);", {"throws-parse_error-on-invalid-json"}}); + // Web/data/realtime stack symbols + cat.add({"express", "app.get", + "app.get(path, handler)", + "network.http.server", + "app.get('/health', (req, res) => res.json({ok:true}))", + {}}); + cat.add({"express", "app.post", + "app.post(path, handler)", + "network.http.server", + "app.post('/tweets', createTweetHandler)", + {}}); + cat.add({"fastapi", "FastAPI", + "FastAPI()", + "network.http.server", + "app = FastAPI()", + {}}); + cat.add({"fastapi", "app.get", + "@app.get(path)", + "network.http.server", + "@app.get('/health')", + {}}); + cat.add({"gin", "router.GET", + "router.GET(path, handler)", + "network.http.server", + "router.GET('/health', healthHandler)", + {}}); + cat.add({"axum", "Router::route", + "Router::route(path, method_router)", + "network.http.server", + "Router::new().route('/health', get(health))", + {}}); + cat.add({"spring-boot", "@RestController", + "@RestController", + "network.http.server", + "@RestController class HealthController { ... }", + {}}); + cat.add({"requests", "requests.get", + "requests.get(url, **kwargs)", + "network.http.client", + "resp = requests.get(url, timeout=5)", + {}}); + cat.add({"axios", "axios.get", + "axios.get(url, config)", + "network.http.client", + "const r = await axios.get(url)", + {}}); + cat.add({"sqlalchemy", "select", + "sqlalchemy.select(model)", + "db.orm", + "stmt = select(User).where(User.id == user_id)", + {}}); + cat.add({"prisma", "prisma.user.findMany", + "prisma.user.findMany(args)", + "db.orm", + "const users = await prisma.user.findMany()", + {}}); + cat.add({"jooq", "DSLContext.selectFrom", + "DSLContext.selectFrom(table)", + "db.orm", + "ctx.selectFrom(USERS).fetch()", + {}}); + cat.add({"flyway", "migrate", + "Flyway.migrate()", + "db.migration", + "flyway.migrate()", + {}}); + cat.add({"zod", "z.object", + "z.object(shape)", + "validation.schema", + "const schema = z.object({ id: z.string() })", + {}}); + cat.add({"pydantic", "BaseModel", + "class BaseModel", + "validation.schema", + "class TweetCreate(BaseModel): ...", + {}}); + cat.add({"bullmq", "Queue.add", + "Queue.add(name, data, opts)", + "storage.queue", + "await queue.add('notify', payload)", + {}}); + cat.add({"redis", "set", + "redis.set(key, value)", + "storage.cache", + "await redis.set('timeline:uid', data)", + {}}); + return cat; } diff --git a/editor/src/mcp/RegisterArchitectIntakeTools.h b/editor/src/mcp/RegisterArchitectIntakeTools.h index e382e1d..d17ee86 100644 --- a/editor/src/mcp/RegisterArchitectIntakeTools.h +++ b/editor/src/mcp/RegisterArchitectIntakeTools.h @@ -20,6 +20,8 @@ {"description", "Optional requirement conflicts from whetstone_architect_intake."}}}, {"availableLibraries", {{"type", "array"}, {"description", "Optional candidate libraries for deterministic library/API dispatch."}}}, + {"libraryPolicy", {{"type", "object"}, + {"description", "Library selection policy: mode=strict|hybrid, expansionMode=auto_include|ask_user|suggest_options."}}}, {"strictExecutionContract", {{"type", "boolean"}, {"description", "Require concrete execution contract metadata (stepIds/files/tools/tests)."}}} }}, {"required", json::array({"normalizedRequirements"})}} @@ -506,6 +508,44 @@ if (!seen) availableLibraries.push_back(lib); } } + std::string libraryMode = "strict"; + std::string libraryExpansionMode = "auto_include"; + int maxAdditionalLibraries = 3; + if (args.contains("libraryPolicy")) { + if (!args["libraryPolicy"].is_object()) { + return { + {"success", false}, + {"error", "library_policy_not_object"} + }; + } + const json& policy = args["libraryPolicy"]; + if (policy.contains("mode")) { + if (!policy["mode"].is_string()) { + return {{"success", false}, {"error", "library_policy_mode_not_string"}}; + } + libraryMode = policy["mode"].get(); + } + if (policy.contains("expansionMode")) { + if (!policy["expansionMode"].is_string()) { + return {{"success", false}, {"error", "library_policy_expansion_mode_not_string"}}; + } + libraryExpansionMode = policy["expansionMode"].get(); + } + if (policy.contains("maxAdditionalLibraries")) { + if (!policy["maxAdditionalLibraries"].is_number_integer()) { + return {{"success", false}, {"error", "library_policy_max_additional_not_int"}}; + } + maxAdditionalLibraries = std::max(0, policy["maxAdditionalLibraries"].get()); + } + } + if (libraryMode != "strict" && libraryMode != "hybrid") { + return {{"success", false}, {"error", "library_policy_mode_invalid"}}; + } + if (libraryExpansionMode != "auto_include" && + libraryExpansionMode != "ask_user" && + libraryExpansionMode != "suggest_options") { + return {{"success", false}, {"error", "library_policy_expansion_mode_invalid"}}; + } const bool libraryDispatchEnabled = !availableLibraries.empty(); auto capabilityLedger = whetstone::Sprint286IntegrationSummary::buildSeedLedger(); auto symbolCatalog = whetstone::Sprint288IntegrationSummary::buildSeedCatalog(); @@ -523,6 +563,7 @@ int escalateCount = 0; int missingContractCount = 0; int enrichedTaskCount = 0; + int pendingLibrarySelectionCount = 0; json tasksJson = json::array(); for (const auto& task : annotated) { json taskJson = { @@ -560,7 +601,58 @@ }); } contract["avoidedLibraries"] = avoided; + contract["librarySelectionMode"] = "direct"; ++enrichedTaskCount; + } else if (libraryMode == "hybrid") { + auto classifier = whetstone::OperationClassifier::withDefaultTaxonomy(); + auto classified = classifier.classify(dispatchText); + if (classified.isKnown) { + auto domainCaps = capabilityLedger.allForDomain(classified.domain); + std::sort(domainCaps.begin(), domainCaps.end(), + [](const auto& a, const auto& b) { + return a.score > b.score; + }); + std::vector additional; + for (const auto& rec : domainCaps) { + bool exists = false; + for (const auto& chosen : availableLibraries) { + if (chosen == rec.libraryId) { + exists = true; + break; + } + } + if (exists) continue; + additional.push_back(rec.libraryId); + if (maxAdditionalLibraries > 0 && + static_cast(additional.size()) >= maxAdditionalLibraries) { + break; + } + } + if (!additional.empty() && libraryExpansionMode == "auto_include") { + std::vector expanded = availableLibraries; + expanded.insert(expanded.end(), additional.begin(), additional.end()); + auto fallback = libraryAnnotator.annotate(dispatchText, expanded, capabilityLedger, symbolCatalog); + if (fallback.isEnriched()) { + contract["selectedLibrary"] = fallback.selectedLibrary; + contract["operationDomain"] = fallback.operationDomain; + contract["capabilityScore"] = fallback.capabilityScore; + contract["preferredAPIs"] = fallback.preferredAPIs; + contract["justification"] = fallback.justification; + contract["librarySelectionMode"] = "hybrid_auto_include"; + contract["autoIncludedLibraries"] = additional; + ++enrichedTaskCount; + } + } else if (!additional.empty()) { + contract["librarySelectionMode"] = libraryExpansionMode == "ask_user" + ? "hybrid_ask_user" + : "hybrid_suggest_options"; + contract["librarySelectionPending"] = (libraryExpansionMode == "ask_user"); + contract["librarySelectionOptions"] = additional; + contract["operationDomain"] = classified.domain; + contract["justification"] = "No match in user-selected libraries for classified domain."; + ++pendingLibrarySelectionCount; + } + } } } taskJson["executionContract"] = contract; @@ -593,7 +685,13 @@ {"libraryDispatch", { {"enabled", libraryDispatchEnabled}, {"availableLibraries", availableLibraries}, - {"enrichedTaskCount", enrichedTaskCount} + {"policy", { + {"mode", libraryMode}, + {"expansionMode", libraryExpansionMode}, + {"maxAdditionalLibraries", maxAdditionalLibraries} + }}, + {"enrichedTaskCount", enrichedTaskCount}, + {"pendingSelectionCount", pendingLibrarySelectionCount} }} }; } diff --git a/tools/mcp/run_sprint_taskitem_pipeline.sh b/tools/mcp/run_sprint_taskitem_pipeline.sh index 95861ff..0b0b638 100755 --- a/tools/mcp/run_sprint_taskitem_pipeline.sh +++ b/tools/mcp/run_sprint_taskitem_pipeline.sh @@ -19,6 +19,9 @@ fi WORKSPACE="${WSTONE_WORKSPACE:-$ROOT_DIR}" LANGUAGE="${WSTONE_LANGUAGE:-cpp}" STRICT_EXECUTION_CONTRACT="${WSTONE_STRICT_EXECUTION_CONTRACT:-1}" +LIBRARY_POLICY_MODE="${WSTONE_LIBRARY_POLICY_MODE:-strict}" +LIBRARY_EXPANSION_MODE="${WSTONE_LIBRARY_EXPANSION_MODE:-auto_include}" +LIBRARY_MAX_ADDITIONAL="${WSTONE_LIBRARY_MAX_ADDITIONAL:-3}" CALIBRATE_AFTER_RUN="${WSTONE_CALIBRATE_AFTER_RUN:-0}" RUN_READINESS_SUITE="${WSTONE_RUN_READINESS_SUITE:-1}" SPEC_READINESS_PRECHECK="${WSTONE_SPEC_READINESS_PRECHECK:-1}" @@ -424,7 +427,8 @@ else fi CONFLICTS="$(printf '%s' "$INTAKE_JSON" | jq '.conflicts // []')" 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")}')" + --arg mode "$LIBRARY_POLICY_MODE" --arg expansion "$LIBRARY_EXPANSION_MODE" --argjson max_additional "$LIBRARY_MAX_ADDITIONAL" \ + '{normalizedRequirements:$nr,conflicts:$cf,availableLibraries:$libs,libraryPolicy:{mode:$mode,expansionMode:$expansion,maxAdditionalLibraries:$max_additional},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")" @@ -448,7 +452,8 @@ if [[ "$NATIVE_DECOMP_RETRY" == "1" && "$native_task_count_initial" -lt "$NATIVE }] ')" 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")}')" + --arg mode "$LIBRARY_POLICY_MODE" --arg expansion "$LIBRARY_EXPANSION_MODE" --argjson max_additional "$LIBRARY_MAX_ADDITIONAL" \ + '{normalizedRequirements:$nr,conflicts:$cf,availableLibraries:$libs,libraryPolicy:{mode:$mode,expansionMode:$expansion,maxAdditionalLibraries:$max_additional},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")" @@ -561,7 +566,8 @@ if [[ "$NATIVE_RAW_CANDIDATE_SEARCH" == "1" ]]; then 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" --argjson libs "$AVAILABLE_LIBRARIES_JSON" --arg strict "$STRICT_EXECUTION_CONTRACT" \ - '{normalizedRequirements:$nr,conflicts:$cf,availableLibraries:$libs,strictExecutionContract:($strict == "1")}')" + --arg mode "$LIBRARY_POLICY_MODE" --arg expansion "$LIBRARY_EXPANSION_MODE" --argjson max_additional "$LIBRARY_MAX_ADDITIONAL" \ + '{normalizedRequirements:$nr,conflicts:$cf,availableLibraries:$libs,libraryPolicy:{mode:$mode,expansionMode:$expansion,maxAdditionalLibraries:$max_additional},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")" @@ -625,7 +631,8 @@ if [[ "$NATIVE_RAW_CANDIDATE_SEARCH" == "1" ]]; then 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" --argjson libs "$AVAILABLE_LIBRARIES_JSON" --arg strict "$STRICT_EXECUTION_CONTRACT" \ - '{normalizedRequirements:$nr,conflicts:$cf,availableLibraries:$libs,strictExecutionContract:($strict == "1")}')" + --arg mode "$LIBRARY_POLICY_MODE" --arg expansion "$LIBRARY_EXPANSION_MODE" --argjson max_additional "$LIBRARY_MAX_ADDITIONAL" \ + '{normalizedRequirements:$nr,conflicts:$cf,availableLibraries:$libs,libraryPolicy:{mode:$mode,expansionMode:$expansion,maxAdditionalLibraries:$max_additional},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")" @@ -813,7 +820,8 @@ if [[ "$NATIVE_MULTISHOT_DECOMP" == "1" ]]; then 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" --argjson libs "$AVAILABLE_LIBRARIES_JSON" --arg strict "$STRICT_EXECUTION_CONTRACT" \ - '{normalizedRequirements:$nr,conflicts:$cf,availableLibraries:$libs,strictExecutionContract:($strict == "1")}')" + --arg mode "$LIBRARY_POLICY_MODE" --arg expansion "$LIBRARY_EXPANSION_MODE" --argjson max_additional "$LIBRARY_MAX_ADDITIONAL" \ + '{normalizedRequirements:$nr,conflicts:$cf,availableLibraries:$libs,libraryPolicy:{mode:$mode,expansionMode:$expansion,maxAdditionalLibraries:$max_additional},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")"