Files
whetstone_DSL/editor/src/WorkItem.h
Bill 25ab38d185 Step 320: WorkItem — Core Execution Model (12/12 tests)
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 <noreply@anthropic.com>
2026-02-15 16:17:35 -07:00

221 lines
7.4 KiB
C++

#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 <nlohmann/json.hpp>
#include <string>
#include <vector>
#include <chrono>
#include <random>
#include <sstream>
#include <iomanip>
using json = nlohmann::json;
// --- WorkItemResult: output produced by a worker ---
struct WorkItemResult {
std::string generatedCode;
json astJson;
std::vector<json> 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<std::string>();
if (j.contains("astJson")) r.astJson = j["astJson"];
if (j.contains("diagnostics")) r.diagnostics = j["diagnostics"].get<std::vector<json>>();
if (j.contains("confidence")) r.confidence = j["confidence"].get<float>();
if (j.contains("tokensBudget")) r.tokensBudget = j["tokensBudget"].get<int>();
if (j.contains("tokensGenerated")) r.tokensGenerated = j["tokensGenerated"].get<int>();
if (j.contains("reasoning")) r.reasoning = j["reasoning"].get<std::string>();
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<std::string> 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<uint64_t> 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<std::string>();
if (j.contains("nodeId")) wi.nodeId = j["nodeId"].get<std::string>();
if (j.contains("nodeName")) wi.nodeName = j["nodeName"].get<std::string>();
if (j.contains("nodeType")) wi.nodeType = j["nodeType"].get<std::string>();
if (j.contains("bufferId")) wi.bufferId = j["bufferId"].get<std::string>();
if (j.contains("contextWidth")) wi.contextWidth = j["contextWidth"].get<std::string>();
if (j.contains("workerType")) wi.workerType = j["workerType"].get<std::string>();
if (j.contains("reviewRequired")) wi.reviewRequired = j["reviewRequired"].get<bool>();
if (j.contains("priority")) wi.priority = j["priority"].get<std::string>();
if (j.contains("dependencies")) wi.dependencies = j["dependencies"].get<std::vector<std::string>>();
if (j.contains("status")) wi.status = j["status"].get<std::string>();
if (j.contains("assignee")) wi.assignee = j["assignee"].get<std::string>();
if (j.contains("createdAt")) wi.createdAt = j["createdAt"].get<std::string>();
if (j.contains("assignedAt")) wi.assignedAt = j["assignedAt"].get<std::string>();
if (j.contains("completedAt")) wi.completedAt = j["completedAt"].get<std::string>();
if (j.contains("result")) wi.result = WorkItemResult::fromJson(j["result"]);
return wi;
}