diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 3bb4372..ab38b3e 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -2335,4 +2335,13 @@ target_link_libraries(step383_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step384_test tests/step384_test.cpp) +target_include_directories(step384_test PRIVATE src) +target_link_libraries(step384_test PRIVATE + nlohmann_json::nlohmann_json + unofficial::tree-sitter::tree-sitter + tree_sitter_python tree_sitter_cpp tree_sitter_elisp + tree_sitter_javascript tree_sitter_typescript + tree_sitter_java tree_sitter_rust tree_sitter_go) + # Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies) diff --git a/editor/src/WorkflowProtocol.h b/editor/src/WorkflowProtocol.h new file mode 100644 index 0000000..22d31ae --- /dev/null +++ b/editor/src/WorkflowProtocol.h @@ -0,0 +1,185 @@ +#pragma once +// Step 384: Workflow session protocol for MCP-driven orchestration. + +#include "WorkflowState.h" +#include +#include +#include + +struct WorkflowSession { + std::string sessionId; + std::string projectName; + std::string currentPhase = "init"; + std::string startedAt = workItemTimestamp(); + std::vector commands; + + json toJson() const { + return { + {"sessionId", sessionId}, + {"projectName", projectName}, + {"currentPhase", currentPhase}, + {"startedAt", startedAt}, + {"commands", commands} + }; + } + + static WorkflowSession fromJson(const json& j) { + WorkflowSession s; + s.sessionId = j.value("sessionId", ""); + s.projectName = j.value("projectName", ""); + s.currentPhase = j.value("currentPhase", "init"); + s.startedAt = j.value("startedAt", workItemTimestamp()); + if (j.contains("commands") && j["commands"].is_array()) { + s.commands = j["commands"].get>(); + } + return s; + } +}; + +struct ProtocolAction { + std::string phase; + std::string command; + std::string reason; + + json toJson() const { + return {{"phase", phase}, {"command", command}, {"reason", reason}}; + } +}; + +inline std::vector workflowProtocolPhases() { + return {"init", "model", "annotate", "plan", "route", + "execute", "assist", "review", "complete"}; +} + +inline bool isWorkflowProtocolPhase(const std::string& phase) { + const auto phases = workflowProtocolPhases(); + return std::find(phases.begin(), phases.end(), phase) != phases.end(); +} + +inline int workflowPhaseIndex(const std::string& phase) { + const auto phases = workflowProtocolPhases(); + for (size_t i = 0; i < phases.size(); ++i) { + if (phases[i] == phase) return static_cast(i); + } + return -1; +} + +inline bool validateTransition(const std::string& from, const std::string& to) { + int fromIdx = workflowPhaseIndex(from); + int toIdx = workflowPhaseIndex(to); + if (fromIdx < 0 || toIdx < 0) return false; + if (fromIdx == toIdx) return true; + return toIdx == fromIdx + 1; +} + +inline std::string protocolPhaseForCommand(const std::string& command) { + if (command == "whetstone_create_skeleton") return "model"; + if (command == "whetstone_add_skeleton_node") return "model"; + if (command == "whetstone_infer_annotations") return "annotate"; + if (command == "whetstone_create_workflow") return "plan"; + if (command == "whetstone_orchestrate_advance") return "route"; + if (command == "whetstone_orchestrate_run_deterministic") return "execute"; + if (command == "whetstone_get_blockers" || command == "whetstone_submit_result") + return "assist"; + if (command == "whetstone_get_review_policy" || command == "whetstone_set_review_policy") + return "review"; + if (command == "whetstone_save_workflow") return "complete"; + return ""; +} + +inline void recordWorkflowCommand(WorkflowSession& session, + const std::string& command) { + session.commands.push_back(command); + std::string hintedPhase = protocolPhaseForCommand(command); + if (hintedPhase.empty()) return; + int current = workflowPhaseIndex(session.currentPhase); + int hinted = workflowPhaseIndex(hintedPhase); + if (hinted > current) session.currentPhase = hintedPhase; +} + +inline std::string detectProtocolPhaseFromWorkflow(const WorkflowState& workflow) { + auto stats = workflow.getStats(); + if (stats.total == 0) return "plan"; + if (stats.complete == stats.total) return "complete"; + if (stats.review > 0) return "review"; + + bool hasExternalOrHuman = false; + for (const auto& status : {WI_ASSIGNED, WI_IN_PROGRESS}) { + for (const auto& item : workflow.queue.getByStatus(status)) { + if (item.workerType == "slm" || item.workerType == "llm" || + item.workerType == "human") { + hasExternalOrHuman = true; + } + } + } + if (hasExternalOrHuman) return "assist"; + if (stats.ready > 0 || stats.assigned > 0 || stats.inProgress > 0) { + return "execute"; + } + return "route"; +} + +inline ProtocolAction getNextAction(const WorkflowSession& session, + const WorkflowState* workflow = nullptr) { + std::string phase = session.currentPhase; + if (workflow) { + std::string inferred = detectProtocolPhaseFromWorkflow(*workflow); + if (workflowPhaseIndex(inferred) > workflowPhaseIndex(phase)) { + phase = inferred; + } + } + + if (phase == "init") { + return {"init", "whetstone_create_skeleton", "Initialize project skeleton."}; + } + if (phase == "model") { + return {"model", "whetstone_add_skeleton_node", + "Add skeleton functions/classes until model is complete."}; + } + if (phase == "annotate") { + return {"annotate", "whetstone_infer_annotations", + "Infer and adjust routing annotations before planning."}; + } + if (phase == "plan") { + return {"plan", "whetstone_create_workflow", + "Create workflow tasks from skeleton annotations."}; + } + if (phase == "route") { + return {"route", "whetstone_orchestrate_advance", + "Route and prepare ready tasks in a single advance batch."}; + } + if (phase == "execute") { + return {"execute", "whetstone_orchestrate_run_deterministic", + "Complete deterministic/template tasks automatically."}; + } + if (phase == "assist") { + return {"assist", "whetstone_get_blockers", + "Fetch blockers, then submit external results as needed."}; + } + if (phase == "review") { + return {"review", "whetstone_get_workflow_state", + "Resolve review items and re-run deterministic orchestration."}; + } + return {"complete", "whetstone_save_workflow", + "Persist completed workflow state."}; +} + +inline json getSessionSummary(const WorkflowSession& session, + const WorkflowState* workflow = nullptr) { + ProtocolAction next = getNextAction(session, workflow); + json summary = { + {"sessionId", session.sessionId}, + {"projectName", session.projectName}, + {"currentPhase", session.currentPhase}, + {"startedAt", session.startedAt}, + {"commandCount", (int)session.commands.size()}, + {"commands", session.commands}, + {"nextAction", next.toJson()} + }; + if (workflow) { + summary["workflowPhase"] = workflowPhaseToString(workflow->getPhase()); + summary["workflowStats"] = workflow->getStats().toJson(); + summary["inferredPhase"] = detectProtocolPhaseFromWorkflow(*workflow); + } + return summary; +} diff --git a/editor/tests/step384_test.cpp b/editor/tests/step384_test.cpp new file mode 100644 index 0000000..8c90abe --- /dev/null +++ b/editor/tests/step384_test.cpp @@ -0,0 +1,177 @@ +// Step 384: Workflow session protocol (12 tests) + +#include +#include +#include +#include "WorkflowProtocol.h" + +static WorkItem makeItem(const std::string& id, + const std::string& workerType, + const std::string& status) { + WorkItem item; + item.id = id; + item.nodeId = id + "_node"; + item.nodeName = id; + item.nodeType = "Function"; + item.bufferId = "buf"; + item.workerType = workerType; + item.contextWidth = "local"; + item.priority = "medium"; + item.status = status; + item.createdAt = workItemTimestamp(); + return item; +} + +int main() { + int passed = 0; + + // Test 1: protocol phase ordering transitions are valid + { + auto phases = workflowProtocolPhases(); + for (size_t i = 1; i < phases.size(); ++i) { + assert(validateTransition(phases[i - 1], phases[i])); + } + std::cout << "Test 1 PASSED: phase ordering transition validity\n"; + passed++; + } + + // Test 2: invalid transition is rejected + { + assert(!validateTransition("init", "route")); + assert(!validateTransition("execute", "annotate")); + std::cout << "Test 2 PASSED: invalid transitions rejected\n"; + passed++; + } + + // Test 3: next action suggestions map to phases + { + WorkflowSession s; + s.currentPhase = "plan"; + auto a = getNextAction(s); + assert(a.command == "whetstone_create_workflow"); + s.currentPhase = "execute"; + auto b = getNextAction(s); + assert(b.command == "whetstone_orchestrate_run_deterministic"); + s.currentPhase = "complete"; + auto c = getNextAction(s); + assert(c.command == "whetstone_save_workflow"); + std::cout << "Test 3 PASSED: next-action hints per phase\n"; + passed++; + } + + // Test 4: session tracks command history + { + WorkflowSession s; + recordWorkflowCommand(s, "whetstone_create_skeleton"); + recordWorkflowCommand(s, "whetstone_add_skeleton_node"); + recordWorkflowCommand(s, "whetstone_create_workflow"); + assert(s.commands.size() == 3); + assert(s.commands[0] == "whetstone_create_skeleton"); + std::cout << "Test 4 PASSED: command history tracked\n"; + passed++; + } + + // Test 5: summary includes core session fields + { + WorkflowSession s; + s.sessionId = "sess-1"; + s.projectName = "proj"; + s.currentPhase = "model"; + recordWorkflowCommand(s, "whetstone_create_skeleton"); + auto summary = getSessionSummary(s); + assert(summary["sessionId"] == "sess-1"); + assert(summary["projectName"] == "proj"); + assert(summary["commandCount"].get() == 1); + assert(summary.contains("nextAction")); + std::cout << "Test 5 PASSED: summary fields correct\n"; + passed++; + } + + // Test 6: phase auto-detection from complete workflow + { + WorkflowState wf("p"); + WorkItem c = makeItem("c1", "template", WI_COMPLETE); + wf.queue.enqueue(c); + auto phase = detectProtocolPhaseFromWorkflow(wf); + assert(phase == "complete"); + std::cout << "Test 6 PASSED: complete workflow auto-detected\n"; + passed++; + } + + // Test 7: phase auto-detection for assist stage + { + WorkflowState wf("p"); + wf.queue.enqueue(makeItem("a1", "llm", WI_IN_PROGRESS)); + auto phase = detectProtocolPhaseFromWorkflow(wf); + assert(phase == "assist"); + std::cout << "Test 7 PASSED: assist phase auto-detected\n"; + passed++; + } + + // Test 8: session JSON persistence roundtrip + { + WorkflowSession s; + s.sessionId = "sess-2"; + s.projectName = "alpha"; + s.currentPhase = "route"; + s.commands = {"whetstone_create_workflow", "whetstone_orchestrate_advance"}; + auto j = s.toJson(); + auto restored = WorkflowSession::fromJson(j); + assert(restored.sessionId == s.sessionId); + assert(restored.projectName == s.projectName); + assert(restored.currentPhase == s.currentPhase); + assert(restored.commands.size() == 2); + std::cout << "Test 8 PASSED: session persistence roundtrip\n"; + passed++; + } + + // Test 9: empty project protocol remains in planning path + { + WorkflowSession s; + s.currentPhase = "plan"; + WorkflowState wf("empty"); + auto next = getNextAction(s, &wf); + assert(next.command == "whetstone_create_workflow"); + std::cout << "Test 9 PASSED: empty project plan behavior\n"; + passed++; + } + + // Test 10: unknown phases are rejected as protocol phases + { + assert(!isWorkflowProtocolPhase("unknown-phase")); + assert(!validateTransition("unknown-phase", "init")); + std::cout << "Test 10 PASSED: unknown phase guards\n"; + passed++; + } + + // Test 11: recorded commands advance session phase monotonically + { + WorkflowSession s; + s.currentPhase = "init"; + recordWorkflowCommand(s, "whetstone_create_skeleton"); + assert(s.currentPhase == "model"); + recordWorkflowCommand(s, "whetstone_orchestrate_advance"); + assert(s.currentPhase == "route"); + recordWorkflowCommand(s, "whetstone_create_workflow"); + assert(s.currentPhase == "route"); // no backward transition + std::cout << "Test 11 PASSED: monotonic phase advancement by command\n"; + passed++; + } + + // Test 12: workflow-aware next action can override stale session phase + { + WorkflowSession s; + s.currentPhase = "plan"; + WorkflowState wf("assist"); + wf.queue.enqueue(makeItem("x1", "human", WI_IN_PROGRESS)); + auto next = getNextAction(s, &wf); + assert(next.phase == "assist"); + assert(next.command == "whetstone_get_blockers"); + std::cout << "Test 12 PASSED: workflow-aware next action override\n"; + passed++; + } + + std::cout << "\nResults: " << passed << "/12\n"; + assert(passed == 12); + return 0; +} diff --git a/progress.md b/progress.md index 11215e0..edce948 100644 --- a/progress.md +++ b/progress.md @@ -3022,6 +3022,47 @@ MCP orchestration tool loop closure. - `editor/src/HeadlessAgentRPCHandler.h` (`2623` > `600`) - `editor/src/MCPServer.h` (`1652` > `600`) +### Step 384: Workflow Session Protocol +**Status:** PASS (12/12 tests) + +Implemented a formal MCP workflow-session protocol model with explicit phases, +transition validation, command-history tracking, workflow-state phase inference, +and next-action guidance for clients. + +**Files created:** +- `editor/src/WorkflowProtocol.h` — protocol model: + - `WorkflowSession` (session metadata + command history) + - `ProtocolAction` (phase + suggested command + reason) + - phase helpers: `workflowProtocolPhases`, `validateTransition`, + `protocolPhaseForCommand`, `recordWorkflowCommand` + - workflow-aware helpers: `detectProtocolPhaseFromWorkflow`, + `getNextAction`, `getSessionSummary` +- `editor/tests/step384_test.cpp` — 12 tests covering: + 1. phase-order transition validity + 2. invalid transition rejection + 3. per-phase next-action suggestions + 4. command-history tracking + 5. session-summary accuracy + 6. complete-phase auto-detection from workflow + 7. assist-phase auto-detection from workflow + 8. session JSON roundtrip persistence + 9. empty-project plan behavior + 10. unknown-phase guards + 11. monotonic phase advancement via command recording + 12. workflow-aware next-action override + +**Files modified:** +- `editor/CMakeLists.txt` — `step384_test` target + +**Verification run:** +- `step384_test` — PASS (12/12) new step coverage +- `step383_test` — PASS (8/8) regression coverage +- `step382_test` — PASS (12/12) regression coverage + +**Architecture gate check:** +- `editor/src/WorkflowProtocol.h` within header-size limit (`185` <= `600`) +- `editor/tests/step384_test.cpp` within test-file size guidance (`177` lines) + # Roadmap Planning — Sprints 12-25+ ## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)