From 85c75dc94626dfeb6c98d4018393cefc216b0850 Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 17 Feb 2026 11:30:11 -0700 Subject: [PATCH] Step 592: add documentation operator playbooks --- editor/CMakeLists.txt | 9 ++ editor/src/DocumentationOperatorPlaybooks.h | 99 +++++++++++++ editor/tests/step592_test.cpp | 154 ++++++++++++++++++++ progress.md | 34 +++++ 4 files changed, 296 insertions(+) create mode 100644 editor/src/DocumentationOperatorPlaybooks.h create mode 100644 editor/tests/step592_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 270b5c9..88b046c 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -4207,4 +4207,13 @@ target_link_libraries(step591_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step592_test tests/step592_test.cpp) +target_include_directories(step592_test PRIVATE src) +target_link_libraries(step592_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/DocumentationOperatorPlaybooks.h b/editor/src/DocumentationOperatorPlaybooks.h new file mode 100644 index 0000000..901414e --- /dev/null +++ b/editor/src/DocumentationOperatorPlaybooks.h @@ -0,0 +1,99 @@ +#pragma once +// Step 592: Documentation + Operator Playbooks + +#include +#include +#include +#include + +struct PlaybookDoc { + std::string docId; + std::string title; + std::vector sections; + std::vector operatorChecks; +}; + +class DocumentationOperatorPlaybooks { +public: + bool addDocument(const std::string& docId, + const std::string& title, + std::string* error) { + if (!error) return false; + error->clear(); + if (docId.empty()) return fail(error, "doc_id_missing"); + if (title.empty()) return fail(error, "doc_title_missing"); + if (docs_.count(docId) != 0) return fail(error, "doc_duplicate"); + docs_[docId] = PlaybookDoc{docId, title, {}, {}}; + return true; + } + + bool addSection(const std::string& docId, + const std::string& section, + std::string* error) { + if (!error) return false; + error->clear(); + auto* doc = find(docId); + if (!doc) return fail(error, "doc_missing"); + if (section.empty()) return fail(error, "section_missing"); + for (const auto& s : doc->sections) if (s == section) return fail(error, "section_duplicate"); + doc->sections.push_back(section); + return true; + } + + bool addOperatorCheck(const std::string& docId, + const std::string& check, + std::string* error) { + if (!error) return false; + error->clear(); + auto* doc = find(docId); + if (!doc) return fail(error, "doc_missing"); + if (check.empty()) return fail(error, "check_missing"); + for (const auto& c : doc->operatorChecks) if (c == check) return fail(error, "check_duplicate"); + doc->operatorChecks.push_back(check); + return true; + } + + bool isDeploymentReady(const std::string& docId) const { + const auto* doc = findConst(docId); + if (!doc) return false; + return !doc->sections.empty() && !doc->operatorChecks.empty(); + } + + int coverageScore(const std::string& docId) const { + const auto* doc = findConst(docId); + if (!doc) return 0; + int score = 0; + score += std::min(60, static_cast(doc->sections.size()) * 15); + score += std::min(40, static_cast(doc->operatorChecks.size()) * 10); + return score; + } + + std::vector allDocuments() const { + std::vector out; + for (const auto& kv : docs_) out.push_back(kv.second); + std::stable_sort(out.begin(), out.end(), [](const PlaybookDoc& a, const PlaybookDoc& b) { + return a.docId < b.docId; + }); + return out; + } + +private: + std::map docs_; + + static bool fail(std::string* error, const char* code) { + *error = code; + return false; + } + + PlaybookDoc* find(const std::string& docId) { + auto it = docs_.find(docId); + if (it == docs_.end()) return nullptr; + return &it->second; + } + + const PlaybookDoc* findConst(const std::string& docId) const { + auto it = docs_.find(docId); + if (it == docs_.end()) return nullptr; + return &it->second; + } +}; diff --git a/editor/tests/step592_test.cpp b/editor/tests/step592_test.cpp new file mode 100644 index 0000000..1355864 --- /dev/null +++ b/editor/tests/step592_test.cpp @@ -0,0 +1,154 @@ +// Step 592: Documentation + Operator Playbooks (12 tests) + +#include "DocumentationOperatorPlaybooks.h" + +#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_add_document_success() { + TEST(add_document_success); + DocumentationOperatorPlaybooks p; + std::string error; + CHECK(p.addDocument("doc-1", "Deployment Guide", &error), "add doc should succeed"); + CHECK(p.allDocuments().size() == 1, "doc count mismatch"); + PASS(); +} + +void test_add_document_rejects_missing_id() { + TEST(add_document_rejects_missing_id); + DocumentationOperatorPlaybooks p; + std::string error; + CHECK(!p.addDocument("", "Deployment Guide", &error), "add doc should fail"); + CHECK(error == "doc_id_missing", "wrong error"); + PASS(); +} + +void test_add_document_rejects_duplicate() { + TEST(add_document_rejects_duplicate); + DocumentationOperatorPlaybooks p; + std::string error; + CHECK(p.addDocument("doc-1", "Deployment Guide", &error), "first add failed"); + CHECK(!p.addDocument("doc-1", "Duplicate", &error), "duplicate should fail"); + CHECK(error == "doc_duplicate", "wrong error"); + PASS(); +} + +void test_add_section_success() { + TEST(add_section_success); + DocumentationOperatorPlaybooks p; + std::string error; + CHECK(p.addDocument("doc-1", "Deployment Guide", &error), "add doc failed"); + CHECK(p.addSection("doc-1", "Install", &error), "add section failed"); + CHECK(p.allDocuments()[0].sections.size() == 1, "section count mismatch"); + PASS(); +} + +void test_add_section_rejects_missing_doc() { + TEST(add_section_rejects_missing_doc); + DocumentationOperatorPlaybooks p; + std::string error; + CHECK(!p.addSection("missing", "Install", &error), "add section should fail"); + CHECK(error == "doc_missing", "wrong error"); + PASS(); +} + +void test_add_section_rejects_duplicate() { + TEST(add_section_rejects_duplicate); + DocumentationOperatorPlaybooks p; + std::string error; + CHECK(p.addDocument("doc-1", "Deployment Guide", &error), "add doc failed"); + CHECK(p.addSection("doc-1", "Install", &error), "add section failed"); + CHECK(!p.addSection("doc-1", "Install", &error), "duplicate section should fail"); + CHECK(error == "section_duplicate", "wrong error"); + PASS(); +} + +void test_add_operator_check_success() { + TEST(add_operator_check_success); + DocumentationOperatorPlaybooks p; + std::string error; + CHECK(p.addDocument("doc-1", "Deployment Guide", &error), "add doc failed"); + CHECK(p.addOperatorCheck("doc-1", "Verify logs clean", &error), "add check failed"); + CHECK(p.allDocuments()[0].operatorChecks.size() == 1, "check count mismatch"); + PASS(); +} + +void test_add_operator_check_rejects_duplicate() { + TEST(add_operator_check_rejects_duplicate); + DocumentationOperatorPlaybooks p; + std::string error; + CHECK(p.addDocument("doc-1", "Deployment Guide", &error), "add doc failed"); + CHECK(p.addOperatorCheck("doc-1", "Verify logs clean", &error), "add check failed"); + CHECK(!p.addOperatorCheck("doc-1", "Verify logs clean", &error), "duplicate check should fail"); + CHECK(error == "check_duplicate", "wrong error"); + PASS(); +} + +void test_is_deployment_ready_requires_sections_and_checks() { + TEST(is_deployment_ready_requires_sections_and_checks); + DocumentationOperatorPlaybooks p; + std::string error; + CHECK(p.addDocument("doc-1", "Deployment Guide", &error), "add doc failed"); + CHECK(!p.isDeploymentReady("doc-1"), "should not be ready yet"); + CHECK(p.addSection("doc-1", "Install", &error), "add section failed"); + CHECK(!p.isDeploymentReady("doc-1"), "should still not be ready"); + CHECK(p.addOperatorCheck("doc-1", "Verify logs clean", &error), "add check failed"); + CHECK(p.isDeploymentReady("doc-1"), "should be ready"); + PASS(); +} + +void test_coverage_score_combines_sections_and_checks() { + TEST(coverage_score_combines_sections_and_checks); + DocumentationOperatorPlaybooks p; + std::string error; + CHECK(p.addDocument("doc-1", "Deployment Guide", &error), "add doc failed"); + CHECK(p.addSection("doc-1", "Install", &error), "add section failed"); + CHECK(p.addSection("doc-1", "Troubleshooting", &error), "add section failed"); + CHECK(p.addOperatorCheck("doc-1", "Verify logs clean", &error), "add check failed"); + CHECK(p.coverageScore("doc-1") == 40, "coverage score mismatch"); + PASS(); +} + +void test_all_documents_sorted_by_doc_id() { + TEST(all_documents_sorted_by_doc_id); + DocumentationOperatorPlaybooks p; + std::string error; + CHECK(p.addDocument("doc-b", "B", &error), "add doc-b failed"); + CHECK(p.addDocument("doc-a", "A", &error), "add doc-a failed"); + auto docs = p.allDocuments(); + CHECK(docs[0].docId == "doc-a", "sorted order mismatch"); + PASS(); +} + +void test_unknown_doc_readiness_and_score_default() { + TEST(unknown_doc_readiness_and_score_default); + DocumentationOperatorPlaybooks p; + CHECK(!p.isDeploymentReady("missing"), "missing doc should not be ready"); + CHECK(p.coverageScore("missing") == 0, "missing doc score should be zero"); + PASS(); +} + +int main() { + std::cout << "Step 592: Documentation + Operator Playbooks\n"; + + test_add_document_success(); // 1 + test_add_document_rejects_missing_id(); // 2 + test_add_document_rejects_duplicate(); // 3 + test_add_section_success(); // 4 + test_add_section_rejects_missing_doc(); // 5 + test_add_section_rejects_duplicate(); // 6 + test_add_operator_check_success(); // 7 + test_add_operator_check_rejects_duplicate(); // 8 + test_is_deployment_ready_requires_sections_and_checks(); // 9 + test_coverage_score_combines_sections_and_checks();// 10 + test_all_documents_sorted_by_doc_id(); // 11 + test_unknown_doc_readiness_and_score_default(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index 0db1b12..1f56412 100644 --- a/progress.md +++ b/progress.md @@ -11226,3 +11226,37 @@ efficiency tracking, aggregation, and baseline-vs-candidate comparison. - `editor/src/BenchmarkComparisonHarness.h` within header-size limit (`109` <= `600`) - `editor/tests/step591_test.cpp` within test-file size guidance (`165` lines) - Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md` + +### Step 592: Documentation + Operator Playbooks +**Status:** PASS (12/12 tests) + +Implements documentation/playbook tracking for deployment and support workflows, +including section/check coverage and readiness scoring. + +**Files added:** +- `editor/src/DocumentationOperatorPlaybooks.h` - doc/playbook module: + - document registration and dedupe guards + - section and operator-check management + - deployment readiness evaluation + - coverage scoring helper + - sorted document inventory helper +- `editor/tests/step592_test.cpp` - 12 tests covering: + - document/section/check add success/failure behavior + - dedupe guard behavior + - readiness gating behavior + - coverage scoring behavior + - sorted inventory and unknown-doc fallback behavior + +**Files modified:** +- `editor/CMakeLists.txt` - `step592_test` target + +**Verification run:** +- `cmake -S editor -B editor/build-native` - PASS +- `cmake --build editor/build-native --target step592_test step591_test` - PASS +- `./editor/build-native/step592_test` - PASS (12/12) +- `./editor/build-native/step591_test` - PASS (12/12) regression coverage + +**Architecture gate check:** +- `editor/src/DocumentationOperatorPlaybooks.h` within header-size limit (`99` <= `600`) +- `editor/tests/step592_test.cpp` within test-file size guidance (`154` lines) +- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`