// Step 682: whetstone_validate_taskitem MCP tool (9 tests) #include "MCPServer.h" #include #include #include #include #include using json = nlohmann::json; namespace fs = std::filesystem; 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; } static json callTool(MCPServer& mcp, const std::string& name, const json& args) { json req = { {"jsonrpc", "2.0"}, {"id", 1}, {"method", "tools/call"}, {"params", {{"name", name}, {"arguments", args}}} }; json resp = mcp.handleRequest(req); std::string text = resp["result"]["content"][0].value("text", "{}"); return json::parse(text); } static json loadToolsJson() { std::ifstream in("tools/claude/tools.json"); if (!in.good()) in.open("../tools/claude/tools.json"); if (!in.good()) in.open("/home/bill/Documents/CLionProjects/whetstone_DSL/tools/claude/tools.json"); if (!in.good()) return json::object(); json d; in >> d; return d; } static std::string makeWorkspace() { fs::path root = fs::temp_directory_path() / "whetstone_step682_workspace"; std::error_code ec; fs::remove_all(root, ec); fs::create_directories(root / "editor/src", ec); std::ofstream(root / "editor/src/file.h") << "int x();\n"; return root.string(); } static json validTaskitem() { return { {"task_id", "T1"}, {"title", "Do thing"}, {"prerequisite_ops", json::array({"read editor/src/file.h"})}, {"reasons", json::array({"r1", "r2", "r3"})}, {"confidence", 90}, {"dependency_task_ids", json::array()} }; } void t1() { TEST(tool_registered); MCPServer mcp; json req = {{"jsonrpc", "2.0"}, {"id", 2}, {"method", "tools/list"}}; auto resp = mcp.handleRequest(req); bool found = false; for (const auto& t : resp["result"]["tools"]) { if (t.value("name", "") == "whetstone_validate_taskitem") { found = true; break; } } CHECK(found, "tool missing"); PASS(); } void t2() { TEST(empty_taskitems_returns_zero_report); MCPServer mcp; auto out = callTool(mcp, "whetstone_validate_taskitem", {{"taskitems", json::array()}}); CHECK(out.value("success", false), "expected success"); CHECK(out["report"].value("total_taskitems", -1) == 0, "expected total 0"); PASS(); } void t3() { TEST(single_valid_taskitem_returns_score); MCPServer mcp; auto workspace = makeWorkspace(); auto out = callTool(mcp, "whetstone_validate_taskitem", {{"taskitems", json::array({validTaskitem()})}, {"workspace", workspace}}); CHECK(out.value("success", false), "expected success"); CHECK(out["report"]["results"].size() == 1, "expected one result"); PASS(); } void t4() { TEST(missing_task_id_returns_error); MCPServer mcp; auto out = callTool(mcp, "whetstone_validate_taskitem", {{"taskitems", json::array({json::object()})}}); CHECK(!out.value("success", true), "expected failure"); PASS(); } void t5() { TEST(prerequisite_ops_resolved_against_workspace); MCPServer mcp; auto workspace = makeWorkspace(); auto out = callTool(mcp, "whetstone_validate_taskitem", {{"taskitems", json::array({validTaskitem()})}, {"workspace", workspace}}); CHECK(out.value("success", false), "expected success"); int score = out["report"]["results"][0].value("score", 0); CHECK(score >= 80, "expected resolved-op high score"); PASS(); } void t6() { TEST(results_length_matches_input_length); MCPServer mcp; auto workspace = makeWorkspace(); auto out = callTool(mcp, "whetstone_validate_taskitem", {{"taskitems", json::array({validTaskitem(), validTaskitem()})}, {"workspace", workspace}}); CHECK(out["report"]["results"].size() == 2, "expected two results"); PASS(); } void t7() { TEST(category_counts_sum_to_total); MCPServer mcp; auto workspace = makeWorkspace(); auto out = callTool(mcp, "whetstone_validate_taskitem", {{"taskitems", json::array({validTaskitem()})}, {"workspace", workspace}}); const auto& r = out["report"]; int total = r.value("total_taskitems", 0); int sum = r.value("self_contained_count", 0) + r.value("warning_count", 0) + r.value("failing_count", 0); CHECK(total == sum, "count sum mismatch"); PASS(); } void t8() { TEST(tool_count_updated_to_88); auto d = loadToolsJson(); CHECK(d.contains("tools"), "tools missing"); CHECK(d["tools"].size() >= 88, "expected at least 88 tools"); PASS(); } void t9() { TEST(cross_project_target_mismatch_classified); MCPServer mcp; json item = validTaskitem(); item["execution_contract"] = { {"targetFiles", json::array({ "editor/src/mcp/RegisterArchitectIntakeTools.h", "tools/mcp/run_sprint_taskitem_pipeline.sh" })} }; auto out = callTool(mcp, "whetstone_validate_taskitem", { {"taskitems", json::array({item})}, {"expected_project_roots", json::array({"cad_orchestrator", "hivemind", "gleaner", "constcad"})} }); CHECK(out.value("success", false), "expected success"); CHECK(out["report"]["results"].size() == 1, "expected one result"); CHECK(out["report"]["results"][0].value("gap_class", "") == "cross_project_targeting_gap", "expected cross_project_targeting_gap"); PASS(); } void t10() { TEST(strict_contract_flags_missing_verification_metadata); MCPServer mcp; auto workspace = makeWorkspace(); json item = validTaskitem(); item["execution_contract"] = { {"stepIds", json::array({"1939"})}, {"targetFiles", json::array({"python/hivemind_installer/models.py"})}, {"requiredTools", json::array({"whetstone_generate_taskitems"})}, {"acceptanceCommands", json::array({"pytest"})}, {"executionSpecificityScore", 75} }; auto out = callTool(mcp, "whetstone_validate_taskitem", { {"taskitems", json::array({item})}, {"workspace", workspace}, {"strict_execution_contract", true} }); CHECK(out.value("success", false), "expected success"); CHECK(out["report"]["results"].size() == 1, "expected one result"); const auto& result = out["report"]["results"][0]; CHECK(result.value("gap_class", "") == "under_constrained_taskitem", "expected under_constrained_taskitem"); bool sawVerificationIssue = false; for (const auto& issue : result["issues"]) { if (!issue.is_string()) continue; const std::string text = issue.get(); if (text.find("verificationType") != std::string::npos || text.find("testFiles") != std::string::npos) { sawVerificationIssue = true; } } CHECK(sawVerificationIssue, "expected verification metadata issue"); PASS(); } void t11() { TEST(malformed_paths_and_wrong_language_commands_are_under_constrained); MCPServer mcp; json item = validTaskitem(); item["execution_contract"] = { {"targetFiles", json::array({ "crates/whimpwm/src/render.rs", "crates/whimpwm/tests/`.", "crates/whimpwm`." })}, {"acceptanceCommands", json::array({ "cargo test", "go test ./..." })}, {"verificationType", "unit"}, {"testFiles", json::array({"crates/whimpwm/tests/main_test.rs"})}, {"executionSpecificityScore", 90} }; auto out = callTool(mcp, "whetstone_validate_taskitem", { {"taskitems", json::array({item})}, {"strict_execution_contract", true} }); CHECK(out.value("success", false), "expected success"); CHECK(out["report"]["results"].size() == 1, "expected one result"); const auto& result = out["report"]["results"][0]; CHECK(result.value("gap_class", "") == "under_constrained_taskitem", "expected under_constrained_taskitem"); bool sawMalformed = false; bool sawIncompatible = false; for (const auto& issue : result["issues"]) { if (!issue.is_string()) continue; const std::string text = issue.get(); if (text.find("malformed_target_path") != std::string::npos) sawMalformed = true; if (text.find("incompatible_acceptance_commands") != std::string::npos) sawIncompatible = true; } CHECK(sawMalformed, "expected malformed target path issue"); CHECK(sawIncompatible, "expected incompatible acceptance commands issue"); PASS(); } int main() { std::cout << "Step 682: whetstone_validate_taskitem MCP tool\n"; t1(); t2(); t3(); t4(); t5(); t6(); t7(); t8(); t9(); t10(); t11(); std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n"; return failed == 0 ? 0 : 1; }