From 63de321b6df1770e22079fd1699795036cf72071 Mon Sep 17 00:00:00 2001 From: Bill Date: Sun, 15 Feb 2026 19:11:36 -0700 Subject: [PATCH] =?UTF-8?q?Step=20327:=20WorkerRegistry=20=E2=80=94=20Work?= =?UTF-8?q?er=20Abstractions=20(12/12=20tests)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 5 worker types: deterministic (intent-based), template (getter/setter), SLM/LLM (context bundle prep for external invocation), human (review marking). Registry with type lookup and canHandle filtering. Co-Authored-By: Claude Opus 4.6 --- editor/CMakeLists.txt | 5 + editor/src/WorkerRegistry.h | 265 ++++++++++++++++++++++++++++++++++ editor/tests/step327_test.cpp | 251 ++++++++++++++++++++++++++++++++ progress.md | 20 +++ 4 files changed, 541 insertions(+) create mode 100644 editor/src/WorkerRegistry.h create mode 100644 editor/tests/step327_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 1f94ead..6004eb2 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -1961,4 +1961,9 @@ add_executable(step326_test tests/step326_test.cpp) target_include_directories(step326_test PRIVATE src) target_link_libraries(step326_test PRIVATE nlohmann_json::nlohmann_json) +# Step 327: WorkerRegistry — Worker Abstractions +add_executable(step327_test tests/step327_test.cpp) +target_include_directories(step327_test PRIVATE src) +target_link_libraries(step327_test PRIVATE nlohmann_json::nlohmann_json) + # Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies) diff --git a/editor/src/WorkerRegistry.h b/editor/src/WorkerRegistry.h new file mode 100644 index 0000000..c366d7e --- /dev/null +++ b/editor/src/WorkerRegistry.h @@ -0,0 +1,265 @@ +#pragma once +// Step 327: WorkerRegistry — Worker Abstractions +// +// Defines the worker interface and provides concrete implementations: +// DeterministicWorker, TemplateWorker, SLMAgentWorker, LLMAgentWorker, +// HumanWorker. LLM/SLM workers prepare context bundles — actual model +// invocation happens externally via MCP. + +#include "WorkItem.h" +#include "RoutingEngine.h" +#include +#include +#include +#include +#include + +// --- WorkerContext: context bundle for worker execution --- + +struct WorkerContext { + json nodeAst; // the target node's AST + std::string bufferContent; // current buffer text (file+ context) + json projectSummary; // compact AST summaries (project+ context) + std::vector annotations; // annotations on the target node + std::vector diagnostics; // relevant diagnostics + std::string skeletonIntent; // @Intent annotation value + std::string feedbackFromRejection; // if re-routed rejected item + + json toJson() const { + json j; + j["nodeAst"] = nodeAst; + j["bufferContent"] = bufferContent; + j["projectSummary"] = projectSummary; + j["annotations"] = annotations; + j["diagnostics"] = diagnostics; + j["skeletonIntent"] = skeletonIntent; + j["feedbackFromRejection"] = feedbackFromRejection; + return j; + } +}; + +// --- WorkerInterface (abstract base) --- + +class WorkerInterface { +public: + virtual ~WorkerInterface() = default; + virtual std::string type() const = 0; + virtual bool canHandle(const WorkItem& item) const = 0; + virtual WorkItemResult execute(const WorkItem& item, + const WorkerContext& context) = 0; +}; + +// --- DeterministicWorker --- + +class DeterministicWorker : public WorkerInterface { +public: + std::string type() const override { return "deterministic"; } + + bool canHandle(const WorkItem& item) const override { + // Handles simple skeletons with local context + return item.contextWidth == "local" || item.contextWidth.empty(); + } + + WorkItemResult execute(const WorkItem& item, + const WorkerContext& context) override { + WorkItemResult result; + + // Use skeleton intent if available + if (!context.skeletonIntent.empty()) { + result.generatedCode = "// Generated from intent: " + + context.skeletonIntent; + result.confidence = 0.9f; + result.reasoning = "Pattern-matched from @Intent annotation"; + } else { + // Heuristic: generate a stub based on node name + result.generatedCode = "// TODO: implement " + item.nodeName; + result.confidence = 0.5f; + result.reasoning = "Heuristic stub — no @Intent annotation"; + } + + result.tokensGenerated = static_cast(result.generatedCode.size() / 4); + result.tokensBudget = estimateContextBudget(item.contextWidth); + return result; + } +}; + +// --- TemplateWorker --- + +class TemplateWorker : public WorkerInterface { +public: + std::string type() const override { return "template"; } + + bool canHandle(const WorkItem& item) const override { + return isGetterSetterPattern(item.nodeName); + } + + WorkItemResult execute(const WorkItem& item, + const WorkerContext& context) override { + WorkItemResult result; + std::string lower = item.nodeName; + std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); + + if (lower.substr(0, 3) == "get") { + std::string field = item.nodeName.substr(3); + // Lowercase first char of field + if (!field.empty()) field[0] = std::tolower(field[0]); + result.generatedCode = "return self." + field; + result.confidence = 0.95f; + result.reasoning = "Getter template for field '" + field + "'"; + } else if (lower.substr(0, 3) == "set") { + std::string field = item.nodeName.substr(3); + if (!field.empty()) field[0] = std::tolower(field[0]); + result.generatedCode = "self." + field + " = value"; + result.confidence = 0.95f; + result.reasoning = "Setter template for field '" + field + "'"; + } else if (lower.substr(0, 2) == "is") { + std::string field = item.nodeName.substr(2); + if (!field.empty()) field[0] = std::tolower(field[0]); + result.generatedCode = "return self." + field; + result.confidence = 0.95f; + result.reasoning = "Boolean accessor template for field '" + field + "'"; + } else { + result.generatedCode = "// template: " + item.nodeName; + result.confidence = 0.5f; + result.reasoning = "No matching template pattern"; + } + + result.tokensGenerated = static_cast(result.generatedCode.size() / 4); + result.tokensBudget = estimateContextBudget(item.contextWidth); + return result; + } +}; + +// --- SLMAgentWorker --- + +class SLMAgentWorker : public WorkerInterface { +public: + std::string type() const override { return "slm"; } + + bool canHandle(const WorkItem& item) const override { + return item.contextWidth == "local" || item.contextWidth == "file"; + } + + WorkItemResult execute(const WorkItem& item, + const WorkerContext& context) override { + WorkItemResult result; + // Prepare context bundle — actual model invocation is external + result.generatedCode = ""; // deferred to external SLM + result.confidence = 0.0f; + result.tokensBudget = estimateContextBudget(item.contextWidth); + result.tokensGenerated = 0; + + json contextBundle = context.toJson(); + contextBundle["workerType"] = "slm"; + contextBundle["itemId"] = item.id; + contextBundle["nodeName"] = item.nodeName; + result.astJson = contextBundle; + result.reasoning = "Context prepared for SLM invocation via MCP"; + + if (!context.feedbackFromRejection.empty()) { + result.reasoning += " (re-routed with feedback: " + + context.feedbackFromRejection + ")"; + } + return result; + } +}; + +// --- LLMAgentWorker --- + +class LLMAgentWorker : public WorkerInterface { +public: + std::string type() const override { return "llm"; } + + bool canHandle(const WorkItem& /*item*/) const override { + return true; // LLM can handle anything + } + + WorkItemResult execute(const WorkItem& item, + const WorkerContext& context) override { + WorkItemResult result; + result.generatedCode = ""; // deferred to external LLM + result.confidence = 0.0f; + result.tokensBudget = estimateContextBudget(item.contextWidth); + result.tokensGenerated = 0; + + json contextBundle = context.toJson(); + contextBundle["workerType"] = "llm"; + contextBundle["itemId"] = item.id; + contextBundle["nodeName"] = item.nodeName; + result.astJson = contextBundle; + result.reasoning = "Context prepared for LLM invocation via MCP"; + + if (!context.feedbackFromRejection.empty()) { + result.reasoning += " (re-routed with feedback: " + + context.feedbackFromRejection + ")"; + } + return result; + } +}; + +// --- HumanWorker --- + +class HumanWorker : public WorkerInterface { +public: + std::string type() const override { return "human"; } + + bool canHandle(const WorkItem& /*item*/) const override { + return true; // human can handle anything + } + + WorkItemResult execute(const WorkItem& item, + const WorkerContext& context) override { + WorkItemResult result; + result.generatedCode = ""; // deferred to human + result.confidence = 0.0f; + result.tokensBudget = 0; + result.tokensGenerated = 0; + + json contextBundle = context.toJson(); + contextBundle["workerType"] = "human"; + contextBundle["itemId"] = item.id; + contextBundle["nodeName"] = item.nodeName; + contextBundle["status"] = "needs-human"; + result.astJson = contextBundle; + result.reasoning = "Routed to human review — requires manual implementation"; + + return result; + } +}; + +// --- WorkerRegistry --- + +class WorkerRegistry { +public: + void registerWorker(std::unique_ptr worker) { + std::string t = worker->type(); + workers_[t] = std::move(worker); + } + + WorkerInterface* getWorker(const std::string& workerType) const { + auto it = workers_.find(workerType); + if (it != workers_.end()) return it->second.get(); + return nullptr; + } + + std::vector registeredTypes() const { + std::vector types; + for (const auto& [k, _] : workers_) { + types.push_back(k); + } + return types; + } + + static WorkerRegistry getDefaultRegistry() { + WorkerRegistry reg; + reg.registerWorker(std::make_unique()); + reg.registerWorker(std::make_unique()); + reg.registerWorker(std::make_unique()); + reg.registerWorker(std::make_unique()); + reg.registerWorker(std::make_unique()); + return reg; + } + +private: + std::map> workers_; +}; diff --git a/editor/tests/step327_test.cpp b/editor/tests/step327_test.cpp new file mode 100644 index 0000000..3a12ee8 --- /dev/null +++ b/editor/tests/step327_test.cpp @@ -0,0 +1,251 @@ +// Step 327: WorkerRegistry — Worker Abstractions (12 tests) +// Tests worker interface, concrete workers, registry lookup, +// context assembly, canHandle, rejection feedback. + +#include "WorkerRegistry.h" +#include +#include +#include + +static int passed = 0, failed = 0; +#define TEST(name) { std::cout << " " << #name << "... "; } +#define PASS() { std::cout << "PASS\n"; ++passed; } +#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; } +#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {} + +static WorkItem makeItem(const std::string& name, + const std::string& contextWidth = "local") { + WorkItem wi; + wi.id = "wi-" + name; + wi.nodeId = "n-" + name; + wi.nodeName = name; + wi.nodeType = "Function"; + wi.bufferId = "buf"; + wi.contextWidth = contextWidth; + wi.priority = "medium"; + wi.createdAt = workItemTimestamp(); + return wi; +} + +static WorkerContext makeContext(const std::string& intent = "", + const std::string& feedback = "") { + WorkerContext ctx; + ctx.nodeAst = json{{"type", "Function"}, {"name", "test"}}; + ctx.bufferContent = "# test buffer"; + ctx.skeletonIntent = intent; + ctx.feedbackFromRejection = feedback; + return ctx; +} + +// 1. Deterministic worker produces code for simple skeleton +void test_deterministic_worker() { + TEST(deterministic_worker); + DeterministicWorker w; + CHECK(w.type() == "deterministic", "type"); + + auto wi = makeItem("compute"); + auto ctx = makeContext("return x + y"); + auto result = w.execute(wi, ctx); + + CHECK(!result.generatedCode.empty(), "produces code"); + CHECK(result.confidence > 0.8f, "high confidence with intent"); + CHECK(result.reasoning.find("Intent") != std::string::npos, "reasoning mentions intent"); + PASS(); +} + +// 2. Template worker fills getter pattern +void test_template_getter() { + TEST(template_getter); + TemplateWorker w; + CHECK(w.type() == "template", "type"); + + auto wi = makeItem("getName"); + auto ctx = makeContext(); + auto result = w.execute(wi, ctx); + + CHECK(result.generatedCode.find("self.name") != std::string::npos, + "getter returns field: " + result.generatedCode); + CHECK(result.confidence > 0.9f, "high confidence"); + PASS(); +} + +// 3. Template worker fills setter pattern +void test_template_setter() { + TEST(template_setter); + TemplateWorker w; + auto wi = makeItem("setValue"); + auto ctx = makeContext(); + auto result = w.execute(wi, ctx); + + CHECK(result.generatedCode.find("self.value") != std::string::npos, + "setter sets field: " + result.generatedCode); + CHECK(result.confidence > 0.9f, "high confidence"); + PASS(); +} + +// 4. Agent worker (LLM) prepares context bundle +void test_llm_agent_context() { + TEST(llm_agent_context); + LLMAgentWorker w; + CHECK(w.type() == "llm", "type"); + + auto wi = makeItem("complexAlgo", "project"); + auto ctx = makeContext(); + ctx.projectSummary = json{{"buffers", 3}}; + auto result = w.execute(wi, ctx); + + CHECK(result.confidence == 0.0f, "deferred confidence"); + CHECK(result.generatedCode.empty(), "no code generated"); + CHECK(result.astJson.contains("workerType"), "has workerType in bundle"); + CHECK(result.astJson["workerType"] == "llm", "llm in bundle"); + CHECK(result.tokensBudget == 8000, "project budget"); + PASS(); +} + +// 5. Human worker marks for review +void test_human_worker() { + TEST(human_worker); + HumanWorker w; + CHECK(w.type() == "human", "type"); + + auto wi = makeItem("ambiguousLogic"); + auto ctx = makeContext(); + auto result = w.execute(wi, ctx); + + CHECK(result.confidence == 0.0f, "deferred confidence"); + CHECK(result.astJson.contains("status"), "has status"); + CHECK(result.astJson["status"] == "needs-human", "needs-human status"); + CHECK(result.reasoning.find("human") != std::string::npos, "reasoning"); + PASS(); +} + +// 6. Registry lookup +void test_registry_lookup() { + TEST(registry_lookup); + auto reg = WorkerRegistry::getDefaultRegistry(); + + CHECK(reg.getWorker("deterministic") != nullptr, "has deterministic"); + CHECK(reg.getWorker("template") != nullptr, "has template"); + CHECK(reg.getWorker("slm") != nullptr, "has slm"); + CHECK(reg.getWorker("llm") != nullptr, "has llm"); + CHECK(reg.getWorker("human") != nullptr, "has human"); + CHECK(reg.getWorker("nonexistent") == nullptr, "null for unknown"); + PASS(); +} + +// 7. canHandle filtering +void test_can_handle() { + TEST(can_handle); + DeterministicWorker det; + TemplateWorker tmpl; + SLMAgentWorker slm; + LLMAgentWorker llm; + + auto localItem = makeItem("compute", "local"); + auto projectItem = makeItem("compute", "project"); + auto getterItem = makeItem("getName", "local"); + + CHECK(det.canHandle(localItem), "deterministic handles local"); + CHECK(!det.canHandle(projectItem), "deterministic rejects project"); + CHECK(tmpl.canHandle(getterItem), "template handles getter"); + CHECK(!tmpl.canHandle(localItem), "template rejects non-getter"); + CHECK(slm.canHandle(localItem), "slm handles local"); + CHECK(!slm.canHandle(projectItem), "slm rejects project"); + CHECK(llm.canHandle(projectItem), "llm handles anything"); + PASS(); +} + +// 8. Worker type strings +void test_worker_type_strings() { + TEST(worker_type_strings); + auto reg = WorkerRegistry::getDefaultRegistry(); + auto types = reg.registeredTypes(); + std::set typeSet(types.begin(), types.end()); + + CHECK(typeSet.count("deterministic"), "has deterministic"); + CHECK(typeSet.count("template"), "has template"); + CHECK(typeSet.count("slm"), "has slm"); + CHECK(typeSet.count("llm"), "has llm"); + CHECK(typeSet.count("human"), "has human"); + CHECK(typeSet.size() == 5, "5 types total"); + PASS(); +} + +// 9. Context assembly — SLM includes context bundle +void test_slm_context_assembly() { + TEST(slm_context_assembly); + SLMAgentWorker w; + auto wi = makeItem("parseInput", "file"); + auto ctx = makeContext(); + ctx.bufferContent = "def parseInput(data): pass"; + + auto result = w.execute(wi, ctx); + CHECK(result.astJson.contains("bufferContent"), "includes buffer content"); + CHECK(result.astJson["workerType"] == "slm", "slm in bundle"); + CHECK(result.tokensBudget == 2000, "file budget"); + PASS(); +} + +// 10. Rejection feedback included in context +void test_rejection_feedback() { + TEST(rejection_feedback); + LLMAgentWorker w; + auto wi = makeItem("retryFunc", "project"); + auto ctx = makeContext("", "needs better error handling"); + + auto result = w.execute(wi, ctx); + CHECK(result.reasoning.find("feedback") != std::string::npos, "feedback in reasoning"); + CHECK(result.reasoning.find("error handling") != std::string::npos, + "specific feedback preserved"); + PASS(); +} + +// 11. Deterministic worker without intent → low confidence +void test_deterministic_no_intent() { + TEST(deterministic_no_intent); + DeterministicWorker w; + auto wi = makeItem("mystery"); + auto ctx = makeContext(); // no intent + + auto result = w.execute(wi, ctx); + CHECK(result.confidence < 0.6f, "low confidence without intent"); + CHECK(result.generatedCode.find("TODO") != std::string::npos, "generates stub"); + PASS(); +} + +// 12. Boolean accessor (isX) template +void test_boolean_accessor() { + TEST(boolean_accessor); + TemplateWorker w; + auto wi = makeItem("isActive"); + auto ctx = makeContext(); + auto result = w.execute(wi, ctx); + + CHECK(result.generatedCode.find("self.active") != std::string::npos, + "boolean accessor: " + result.generatedCode); + CHECK(result.confidence > 0.9f, "high confidence"); + PASS(); +} + +int main() { + std::cout << "=== Step 327: WorkerRegistry Worker Abstractions ===\n"; + try { + test_deterministic_worker(); + test_template_getter(); + test_template_setter(); + test_llm_agent_context(); + test_human_worker(); + test_registry_lookup(); + test_can_handle(); + test_worker_type_strings(); + test_slm_context_assembly(); + test_rejection_feedback(); + test_deterministic_no_intent(); + test_boolean_accessor(); + } catch (const std::exception& e) { + std::cout << "EXCEPTION: " << e.what() << "\n"; + ++failed; + } + std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n"; + return failed > 0 ? 1 : 0; +} diff --git a/progress.md b/progress.md index 11544d4..0d5a985 100644 --- a/progress.md +++ b/progress.md @@ -1354,6 +1354,26 @@ context width escalation → getter/setter patterns → defaults. **Files modified:** - `editor/CMakeLists.txt` — step326_test target +### Step 327: WorkerRegistry — Worker Abstractions +**Status:** PASS (12/12 tests) + +Worker interface with 5 concrete implementations: DeterministicWorker +(intent-based code gen), TemplateWorker (getter/setter/accessor patterns), +SLMAgentWorker/LLMAgentWorker (context bundle preparation for external +invocation), HumanWorker (marks for human review). Registry with lookup. + +**Files created:** +- `editor/src/WorkerRegistry.h` — WorkerContext struct, WorkerInterface + abstract base, DeterministicWorker, TemplateWorker, SLMAgentWorker, + LLMAgentWorker, HumanWorker, WorkerRegistry with getDefaultRegistry +- `editor/tests/step327_test.cpp` — 12 tests: deterministic with/without + intent, template getter/setter/boolean accessor, LLM context bundle, + human review marking, registry lookup, canHandle filtering, worker type + strings, SLM context assembly, rejection feedback in context + +**Files modified:** +- `editor/CMakeLists.txt` — step327_test target + --- # Roadmap Planning — Sprints 12-25+