Step 419: add MCP agent workflow loop
This commit is contained in:
@@ -2650,4 +2650,13 @@ target_link_libraries(step418_test PRIVATE
|
||||
tree_sitter_javascript tree_sitter_typescript
|
||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||
|
||||
add_executable(step419_test tests/step419_test.cpp)
|
||||
target_include_directories(step419_test PRIVATE src)
|
||||
target_link_libraries(step419_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)
|
||||
|
||||
137
editor/src/MCPAgentWorkflowLoop.h
Normal file
137
editor/src/MCPAgentWorkflowLoop.h
Normal file
@@ -0,0 +1,137 @@
|
||||
#pragma once
|
||||
|
||||
#include "MCPServer.h"
|
||||
|
||||
#include <functional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class MCPAgentWorkflowLoop {
|
||||
public:
|
||||
struct Config {
|
||||
std::string projectName = "workflow-project";
|
||||
std::string skeletonName = "skeleton";
|
||||
std::string language = "cpp";
|
||||
};
|
||||
|
||||
struct Blocker {
|
||||
std::string itemId;
|
||||
std::string kind;
|
||||
std::string prompt;
|
||||
};
|
||||
|
||||
struct AgentSubmission {
|
||||
std::string generatedCode;
|
||||
double confidence = 0.8;
|
||||
std::string reasoning;
|
||||
};
|
||||
|
||||
struct Report {
|
||||
bool success = false;
|
||||
int blockersProcessed = 0;
|
||||
std::vector<std::string> toolCalls;
|
||||
json finalProgress = json::object();
|
||||
};
|
||||
|
||||
using AgentHandler = std::function<AgentSubmission(const Blocker&)>;
|
||||
|
||||
explicit MCPAgentWorkflowLoop(MCPServer& server) : server_(server) {}
|
||||
|
||||
Report run(const Config& config, AgentHandler handler) {
|
||||
Report r;
|
||||
ensureRequiredToolsPresent();
|
||||
|
||||
callTool("whetstone_infer_annotations", json::object(), r);
|
||||
callTool("whetstone_create_skeleton",
|
||||
{{"name", config.skeletonName}, {"language", config.language}}, r);
|
||||
callTool("whetstone_create_workflow", {{"projectName", config.projectName}}, r);
|
||||
callTool("whetstone_orchestrate_run_deterministic", json::object(), r);
|
||||
|
||||
json blockersResp = callTool("whetstone_get_blockers", json::object(), r);
|
||||
auto blockers = parseBlockers(blockersResp);
|
||||
for (const auto& b : blockers) {
|
||||
AgentSubmission sub = handler ? handler(b) : AgentSubmission{};
|
||||
json result = {
|
||||
{"generatedCode", sub.generatedCode},
|
||||
{"confidence", sub.confidence},
|
||||
{"reasoning", sub.reasoning}
|
||||
};
|
||||
callTool("whetstone_submit_result",
|
||||
{{"itemId", b.itemId}, {"result", result}}, r);
|
||||
r.blockersProcessed++;
|
||||
}
|
||||
|
||||
r.finalProgress = callTool("whetstone_get_progress", json::object(), r);
|
||||
r.success = !r.finalProgress.contains("error");
|
||||
return r;
|
||||
}
|
||||
|
||||
static std::vector<Blocker> parseBlockers(const json& response) {
|
||||
std::vector<Blocker> out;
|
||||
if (!response.is_object()) return out;
|
||||
if (!response.contains("blockers") || !response["blockers"].is_array()) return out;
|
||||
|
||||
for (const auto& b : response["blockers"]) {
|
||||
Blocker item;
|
||||
item.itemId = b.value("itemId", b.value("id", ""));
|
||||
item.kind = b.value("kind", b.value("type", ""));
|
||||
item.prompt = b.value("prompt", b.value("reason", ""));
|
||||
if (!item.itemId.empty()) out.push_back(item);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private:
|
||||
MCPServer& server_;
|
||||
|
||||
void ensureRequiredToolsPresent() const {
|
||||
const std::vector<std::string> required = {
|
||||
"whetstone_infer_annotations",
|
||||
"whetstone_create_skeleton",
|
||||
"whetstone_create_workflow",
|
||||
"whetstone_orchestrate_run_deterministic",
|
||||
"whetstone_get_blockers",
|
||||
"whetstone_submit_result",
|
||||
"whetstone_get_progress"
|
||||
};
|
||||
for (const auto& name : required) {
|
||||
if (!hasTool(name)) {
|
||||
throw std::runtime_error("Missing required MCP tool: " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool hasTool(const std::string& name) const {
|
||||
for (const auto& t : server_.getTools()) if (t.name == name) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
json callTool(const std::string& name, const json& args, Report& r) {
|
||||
r.toolCalls.push_back(name);
|
||||
json req = {
|
||||
{"jsonrpc", "2.0"},
|
||||
{"id", 1},
|
||||
{"method", "tools/call"},
|
||||
{"params", {
|
||||
{"name", name},
|
||||
{"arguments", args}
|
||||
}}
|
||||
};
|
||||
|
||||
json resp = server_.handleRequest(req);
|
||||
if (!resp.contains("result")) return json{{"error", "missing result"}};
|
||||
auto result = resp["result"];
|
||||
if (result.value("isError", false)) return json{{"error", "tool call failed"}};
|
||||
if (!result.contains("content") || !result["content"].is_array() || result["content"].empty()) {
|
||||
return json{{"error", "missing content"}};
|
||||
}
|
||||
|
||||
std::string text = result["content"][0].value("text", "");
|
||||
try {
|
||||
return json::parse(text);
|
||||
} catch (...) {
|
||||
return json{{"raw", text}};
|
||||
}
|
||||
}
|
||||
};
|
||||
299
editor/tests/step419_test.cpp
Normal file
299
editor/tests/step419_test.cpp
Normal file
@@ -0,0 +1,299 @@
|
||||
// Step 419: Agent Workflow Loop Tests (12 tests)
|
||||
|
||||
#include "MCPAgentWorkflowLoop.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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 {}
|
||||
|
||||
struct RpcRecorder {
|
||||
std::vector<std::string> methods;
|
||||
std::vector<json> params;
|
||||
json operator()(const json& request) {
|
||||
std::string method = request.value("method", "");
|
||||
json p = request.value("params", json::object());
|
||||
methods.push_back(method);
|
||||
params.push_back(p);
|
||||
|
||||
if (method == "getBlockers") {
|
||||
return {{"jsonrpc", "2.0"}, {"id", 1}, {"result", {{"blockers", json::array()}}}};
|
||||
}
|
||||
if (method == "getProgress") {
|
||||
return {{"jsonrpc", "2.0"}, {"id", 1},
|
||||
{"result", {{"percentComplete", 100}, {"status", "complete"}}}};
|
||||
}
|
||||
return {{"jsonrpc", "2.0"}, {"id", 1}, {"result", json::object()}};
|
||||
}
|
||||
};
|
||||
|
||||
void test_loop_calls_expected_tools_without_blockers() {
|
||||
TEST(loop_calls_expected_tools_without_blockers);
|
||||
MCPServer server;
|
||||
RpcRecorder rec;
|
||||
server.setRpcCallback([&](const json& req){ return rec(req); });
|
||||
MCPAgentWorkflowLoop loop(server);
|
||||
|
||||
auto report = loop.run({}, [&](const MCPAgentWorkflowLoop::Blocker&) {
|
||||
return MCPAgentWorkflowLoop::AgentSubmission{};
|
||||
});
|
||||
CHECK(report.success, "expected success");
|
||||
CHECK(report.blockersProcessed == 0, "expected zero blockers processed");
|
||||
CHECK(rec.methods.size() == 6, "expected six RPC calls without submit");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_loop_processes_single_blocker_and_submits_result() {
|
||||
TEST(loop_processes_single_blocker_and_submits_result);
|
||||
MCPServer server;
|
||||
std::vector<std::string> methods;
|
||||
server.setRpcCallback([&](const json& req) -> json {
|
||||
std::string m = req.value("method", "");
|
||||
methods.push_back(m);
|
||||
if (m == "getBlockers") {
|
||||
return {{"jsonrpc", "2.0"}, {"id", 1},
|
||||
{"result", {{"blockers", json::array({
|
||||
{{"itemId", "w1"}, {"kind", "needs-external-model"}, {"prompt", "Implement W1"}}
|
||||
})}}}};
|
||||
}
|
||||
if (m == "getProgress") {
|
||||
return {{"jsonrpc", "2.0"}, {"id", 1},
|
||||
{"result", {{"percentComplete", 75}, {"status", "in-progress"}}}};
|
||||
}
|
||||
return {{"jsonrpc", "2.0"}, {"id", 1}, {"result", json::object()}};
|
||||
});
|
||||
|
||||
MCPAgentWorkflowLoop loop(server);
|
||||
auto report = loop.run({}, [&](const MCPAgentWorkflowLoop::Blocker& b) {
|
||||
MCPAgentWorkflowLoop::AgentSubmission s;
|
||||
s.generatedCode = "// completed " + b.itemId;
|
||||
s.reasoning = "done";
|
||||
s.confidence = 0.92;
|
||||
return s;
|
||||
});
|
||||
|
||||
CHECK(report.blockersProcessed == 1, "expected one processed blocker");
|
||||
bool submitted = false;
|
||||
for (const auto& m : methods) if (m == "submitExternalResult") submitted = true;
|
||||
CHECK(submitted, "expected submitExternalResult call");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_loop_processes_multiple_blockers() {
|
||||
TEST(loop_processes_multiple_blockers);
|
||||
MCPServer server;
|
||||
int submits = 0;
|
||||
server.setRpcCallback([&](const json& req) -> json {
|
||||
std::string m = req.value("method", "");
|
||||
if (m == "getBlockers") {
|
||||
return {{"jsonrpc", "2.0"}, {"id", 1},
|
||||
{"result", {{"blockers", json::array({
|
||||
{{"itemId", "w1"}, {"prompt", "p1"}},
|
||||
{{"itemId", "w2"}, {"prompt", "p2"}}
|
||||
})}}}};
|
||||
}
|
||||
if (m == "submitExternalResult") submits++;
|
||||
if (m == "getProgress") {
|
||||
return {{"jsonrpc", "2.0"}, {"id", 1}, {"result", {{"percentComplete", 60}}}};
|
||||
}
|
||||
return {{"jsonrpc", "2.0"}, {"id", 1}, {"result", json::object()}};
|
||||
});
|
||||
|
||||
MCPAgentWorkflowLoop loop(server);
|
||||
auto report = loop.run({}, [&](const MCPAgentWorkflowLoop::Blocker&) {
|
||||
return MCPAgentWorkflowLoop::AgentSubmission{"code", 0.8, "ok"};
|
||||
});
|
||||
|
||||
CHECK(report.blockersProcessed == 2, "expected two blockers processed");
|
||||
CHECK(submits == 2, "expected two submit calls");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_loop_propagates_config_values_to_calls() {
|
||||
TEST(loop_propagates_config_values_to_calls);
|
||||
MCPServer server;
|
||||
std::vector<json> skeletonParams;
|
||||
std::vector<json> workflowParams;
|
||||
server.setRpcCallback([&](const json& req) -> json {
|
||||
std::string m = req.value("method", "");
|
||||
if (m == "createSkeleton") skeletonParams.push_back(req.value("params", json::object()));
|
||||
if (m == "createWorkflow") workflowParams.push_back(req.value("params", json::object()));
|
||||
if (m == "getBlockers") return {{"jsonrpc","2.0"},{"id",1},{"result",{{"blockers",json::array()}}}};
|
||||
if (m == "getProgress") return {{"jsonrpc","2.0"},{"id",1},{"result",{{"percentComplete",100}}}};
|
||||
return {{"jsonrpc","2.0"},{"id",1},{"result",json::object()}};
|
||||
});
|
||||
MCPAgentWorkflowLoop loop(server);
|
||||
MCPAgentWorkflowLoop::Config cfg;
|
||||
cfg.projectName = "Alpha";
|
||||
cfg.skeletonName = "AlphaSkeleton";
|
||||
cfg.language = "python";
|
||||
loop.run(cfg, nullptr);
|
||||
|
||||
CHECK(!skeletonParams.empty(), "missing createSkeleton params");
|
||||
CHECK(!workflowParams.empty(), "missing createWorkflow params");
|
||||
CHECK(skeletonParams[0].value("name", "") == "AlphaSkeleton", "wrong skeleton name");
|
||||
CHECK(skeletonParams[0].value("language", "") == "python", "wrong language");
|
||||
CHECK(workflowParams[0].value("projectName", "") == "Alpha", "wrong project name");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_parse_blockers_accepts_itemId_format() {
|
||||
TEST(parse_blockers_accepts_itemId_format);
|
||||
json r = {{"blockers", json::array({{{"itemId", "w1"}, {"kind", "k"}, {"prompt", "p"}}})}};
|
||||
auto v = MCPAgentWorkflowLoop::parseBlockers(r);
|
||||
CHECK(v.size() == 1, "expected one blocker");
|
||||
CHECK(v[0].itemId == "w1", "wrong blocker id");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_parse_blockers_accepts_id_alias() {
|
||||
TEST(parse_blockers_accepts_id_alias);
|
||||
json r = {{"blockers", json::array({{{"id", "w9"}, {"type", "human"}, {"reason", "needs review"}}})}};
|
||||
auto v = MCPAgentWorkflowLoop::parseBlockers(r);
|
||||
CHECK(v.size() == 1, "expected one blocker");
|
||||
CHECK(v[0].itemId == "w9", "wrong alias id");
|
||||
CHECK(v[0].kind == "human", "wrong alias kind");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_parse_blockers_ignores_invalid_entries() {
|
||||
TEST(parse_blockers_ignores_invalid_entries);
|
||||
json r = {{"blockers", json::array({{{"prompt", "missing id"}}, {{"itemId", "ok"}}})}};
|
||||
auto v = MCPAgentWorkflowLoop::parseBlockers(r);
|
||||
CHECK(v.size() == 1, "expected one valid blocker");
|
||||
CHECK(v[0].itemId == "ok", "expected valid item");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_report_tracks_tool_call_order() {
|
||||
TEST(report_tracks_tool_call_order);
|
||||
MCPServer server;
|
||||
server.setRpcCallback([&](const json& req) -> json {
|
||||
std::string m = req.value("method", "");
|
||||
if (m == "getBlockers") return {{"jsonrpc","2.0"},{"id",1},{"result",{{"blockers",json::array()}}}};
|
||||
if (m == "getProgress") return {{"jsonrpc","2.0"},{"id",1},{"result",{{"percentComplete",100}}}};
|
||||
return {{"jsonrpc","2.0"},{"id",1},{"result",json::object()}};
|
||||
});
|
||||
MCPAgentWorkflowLoop loop(server);
|
||||
auto report = loop.run({}, nullptr);
|
||||
CHECK(report.toolCalls.size() == 6, "expected six tool calls");
|
||||
CHECK(report.toolCalls.front() == "whetstone_infer_annotations", "first call mismatch");
|
||||
CHECK(report.toolCalls.back() == "whetstone_get_progress", "last call mismatch");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_agent_handler_receives_blocker_payload() {
|
||||
TEST(agent_handler_receives_blocker_payload);
|
||||
MCPServer server;
|
||||
server.setRpcCallback([&](const json& req) -> json {
|
||||
std::string m = req.value("method", "");
|
||||
if (m == "getBlockers") {
|
||||
return {{"jsonrpc","2.0"},{"id",1},
|
||||
{"result",{{"blockers",json::array({
|
||||
{{"itemId","w42"},{"kind","needs-external-model"},{"prompt","Write adapter"}}
|
||||
})}}}};
|
||||
}
|
||||
if (m == "getProgress") return {{"jsonrpc","2.0"},{"id",1},{"result",{{"percentComplete",50}}}};
|
||||
return {{"jsonrpc","2.0"},{"id",1},{"result",json::object()}};
|
||||
});
|
||||
MCPAgentWorkflowLoop loop(server);
|
||||
std::string seenId, seenPrompt;
|
||||
loop.run({}, [&](const MCPAgentWorkflowLoop::Blocker& b) {
|
||||
seenId = b.itemId;
|
||||
seenPrompt = b.prompt;
|
||||
return MCPAgentWorkflowLoop::AgentSubmission{"code", 0.7, "reason"};
|
||||
});
|
||||
CHECK(seenId == "w42", "blocker id not delivered");
|
||||
CHECK(seenPrompt == "Write adapter", "blocker prompt not delivered");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_submit_result_contains_generated_code_and_confidence() {
|
||||
TEST(submit_result_contains_generated_code_and_confidence);
|
||||
MCPServer server;
|
||||
json submitParams = json::object();
|
||||
server.setRpcCallback([&](const json& req) -> json {
|
||||
std::string m = req.value("method", "");
|
||||
if (m == "getBlockers") {
|
||||
return {{"jsonrpc","2.0"},{"id",1},
|
||||
{"result",{{"blockers",json::array({{{"itemId","w1"},{"prompt","do"}}})}}}};
|
||||
}
|
||||
if (m == "submitExternalResult") submitParams = req.value("params", json::object());
|
||||
if (m == "getProgress") return {{"jsonrpc","2.0"},{"id",1},{"result",{{"percentComplete",80}}}};
|
||||
return {{"jsonrpc","2.0"},{"id",1},{"result",json::object()}};
|
||||
});
|
||||
MCPAgentWorkflowLoop loop(server);
|
||||
loop.run({}, [&](const MCPAgentWorkflowLoop::Blocker&) {
|
||||
return MCPAgentWorkflowLoop::AgentSubmission{"int x = 1;", 0.91, "ok"};
|
||||
});
|
||||
CHECK(submitParams.value("itemId", "") == "w1", "missing item id");
|
||||
CHECK(submitParams["result"].value("generatedCode", "") == "int x = 1;", "missing code");
|
||||
CHECK(submitParams["result"].value("confidence", 0.0) > 0.9, "missing confidence");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_final_progress_is_returned_in_report() {
|
||||
TEST(final_progress_is_returned_in_report);
|
||||
MCPServer server;
|
||||
server.setRpcCallback([&](const json& req) -> json {
|
||||
std::string m = req.value("method", "");
|
||||
if (m == "getBlockers") return {{"jsonrpc","2.0"},{"id",1},{"result",{{"blockers",json::array()}}}};
|
||||
if (m == "getProgress") return {{"jsonrpc","2.0"},{"id",1},
|
||||
{"result",{{"percentComplete",100},{"etaMinutes",0}}}};
|
||||
return {{"jsonrpc","2.0"},{"id",1},{"result",json::object()}};
|
||||
});
|
||||
MCPAgentWorkflowLoop loop(server);
|
||||
auto report = loop.run({}, nullptr);
|
||||
CHECK(report.finalProgress.value("percentComplete", 0) == 100, "missing progress percent");
|
||||
CHECK(report.success, "report should mark success");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_tool_set_contains_required_workflow_tools() {
|
||||
TEST(tool_set_contains_required_workflow_tools);
|
||||
MCPServer server;
|
||||
auto tools = server.getTools();
|
||||
std::map<std::string, bool> needed = {
|
||||
{"whetstone_infer_annotations", false},
|
||||
{"whetstone_create_skeleton", false},
|
||||
{"whetstone_create_workflow", false},
|
||||
{"whetstone_orchestrate_run_deterministic", false},
|
||||
{"whetstone_get_blockers", false},
|
||||
{"whetstone_submit_result", false},
|
||||
{"whetstone_get_progress", false}
|
||||
};
|
||||
for (const auto& t : tools) {
|
||||
if (needed.count(t.name)) needed[t.name] = true;
|
||||
}
|
||||
for (const auto& [name, ok] : needed) {
|
||||
CHECK(ok, "missing required tool: " + name);
|
||||
}
|
||||
PASS();
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "Step 419: Agent Workflow Loop Tests\n";
|
||||
|
||||
test_loop_calls_expected_tools_without_blockers(); // 1
|
||||
test_loop_processes_single_blocker_and_submits_result(); // 2
|
||||
test_loop_processes_multiple_blockers(); // 3
|
||||
test_loop_propagates_config_values_to_calls(); // 4
|
||||
test_parse_blockers_accepts_itemId_format(); // 5
|
||||
test_parse_blockers_accepts_id_alias(); // 6
|
||||
test_parse_blockers_ignores_invalid_entries(); // 7
|
||||
test_report_tracks_tool_call_order(); // 8
|
||||
test_agent_handler_receives_blocker_payload(); // 9
|
||||
test_submit_result_contains_generated_code_and_confidence(); // 10
|
||||
test_final_progress_is_returned_in_report(); // 11
|
||||
test_tool_set_contains_required_workflow_tools(); // 12
|
||||
|
||||
std::cout << "\nResults: " << passed << "/" << (passed + failed)
|
||||
<< " passed\n";
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
51
progress.md
51
progress.md
@@ -4322,6 +4322,57 @@ flows (`architect_project`, `modernize_module`, `port_to_language`,
|
||||
- `editor/src/MCPServer.h` (`1679` > `600`)
|
||||
- `editor/src/HeadlessAgentRPCHandler.h` (`2629` > `600`)
|
||||
|
||||
### Step 419: Agent Workflow Loop
|
||||
**Status:** PASS (12/12 tests)
|
||||
|
||||
Added a concrete MCP-driven agent loop wrapper that executes the Phase 18a
|
||||
sequence across real MCP tool calls and handles external-result submission for
|
||||
blocker items.
|
||||
|
||||
**Files created:**
|
||||
- `editor/src/MCPAgentWorkflowLoop.h` — workflow-loop support:
|
||||
- required-tool gate for:
|
||||
- `whetstone_infer_annotations`
|
||||
- `whetstone_create_skeleton`
|
||||
- `whetstone_create_workflow`
|
||||
- `whetstone_orchestrate_run_deterministic`
|
||||
- `whetstone_get_blockers`
|
||||
- `whetstone_submit_result`
|
||||
- `whetstone_get_progress`
|
||||
- MCP `tools/call` execution wrapper with JSON result parsing
|
||||
- blocker parsing support (`itemId` and `id` aliases)
|
||||
- orchestrated run sequence with configurable project/skeleton metadata
|
||||
- loop report with call trace, blocker count, and final progress snapshot
|
||||
- `editor/tests/step419_test.cpp` — 12 tests covering:
|
||||
1. baseline loop order without blockers
|
||||
2. single-blocker submit flow
|
||||
3. multi-blocker submit flow
|
||||
4. config propagation to skeleton/workflow creation calls
|
||||
5. blocker parsing (`itemId`)
|
||||
6. blocker parsing (`id` alias)
|
||||
7. invalid blocker filtering
|
||||
8. report call-order tracking
|
||||
9. agent handler payload delivery
|
||||
10. submit payload shape (code + confidence)
|
||||
11. final progress propagation into report
|
||||
12. required MCP tool presence check
|
||||
|
||||
**Files modified:**
|
||||
- `editor/CMakeLists.txt` — `step419_test` target
|
||||
|
||||
**Verification run:**
|
||||
- `step419_test` — PASS (12/12) new step coverage
|
||||
- `step418_test` — PASS (12/12) regression coverage
|
||||
- `step417_test` — PASS (12/12) regression coverage
|
||||
|
||||
**Architecture gate check:**
|
||||
- `editor/src/MCPAgentWorkflowLoop.h` within header-size limit (`137` <= `600`)
|
||||
- `editor/tests/step419_test.cpp` within test-file size guidance (`299` 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)
|
||||
|
||||
Reference in New Issue
Block a user