From 29ae2aae7af8390219ce29336a9d2f73348b81dd Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 16 Feb 2026 16:09:01 -0700 Subject: [PATCH] Step 418: add workflow MCP prompt templates --- editor/CMakeLists.txt | 9 ++ editor/src/MCPWorkflowPrompts.h | 159 ++++++++++++++++++++++++++++++++ editor/tests/step418_test.cpp | 146 +++++++++++++++++++++++++++++ progress.md | 44 +++++++++ 4 files changed, 358 insertions(+) create mode 100644 editor/src/MCPWorkflowPrompts.h create mode 100644 editor/tests/step418_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index d4e188f..876c48c 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -2641,4 +2641,13 @@ target_link_libraries(step417_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step418_test tests/step418_test.cpp) +target_include_directories(step418_test PRIVATE src) +target_link_libraries(step418_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/MCPWorkflowPrompts.h b/editor/src/MCPWorkflowPrompts.h new file mode 100644 index 0000000..7baf261 --- /dev/null +++ b/editor/src/MCPWorkflowPrompts.h @@ -0,0 +1,159 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +using json = nlohmann::json; + +struct MCPWorkflowPromptTemplate { + std::string name; + std::string description; + std::string userPrompt; + std::vector expectedTools; +}; + +class MCPWorkflowPrompts { +public: + static std::vector templates() { + return { + { + "architect_project", + "Analyze codebase and create annotated project skeleton.", + "Protocol:\n" + "1. Run whetstone_infer_annotations over key modules.\n" + "2. Create structural scaffold with whetstone_create_skeleton.\n" + "3. Build initial task plan using whetstone_create_workflow.\n" + "4. Explain architecture invariants and routing assumptions.\n" + "Goal: Analyze {{project_name}} and produce an annotated skeleton.", + {"whetstone_infer_annotations", "whetstone_create_skeleton", "whetstone_create_workflow"} + }, + { + "modernize_module", + "Modernize legacy module with workflow-guided safety.", + "Protocol:\n" + "1. Infer annotations and constraints for {{module_name}} with whetstone_infer_annotations.\n" + "2. Run automatic safe tasks via whetstone_orchestrate_run_deterministic.\n" + "3. Fetch unresolved items with whetstone_get_blockers.\n" + "4. Submit agent changes with whetstone_submit_result.\n" + "Goal: Modernize module while preserving behavior and auditability.", + {"whetstone_infer_annotations", "whetstone_orchestrate_run_deterministic", + "whetstone_get_blockers", "whetstone_submit_result"} + }, + { + "port_to_language", + "Port module between languages through workflow routing.", + "Protocol:\n" + "1. Read source semantics and infer annotations with whetstone_infer_annotations.\n" + "2. Project using whetstone_project_language from {{source_language}} to {{target_language}}.\n" + "3. Plan remaining edits with whetstone_create_workflow.\n" + "4. Execute deterministic tasks and report blockers with whetstone_get_blockers.\n" + "Goal: Port code to target language with semantic parity.", + {"whetstone_infer_annotations", "whetstone_project_language", + "whetstone_create_workflow", "whetstone_get_blockers"} + }, + { + "add_feature", + "Add feature through orchestrated skeleton-first workflow.", + "Protocol:\n" + "1. Add feature skeleton nodes with whetstone_create_skeleton.\n" + "2. Create execution plan via whetstone_create_workflow.\n" + "3. Execute deterministic tasks with whetstone_orchestrate_run_deterministic.\n" + "4. Track progress using whetstone_get_progress.\n" + "Goal: Implement {{feature_name}} with explicit review checkpoints.", + {"whetstone_create_skeleton", "whetstone_create_workflow", + "whetstone_orchestrate_run_deterministic", "whetstone_get_progress"} + }, + { + "review_pending", + "Review and approve/reject pending workflow items.", + "Protocol:\n" + "1. Load pending review items using whetstone_get_review_queue.\n" + "2. For each item, open full details with whetstone_get_review_context.\n" + "3. Approve with whetstone_approve_item or reject with whetstone_reject_item.\n" + "4. Summarize accepted/rejected decisions and rationale.\n" + "Goal: Clear review queue with traceable decisions.", + {"whetstone_get_review_queue", "whetstone_get_review_context", + "whetstone_approve_item", "whetstone_reject_item"} + } + }; + } + + static bool hasTemplate(const std::string& name) { + for (const auto& t : templates()) if (t.name == name) return true; + return false; + } + + static MCPWorkflowPromptTemplate getTemplate(const std::string& name) { + for (const auto& t : templates()) if (t.name == name) return t; + return {}; + } + + static std::string renderPrompt(const std::string& name, + const std::map& params) { + auto t = getTemplate(name); + std::string out = t.userPrompt; + for (const auto& [k, v] : params) { + replaceAll(out, "{{" + k + "}}", v); + } + return out; + } + + static bool validate(const MCPWorkflowPromptTemplate& t, std::string& error) { + if (t.name.empty()) { error = "template name is empty"; return false; } + if (t.userPrompt.find("Protocol:") == std::string::npos) { + error = "missing protocol section"; + return false; + } + if (t.expectedTools.empty()) { + error = "expected tools list is empty"; + return false; + } + for (const auto& tool : t.expectedTools) { + if (t.userPrompt.find(tool) == std::string::npos) { + error = "prompt does not reference required tool: " + tool; + return false; + } + } + return true; + } + + static json manifestJson() { + json arr = json::array(); + for (const auto& t : templates()) { + arr.push_back({ + {"name", t.name}, + {"description", t.description}, + {"expectedTools", t.expectedTools} + }); + } + return {{"templates", arr}}; + } + + static bool writeTemplateFiles(const std::string& outputDir) { + std::filesystem::create_directories(outputDir); + for (const auto& t : templates()) { + std::ofstream out(std::filesystem::path(outputDir) / (t.name + ".prompt")); + if (!out.is_open()) return false; + out << "# " << t.name << "\n\n"; + out << "description:\n" << t.description << "\n\n"; + out << "user:\n" << t.userPrompt << "\n\n"; + out << "expected_tools:\n"; + for (const auto& tool : t.expectedTools) out << "- " << tool << "\n"; + } + return true; + } + +private: + static void replaceAll(std::string& text, const std::string& from, const std::string& to) { + if (from.empty()) return; + size_t pos = 0; + while ((pos = text.find(from, pos)) != std::string::npos) { + text.replace(pos, from.size(), to); + pos += to.size(); + } + } +}; diff --git a/editor/tests/step418_test.cpp b/editor/tests/step418_test.cpp new file mode 100644 index 0000000..11230d9 --- /dev/null +++ b/editor/tests/step418_test.cpp @@ -0,0 +1,146 @@ +// Step 418: Workflow Prompt Templates Tests (12 tests) + +#include "MCPWorkflowPrompts.h" + +#include +#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 {} + +void test_templates_include_required_names() { + TEST(templates_include_required_names); + CHECK(MCPWorkflowPrompts::hasTemplate("architect_project"), "missing architect_project"); + CHECK(MCPWorkflowPrompts::hasTemplate("modernize_module"), "missing modernize_module"); + CHECK(MCPWorkflowPrompts::hasTemplate("port_to_language"), "missing port_to_language"); + CHECK(MCPWorkflowPrompts::hasTemplate("add_feature"), "missing add_feature"); + CHECK(MCPWorkflowPrompts::hasTemplate("review_pending"), "missing review_pending"); + PASS(); +} + +void test_architect_project_tools_present() { + TEST(architect_project_tools_present); + auto t = MCPWorkflowPrompts::getTemplate("architect_project"); + CHECK(t.userPrompt.find("whetstone_infer_annotations") != std::string::npos, "missing infer tool"); + CHECK(t.userPrompt.find("whetstone_create_skeleton") != std::string::npos, "missing skeleton tool"); + CHECK(t.userPrompt.find("whetstone_create_workflow") != std::string::npos, "missing workflow tool"); + PASS(); +} + +void test_modernize_module_tools_present() { + TEST(modernize_module_tools_present); + auto t = MCPWorkflowPrompts::getTemplate("modernize_module"); + CHECK(t.userPrompt.find("whetstone_orchestrate_run_deterministic") != std::string::npos, + "missing deterministic run tool"); + CHECK(t.userPrompt.find("whetstone_get_blockers") != std::string::npos, "missing blockers tool"); + CHECK(t.userPrompt.find("whetstone_submit_result") != std::string::npos, "missing submit tool"); + PASS(); +} + +void test_port_template_mentions_source_target() { + TEST(port_template_mentions_source_target); + auto rendered = MCPWorkflowPrompts::renderPrompt( + "port_to_language", + {{"source_language", "python"}, {"target_language", "rust"}}); + CHECK(rendered.find("python") != std::string::npos, "missing source language replacement"); + CHECK(rendered.find("rust") != std::string::npos, "missing target language replacement"); + PASS(); +} + +void test_add_feature_template_progress_tools_present() { + TEST(add_feature_template_progress_tools_present); + auto t = MCPWorkflowPrompts::getTemplate("add_feature"); + CHECK(t.userPrompt.find("whetstone_create_workflow") != std::string::npos, "missing workflow"); + CHECK(t.userPrompt.find("whetstone_get_progress") != std::string::npos, "missing progress"); + PASS(); +} + +void test_review_pending_template_review_tools_present() { + TEST(review_pending_template_review_tools_present); + auto t = MCPWorkflowPrompts::getTemplate("review_pending"); + CHECK(t.userPrompt.find("whetstone_get_review_queue") != std::string::npos, "missing review queue"); + CHECK(t.userPrompt.find("whetstone_get_review_context") != std::string::npos, "missing review context"); + CHECK(t.userPrompt.find("whetstone_approve_item") != std::string::npos, "missing approve"); + CHECK(t.userPrompt.find("whetstone_reject_item") != std::string::npos, "missing reject"); + PASS(); +} + +void test_render_prompt_substitutes_project_name() { + TEST(render_prompt_substitutes_project_name); + std::string rendered = MCPWorkflowPrompts::renderPrompt( + "architect_project", {{"project_name", "AlphaRepo"}}); + CHECK(rendered.find("AlphaRepo") != std::string::npos, "project_name not substituted"); + PASS(); +} + +void test_render_prompt_keeps_unprovided_placeholders() { + TEST(render_prompt_keeps_unprovided_placeholders); + std::string rendered = MCPWorkflowPrompts::renderPrompt("add_feature", {}); + CHECK(rendered.find("{{feature_name}}") != std::string::npos, + "unprovided placeholder should remain explicit"); + PASS(); +} + +void test_validate_rejects_empty_tools() { + TEST(validate_rejects_empty_tools); + MCPWorkflowPromptTemplate t; + t.name = "bad"; + t.userPrompt = "Protocol:\n1. do a thing"; + std::string err; + CHECK(!MCPWorkflowPrompts::validate(t, err), "expected validation failure"); + CHECK(!err.empty(), "expected error message"); + PASS(); +} + +void test_manifest_json_contains_all_templates() { + TEST(manifest_json_contains_all_templates); + json m = MCPWorkflowPrompts::manifestJson(); + CHECK(m.contains("templates"), "missing templates field"); + CHECK(m["templates"].is_array(), "templates should be array"); + CHECK(m["templates"].size() == 5, "expected five templates"); + PASS(); +} + +void test_write_template_files_to_directory() { + TEST(write_template_files_to_directory); + auto outDir = std::filesystem::temp_directory_path() / "whetstone_step418_prompts"; + CHECK(MCPWorkflowPrompts::writeTemplateFiles(outDir.string()), "writeTemplateFiles failed"); + CHECK(std::filesystem::exists(outDir / "architect_project.prompt"), "missing architect prompt file"); + CHECK(std::filesystem::exists(outDir / "review_pending.prompt"), "missing review prompt file"); + PASS(); +} + +void test_all_templates_validate_successfully() { + TEST(all_templates_validate_successfully); + for (const auto& t : MCPWorkflowPrompts::templates()) { + std::string err; + CHECK(MCPWorkflowPrompts::validate(t, err), "template validation failed"); + } + PASS(); +} + +int main() { + std::cout << "Step 418: Workflow Prompt Templates Tests\n"; + + test_templates_include_required_names(); // 1 + test_architect_project_tools_present(); // 2 + test_modernize_module_tools_present(); // 3 + test_port_template_mentions_source_target(); // 4 + test_add_feature_template_progress_tools_present(); // 5 + test_review_pending_template_review_tools_present(); // 6 + test_render_prompt_substitutes_project_name(); // 7 + test_render_prompt_keeps_unprovided_placeholders(); // 8 + test_validate_rejects_empty_tools(); // 9 + test_manifest_json_contains_all_templates(); // 10 + test_write_template_files_to_directory(); // 11 + test_all_templates_validate_successfully(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) + << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index e256757..b470b15 100644 --- a/progress.md +++ b/progress.md @@ -4278,6 +4278,50 @@ inspection without adding a custom plugin binary. - `editor/src/MCPServer.h` (`1679` > `600`) - `editor/src/HeadlessAgentRPCHandler.h` (`2629` > `600`) +### Step 418: Workflow Prompt Templates +**Status:** PASS (12/12 tests) + +Added workflow-specific MCP prompt templates aligned to Sprint 18a's named +flows (`architect_project`, `modernize_module`, `port_to_language`, +`add_feature`, `review_pending`) with explicit tool references and rendering. + +**Files created:** +- `editor/src/MCPWorkflowPrompts.h` — workflow prompt-template support: + - canonical template catalog (5 templates) + - parameterized prompt rendering (`{{...}}` substitution) + - template validation (protocol section + required tool mentions) + - JSON manifest export for client/tooling discovery + - prompt-file writer for emitting portable `.prompt` files +- `editor/tests/step418_test.cpp` — 12 tests covering: + 1. required template-name presence + 2. `architect_project` tool references + 3. `modernize_module` tool references + 4. `port_to_language` source/target substitution + 5. `add_feature` progress workflow references + 6. `review_pending` review queue/context/approve/reject references + 7. parameter substitution behavior + 8. unresolved placeholder behavior when param omitted + 9. validation failure on empty tool list + 10. manifest structure and template count + 11. template-file write-out + 12. validation success for all shipped templates + +**Files modified:** +- `editor/CMakeLists.txt` — `step418_test` target + +**Verification run:** +- `step418_test` — PASS (12/12) new step coverage +- `step417_test` — PASS (12/12) regression coverage +- `step416_test` — PASS (8/8) regression coverage + +**Architecture gate check:** +- `editor/src/MCPWorkflowPrompts.h` within header-size limit (`159` <= `600`) +- `editor/tests/step418_test.cpp` within test-file size guidance (`146` lines) +- Legacy oversized headers persist: + - `editor/src/ast/Serialization.h` (`1427` > `600`) + - `editor/src/MCPServer.h` (`1679` > `600`) + - `editor/src/HeadlessAgentRPCHandler.h` (`2629` > `600`) + # Roadmap Planning — Sprints 12-25+ ## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)