From 25ab38d18567304a4cc08bc67b31c9d7feb2568f Mon Sep 17 00:00:00 2001 From: Bill Date: Sun, 15 Feb 2026 16:17:35 -0700 Subject: [PATCH] =?UTF-8?q?Step=20320:=20WorkItem=20=E2=80=94=20Core=20Exe?= =?UTF-8?q?cution=20Model=20(12/12=20tests)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sprint 12 Phase 12a start. WorkItem extends SkeletonTask with execution lifecycle tracking: 7-state machine, worker assignment, timestamps, results, and full JSON serialization. Co-Authored-By: Claude Opus 4.6 --- editor/CMakeLists.txt | 5 + editor/src/WorkItem.h | 220 ++++++++++++++++++++++++++ editor/tests/step320_test.cpp | 285 ++++++++++++++++++++++++++++++++++ progress.md | 29 ++++ 4 files changed, 539 insertions(+) create mode 100644 editor/src/WorkItem.h create mode 100644 editor/tests/step320_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 87ece25..2c17627 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -1916,4 +1916,9 @@ target_link_libraries(step318_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +# Step 320: WorkItem — Core Execution Model +add_executable(step320_test tests/step320_test.cpp) +target_include_directories(step320_test PRIVATE src) +target_link_libraries(step320_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/WorkItem.h b/editor/src/WorkItem.h new file mode 100644 index 0000000..37ee191 --- /dev/null +++ b/editor/src/WorkItem.h @@ -0,0 +1,220 @@ +#pragma once +// Step 320: WorkItem — Core Execution Model +// +// Extends SkeletonTask with execution lifecycle tracking: status transitions, +// worker assignment, timestamps, and results. This is the unit of work that +// flows through the routing engine. + +#include "SkeletonAST.h" +#include +#include +#include +#include +#include +#include +#include + +using json = nlohmann::json; + +// --- WorkItemResult: output produced by a worker --- + +struct WorkItemResult { + std::string generatedCode; + json astJson; + std::vector diagnostics; + float confidence = 0.0f; + int tokensBudget = 0; + int tokensGenerated = 0; + std::string reasoning; + + json toJson() const { + json j; + j["generatedCode"] = generatedCode; + j["astJson"] = astJson; + j["diagnostics"] = diagnostics; + j["confidence"] = confidence; + j["tokensBudget"] = tokensBudget; + j["tokensGenerated"] = tokensGenerated; + j["reasoning"] = reasoning; + return j; + } + + static WorkItemResult fromJson(const json& j) { + WorkItemResult r; + if (j.contains("generatedCode")) r.generatedCode = j["generatedCode"].get(); + if (j.contains("astJson")) r.astJson = j["astJson"]; + if (j.contains("diagnostics")) r.diagnostics = j["diagnostics"].get>(); + if (j.contains("confidence")) r.confidence = j["confidence"].get(); + if (j.contains("tokensBudget")) r.tokensBudget = j["tokensBudget"].get(); + if (j.contains("tokensGenerated")) r.tokensGenerated = j["tokensGenerated"].get(); + if (j.contains("reasoning")) r.reasoning = j["reasoning"].get(); + return r; + } +}; + +// --- WorkItem status values --- + +inline const std::string WI_PENDING = "pending"; +inline const std::string WI_READY = "ready"; +inline const std::string WI_ASSIGNED = "assigned"; +inline const std::string WI_IN_PROGRESS = "in-progress"; +inline const std::string WI_REVIEW = "review"; +inline const std::string WI_COMPLETE = "complete"; +inline const std::string WI_REJECTED = "rejected"; + +// --- WorkItem struct --- + +struct WorkItem { + std::string id; + std::string nodeId; + std::string nodeName; + std::string nodeType; + std::string bufferId; + + // Routing + std::string contextWidth; + std::string workerType; + bool reviewRequired = false; + std::string priority; + std::vector dependencies; + + // Lifecycle + std::string status = WI_PENDING; + + // Assignment + std::string assignee; + std::string createdAt; + std::string assignedAt; + std::string completedAt; + + // Result + WorkItemResult result; +}; + +// --- Helper: ISO timestamp --- + +inline std::string workItemTimestamp() { + auto now = std::chrono::system_clock::now(); + auto time = std::chrono::system_clock::to_time_t(now); + std::tm tm{}; + gmtime_r(&time, &tm); + std::ostringstream oss; + oss << std::put_time(&tm, "%Y-%m-%dT%H:%M:%SZ"); + return oss.str(); +} + +// --- Helper: generate unique ID --- + +inline std::string generateWorkItemId() { + static std::mt19937 rng(std::random_device{}()); + static std::uniform_int_distribution dist; + std::ostringstream oss; + oss << "wi-" << std::hex << dist(rng); + return oss.str(); +} + +// --- createWorkItem from SkeletonTask --- + +inline WorkItem createWorkItem(const SkeletonTask& task, const std::string& bufferId) { + WorkItem wi; + wi.id = generateWorkItemId(); + wi.nodeId = task.nodeId; + wi.nodeName = task.nodeName; + wi.nodeType = task.nodeType; + wi.bufferId = bufferId; + wi.contextWidth = task.contextWidth; + wi.workerType = task.automatability; + wi.reviewRequired = task.reviewRequired; + wi.priority = task.priority; + wi.dependencies = task.dependencies; + wi.status = WI_PENDING; + wi.createdAt = workItemTimestamp(); + return wi; +} + +// --- State machine helpers --- + +inline bool isTerminal(const std::string& status) { + return status == WI_COMPLETE || status == WI_REJECTED; +} + +inline bool canTransition(const std::string& from, const std::string& to) { + // pending → ready + if (from == WI_PENDING && to == WI_READY) return true; + // ready → assigned + if (from == WI_READY && to == WI_ASSIGNED) return true; + // assigned → in-progress + if (from == WI_ASSIGNED && to == WI_IN_PROGRESS) return true; + // in-progress → review | complete + if (from == WI_IN_PROGRESS && (to == WI_REVIEW || to == WI_COMPLETE)) return true; + // review → complete | rejected + if (from == WI_REVIEW && (to == WI_COMPLETE || to == WI_REJECTED)) return true; + // rejected → ready (retry) + if (from == WI_REJECTED && to == WI_READY) return true; + return false; +} + +inline bool transitionWorkItem(WorkItem& item, const std::string& newStatus) { + if (!canTransition(item.status, newStatus)) return false; + item.status = newStatus; + if (newStatus == WI_ASSIGNED) { + item.assignedAt = workItemTimestamp(); + } else if (newStatus == WI_COMPLETE || newStatus == WI_REJECTED) { + item.completedAt = workItemTimestamp(); + } + return true; +} + +// --- Priority ordering --- + +inline int priorityToInt(const std::string& p) { + if (p == "critical") return 0; + if (p == "high") return 1; + if (p == "medium") return 2; + if (p == "low") return 3; + return 4; // unknown +} + +// --- JSON serialization --- + +inline json workItemToJson(const WorkItem& wi) { + json j; + j["id"] = wi.id; + j["nodeId"] = wi.nodeId; + j["nodeName"] = wi.nodeName; + j["nodeType"] = wi.nodeType; + j["bufferId"] = wi.bufferId; + j["contextWidth"] = wi.contextWidth; + j["workerType"] = wi.workerType; + j["reviewRequired"] = wi.reviewRequired; + j["priority"] = wi.priority; + j["dependencies"] = wi.dependencies; + j["status"] = wi.status; + j["assignee"] = wi.assignee; + j["createdAt"] = wi.createdAt; + j["assignedAt"] = wi.assignedAt; + j["completedAt"] = wi.completedAt; + j["result"] = wi.result.toJson(); + return j; +} + +inline WorkItem workItemFromJson(const json& j) { + WorkItem wi; + if (j.contains("id")) wi.id = j["id"].get(); + if (j.contains("nodeId")) wi.nodeId = j["nodeId"].get(); + if (j.contains("nodeName")) wi.nodeName = j["nodeName"].get(); + if (j.contains("nodeType")) wi.nodeType = j["nodeType"].get(); + if (j.contains("bufferId")) wi.bufferId = j["bufferId"].get(); + if (j.contains("contextWidth")) wi.contextWidth = j["contextWidth"].get(); + if (j.contains("workerType")) wi.workerType = j["workerType"].get(); + if (j.contains("reviewRequired")) wi.reviewRequired = j["reviewRequired"].get(); + if (j.contains("priority")) wi.priority = j["priority"].get(); + if (j.contains("dependencies")) wi.dependencies = j["dependencies"].get>(); + if (j.contains("status")) wi.status = j["status"].get(); + if (j.contains("assignee")) wi.assignee = j["assignee"].get(); + if (j.contains("createdAt")) wi.createdAt = j["createdAt"].get(); + if (j.contains("assignedAt")) wi.assignedAt = j["assignedAt"].get(); + if (j.contains("completedAt")) wi.completedAt = j["completedAt"].get(); + if (j.contains("result")) wi.result = WorkItemResult::fromJson(j["result"]); + return wi; +} diff --git a/editor/tests/step320_test.cpp b/editor/tests/step320_test.cpp new file mode 100644 index 0000000..a865406 --- /dev/null +++ b/editor/tests/step320_test.cpp @@ -0,0 +1,285 @@ +// Step 320: WorkItem — Core Execution Model (12 tests) +// Tests lifecycle state machine, JSON roundtrip, timestamps, priority ordering. + +#include "WorkItem.h" +#include +#include +#include +#include + +using json = nlohmann::json; + +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 {} + +// 1. WorkItem construction from SkeletonTask — fields copied correctly +void test_create_from_skeleton() { + TEST(create_from_skeleton); + SkeletonTask task; + task.nodeId = "fn_42"; + task.nodeName = "processData"; + task.nodeType = "Function"; + task.contextWidth = "module"; + task.automatability = "llm"; + task.priority = "high"; + task.reviewRequired = true; + task.dependencies = {"fn_10", "fn_20"}; + + WorkItem wi = createWorkItem(task, "buf-1"); + CHECK(wi.nodeId == "fn_42", "nodeId"); + CHECK(wi.nodeName == "processData", "nodeName"); + CHECK(wi.nodeType == "Function", "nodeType"); + CHECK(wi.bufferId == "buf-1", "bufferId"); + CHECK(wi.contextWidth == "module", "contextWidth"); + CHECK(wi.workerType == "llm", "workerType"); + CHECK(wi.priority == "high", "priority"); + CHECK(wi.reviewRequired == true, "reviewRequired"); + CHECK(wi.dependencies.size() == 2, "dependencies count"); + CHECK(wi.status == "pending", "initial status"); + PASS(); +} + +// 2. ID generation — unique, non-empty +void test_id_generation() { + TEST(id_generation); + SkeletonTask task; + task.nodeId = "n1"; + task.nodeName = "foo"; + task.nodeType = "Function"; + + std::set ids; + for (int i = 0; i < 100; ++i) { + WorkItem wi = createWorkItem(task, "buf"); + CHECK(!wi.id.empty(), "ID non-empty"); + ids.insert(wi.id); + } + CHECK(ids.size() == 100, "All 100 IDs unique, got " + std::to_string(ids.size())); + PASS(); +} + +// 3. Valid state transitions +void test_valid_transitions() { + TEST(valid_transitions); + CHECK(canTransition("pending", "ready"), "pending→ready"); + CHECK(canTransition("ready", "assigned"), "ready→assigned"); + CHECK(canTransition("assigned", "in-progress"), "assigned→in-progress"); + CHECK(canTransition("in-progress", "review"), "in-progress→review"); + CHECK(canTransition("in-progress", "complete"), "in-progress→complete"); + CHECK(canTransition("review", "complete"), "review→complete"); + CHECK(canTransition("review", "rejected"), "review→rejected"); + CHECK(canTransition("rejected", "ready"), "rejected→ready"); + PASS(); +} + +// 4. Invalid state transitions rejected +void test_invalid_transitions() { + TEST(invalid_transitions); + CHECK(!canTransition("pending", "complete"), "pending→complete blocked"); + CHECK(!canTransition("complete", "ready"), "complete→ready blocked"); + CHECK(!canTransition("pending", "in-progress"), "pending→in-progress blocked"); + CHECK(!canTransition("assigned", "complete"), "assigned→complete blocked"); + CHECK(!canTransition("ready", "review"), "ready→review blocked"); + CHECK(!canTransition("complete", "pending"), "complete→pending blocked"); + PASS(); +} + +// 5. transitionWorkItem populates timestamps +void test_transition_timestamps() { + TEST(transition_timestamps); + SkeletonTask task; + task.nodeId = "n1"; + task.nodeName = "foo"; + task.nodeType = "Function"; + WorkItem wi = createWorkItem(task, "buf"); + + CHECK(wi.assignedAt.empty(), "assignedAt initially empty"); + CHECK(wi.completedAt.empty(), "completedAt initially empty"); + + transitionWorkItem(wi, "ready"); + transitionWorkItem(wi, "assigned"); + CHECK(!wi.assignedAt.empty(), "assignedAt set on assigned"); + + transitionWorkItem(wi, "in-progress"); + transitionWorkItem(wi, "complete"); + CHECK(!wi.completedAt.empty(), "completedAt set on complete"); + PASS(); +} + +// 6. WorkItemResult toJson/fromJson roundtrip +void test_result_roundtrip() { + TEST(result_roundtrip); + WorkItemResult r; + r.generatedCode = "def foo(): return 42"; + r.astJson = json::parse(R"({"type":"Function","name":"foo"})"); + r.diagnostics = {json{{"level", "warning"}, {"msg", "unused var"}}}; + r.confidence = 0.95f; + r.tokensBudget = 4000; + r.tokensGenerated = 1200; + r.reasoning = "Simple pure function"; + + json j = r.toJson(); + WorkItemResult r2 = WorkItemResult::fromJson(j); + CHECK(r2.generatedCode == r.generatedCode, "generatedCode"); + CHECK(r2.astJson == r.astJson, "astJson"); + CHECK(r2.diagnostics.size() == 1, "diagnostics count"); + CHECK(r2.confidence > 0.94f && r2.confidence < 0.96f, "confidence"); + CHECK(r2.tokensBudget == 4000, "tokensBudget"); + CHECK(r2.tokensGenerated == 1200, "tokensGenerated"); + CHECK(r2.reasoning == "Simple pure function", "reasoning"); + PASS(); +} + +// 7. WorkItem toJson/fromJson roundtrip — all fields preserved +void test_workitem_roundtrip() { + TEST(workitem_roundtrip); + WorkItem wi; + wi.id = "wi-abc123"; + wi.nodeId = "fn_1"; + wi.nodeName = "compute"; + wi.nodeType = "Function"; + wi.bufferId = "buf-main"; + wi.contextWidth = "project"; + wi.workerType = "llm"; + wi.reviewRequired = true; + wi.priority = "critical"; + wi.dependencies = {"fn_0"}; + wi.status = "in-progress"; + wi.assignee = "worker-7"; + wi.createdAt = "2026-01-01T00:00:00Z"; + wi.assignedAt = "2026-01-01T00:01:00Z"; + wi.completedAt = ""; + wi.result.generatedCode = "int x = 1;"; + wi.result.confidence = 0.8f; + + json j = workItemToJson(wi); + WorkItem wi2 = workItemFromJson(j); + + CHECK(wi2.id == wi.id, "id"); + CHECK(wi2.nodeId == wi.nodeId, "nodeId"); + CHECK(wi2.nodeName == wi.nodeName, "nodeName"); + CHECK(wi2.nodeType == wi.nodeType, "nodeType"); + CHECK(wi2.bufferId == wi.bufferId, "bufferId"); + CHECK(wi2.contextWidth == wi.contextWidth, "contextWidth"); + CHECK(wi2.workerType == wi.workerType, "workerType"); + CHECK(wi2.reviewRequired == wi.reviewRequired, "reviewRequired"); + CHECK(wi2.priority == wi.priority, "priority"); + CHECK(wi2.dependencies.size() == 1, "dependencies"); + CHECK(wi2.status == wi.status, "status"); + CHECK(wi2.assignee == wi.assignee, "assignee"); + CHECK(wi2.createdAt == wi.createdAt, "createdAt"); + CHECK(wi2.assignedAt == wi.assignedAt, "assignedAt"); + CHECK(wi2.result.generatedCode == "int x = 1;", "result.generatedCode"); + PASS(); +} + +// 8. isTerminal — complete=true, rejected=true, others=false +void test_is_terminal() { + TEST(is_terminal); + CHECK(isTerminal("complete"), "complete is terminal"); + CHECK(isTerminal("rejected"), "rejected is terminal"); + CHECK(!isTerminal("pending"), "pending not terminal"); + CHECK(!isTerminal("ready"), "ready not terminal"); + CHECK(!isTerminal("assigned"), "assigned not terminal"); + CHECK(!isTerminal("in-progress"), "in-progress not terminal"); + CHECK(!isTerminal("review"), "review not terminal"); + PASS(); +} + +// 9. createWorkItem sets createdAt timestamp +void test_created_at_timestamp() { + TEST(created_at_timestamp); + SkeletonTask task; + task.nodeId = "n1"; + task.nodeName = "bar"; + task.nodeType = "Function"; + WorkItem wi = createWorkItem(task, "buf"); + CHECK(!wi.createdAt.empty(), "createdAt non-empty"); + // Should look like ISO timestamp: starts with 20 + CHECK(wi.createdAt.substr(0, 2) == "20", "createdAt starts with year"); + CHECK(wi.createdAt.find('T') != std::string::npos, "createdAt has T separator"); + PASS(); +} + +// 10. Dependencies preserved from SkeletonTask +void test_dependencies_preserved() { + TEST(dependencies_preserved); + SkeletonTask task; + task.nodeId = "n5"; + task.nodeName = "aggregate"; + task.nodeType = "Function"; + task.dependencies = {"n1", "n2", "n3"}; + + WorkItem wi = createWorkItem(task, "buf"); + CHECK(wi.dependencies.size() == 3, "3 dependencies"); + CHECK(wi.dependencies[0] == "n1", "dep 0"); + CHECK(wi.dependencies[1] == "n2", "dep 1"); + CHECK(wi.dependencies[2] == "n3", "dep 2"); + PASS(); +} + +// 11. priorityToInt ordering (critical < high < medium < low) +void test_priority_ordering() { + TEST(priority_ordering); + CHECK(priorityToInt("critical") < priorityToInt("high"), "critical < high"); + CHECK(priorityToInt("high") < priorityToInt("medium"), "high < medium"); + CHECK(priorityToInt("medium") < priorityToInt("low"), "medium < low"); + CHECK(priorityToInt("critical") == 0, "critical=0"); + CHECK(priorityToInt("low") == 3, "low=3"); + PASS(); +} + +// 12. Result attachment — set result, verify in JSON roundtrip +void test_result_attachment() { + TEST(result_attachment); + SkeletonTask task; + task.nodeId = "n1"; + task.nodeName = "gen"; + task.nodeType = "Function"; + task.contextWidth = "local"; + task.automatability = "llm"; + task.priority = "medium"; + + WorkItem wi = createWorkItem(task, "buf-2"); + wi.result.generatedCode = "return 42;"; + wi.result.confidence = 0.99f; + wi.result.tokensBudget = 2000; + wi.result.tokensGenerated = 50; + wi.result.reasoning = "trivial"; + wi.result.diagnostics = {json{{"level", "info"}, {"msg", "ok"}}}; + + json j = workItemToJson(wi); + WorkItem wi2 = workItemFromJson(j); + CHECK(wi2.result.generatedCode == "return 42;", "code preserved"); + CHECK(wi2.result.confidence > 0.98f, "confidence preserved"); + CHECK(wi2.result.tokensBudget == 2000, "budget preserved"); + CHECK(wi2.result.tokensGenerated == 50, "generated preserved"); + CHECK(wi2.result.reasoning == "trivial", "reasoning preserved"); + CHECK(wi2.result.diagnostics.size() == 1, "diagnostics preserved"); + PASS(); +} + +int main() { + std::cout << "=== Step 320: WorkItem Core Execution Model ===\n"; + try { + test_create_from_skeleton(); + test_id_generation(); + test_valid_transitions(); + test_invalid_transitions(); + test_transition_timestamps(); + test_result_roundtrip(); + test_workitem_roundtrip(); + test_is_terminal(); + test_created_at_timestamp(); + test_dependencies_preserved(); + test_priority_ordering(); + test_result_attachment(); + } 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 cefade6..8b18f47 100644 --- a/progress.md +++ b/progress.md @@ -1199,6 +1199,35 @@ Full pipeline integration for Kotlin + C#: --- +# Sprint 12 — Workflow Model + C++ Depth + +## Phase 12a: Core Workflow Model + +### Step 320: WorkItem — Core Execution Model +**Status:** PASS (12/12 tests) + +WorkItem extends SkeletonTask with execution lifecycle tracking — status +transitions, worker assignment, timestamps, and results. This is the unit of +work that flows through the routing engine. + +**Files created:** +- `editor/src/WorkItem.h` — WorkItemResult struct (generatedCode, astJson, + diagnostics, confidence, tokens, reasoning + JSON roundtrip), WorkItem struct + (identity, routing, lifecycle, assignment, result), state machine helpers + (isTerminal, canTransition, transitionWorkItem), createWorkItem from + SkeletonTask, priorityToInt ordering, full JSON serialization +- `editor/tests/step320_test.cpp` — 12 tests: construction from SkeletonTask, + unique ID generation, valid/invalid state transitions, timestamp population, + WorkItemResult roundtrip, WorkItem roundtrip, isTerminal, createdAt, + dependencies preserved, priority ordering, result attachment roundtrip + +**Files modified:** +- `editor/CMakeLists.txt` — step320_test target + +**State machine:** pending→ready→assigned→in-progress→(review→complete|rejected OR complete); rejected→ready + +--- + # Roadmap Planning — Sprints 12-25+ ## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)