From 922e7c4ba96cdbeff2de3492a494ba1ccde84b8b Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 16 Feb 2026 21:14:27 -0700 Subject: [PATCH] Complete Step 484: prior step context injector --- editor/CMakeLists.txt | 9 ++ editor/src/PriorStepContextInjector.h | 125 ++++++++++++++++++++++++++ editor/tests/step484_test.cpp | 120 +++++++++++++++++++++++++ progress.md | 37 ++++++++ 4 files changed, 291 insertions(+) create mode 100644 editor/src/PriorStepContextInjector.h create mode 100644 editor/tests/step484_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 3db55e6..aa2505d 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -3235,4 +3235,13 @@ target_link_libraries(step483_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step484_test tests/step484_test.cpp) +target_include_directories(step484_test PRIVATE src) +target_link_libraries(step484_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/PriorStepContextInjector.h b/editor/src/PriorStepContextInjector.h new file mode 100644 index 0000000..1fb0875 --- /dev/null +++ b/editor/src/PriorStepContextInjector.h @@ -0,0 +1,125 @@ +#pragma once + +// Step 484: Prior-Step Context Injector +// Builds a condensed API/context brief from prior step headers. + +#include +#include +#include +#include +#include +#include +#include +#include + +struct ApiMethodSummary { + std::string signature; +}; + +struct ApiTypeSummary { + std::string kind; // class | struct + std::string name; + std::vector methods; +}; + +struct StepContext { + std::vector priorApis; + std::map> includeDependencies; + std::vector namingPatterns; + std::vector warnings; + + std::string condensedBrief() const { + std::ostringstream out; + out << "types:"; + for (const auto& t : priorApis) out << " " << t.name; + out << " | patterns:"; + for (const auto& p : namingPatterns) out << " " << p; + return out.str(); + } +}; + +class PriorStepContextInjector { +public: + static StepContext build(const std::vector& headerPaths) { + StepContext out; + std::set patterns; + + for (const auto& p : headerPaths) { + std::string text = readAll(p); + if (text.empty()) { + out.warnings.push_back("Missing or unreadable header: " + p); + continue; + } + + out.includeDependencies[p] = parseIncludes(text); + auto types = parseTypesAndMethods(text); + for (const auto& t : types) { + out.priorApis.push_back(t); + if (endsWith(t.name, "Report")) patterns.insert("Suffix:Report"); + if (endsWith(t.name, "Spec")) patterns.insert("Suffix:Spec"); + if (endsWith(t.name, "State")) patterns.insert("Suffix:State"); + } + } + + out.namingPatterns.assign(patterns.begin(), patterns.end()); + return out; + } + +private: + static std::string readAll(const std::string& path) { + std::ifstream in(path); + if (!in.is_open()) return ""; + std::ostringstream ss; + ss << in.rdbuf(); + return ss.str(); + } + + static bool endsWith(const std::string& v, const std::string& suffix) { + return v.size() >= suffix.size() && + v.compare(v.size() - suffix.size(), suffix.size(), suffix) == 0; + } + + static std::vector parseIncludes(const std::string& text) { + std::vector out; + std::regex incRe(R"inc(#include\s+"([^"]+)")inc"); + auto begin = std::sregex_iterator(text.begin(), text.end(), incRe); + auto end = std::sregex_iterator(); + for (auto it = begin; it != end; ++it) { + out.push_back((*it)[1].str()); + } + return out; + } + + static std::vector parseTypesAndMethods(const std::string& text) { + std::vector out; + std::regex typeRe(R"(\b(class|struct)\s+([A-Za-z_][A-Za-z0-9_]*))"); + auto begin = std::sregex_iterator(text.begin(), text.end(), typeRe); + auto end = std::sregex_iterator(); + for (auto it = begin; it != end; ++it) { + ApiTypeSummary t; + t.kind = (*it)[1].str(); + t.name = (*it)[2].str(); + out.push_back(std::move(t)); + } + + std::regex methodRe( + R"((?:static\s+)?[A-Za-z_][A-Za-z0-9_:<>\s,&\*]*\s+[A-Za-z_][A-Za-z0-9_]*\s*\([^)]*\)\s*(?:const\s*)?(?:\{|;))"); + auto mBegin = std::sregex_iterator(text.begin(), text.end(), methodRe); + auto mEnd = std::sregex_iterator(); + + std::vector methods; + for (auto it = mBegin; it != mEnd; ++it) { + ApiMethodSummary m; + m.signature = std::regex_replace((*it).str(), std::regex(R"(\s+)"), " "); + if (!m.signature.empty() && + (m.signature.back() == '{' || m.signature.back() == ';')) { + m.signature.pop_back(); + } + methods.push_back(std::move(m)); + } + if (!out.empty()) { + for (auto& t : out) t.methods = methods; + } + return out; + } +}; diff --git a/editor/tests/step484_test.cpp b/editor/tests/step484_test.cpp new file mode 100644 index 0000000..54b0355 --- /dev/null +++ b/editor/tests/step484_test.cpp @@ -0,0 +1,120 @@ +// Step 484: Prior-Step Context Injector Tests (unit, integration) + +#include "PriorStepContextInjector.h" + +#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_extracts_type_names_from_prior_headers() { + TEST(extracts_type_names_from_prior_headers); + auto ctx = PriorStepContextInjector::build({ + "editor/src/StepSpecExpander.h", + "editor/src/ProjectConventionAnalyzer.h" + }); + bool foundStepSpec = false, foundConventions = false; + for (const auto& t : ctx.priorApis) { + if (t.name == "StepSpec") foundStepSpec = true; + if (t.name == "ProjectConventions") foundConventions = true; + } + CHECK(foundStepSpec, "missing StepSpec"); + CHECK(foundConventions, "missing ProjectConventions"); + PASS(); +} + +void test_extracts_method_signatures_from_headers() { + TEST(extracts_method_signatures_from_headers); + auto ctx = PriorStepContextInjector::build({"editor/src/StepSpecExpander.h"}); + CHECK(!ctx.priorApis.empty(), "expected extracted APIs"); + bool hasExpand = false; + for (const auto& m : ctx.priorApis[0].methods) { + if (m.signature.find("expand(") != std::string::npos) hasExpand = true; + } + CHECK(hasExpand, "missing expand method signature"); + PASS(); +} + +void test_builds_include_dependency_graph() { + TEST(builds_include_dependency_graph); + auto path = std::string("editor/src/StepSpecExpander.h"); + auto ctx = PriorStepContextInjector::build({path}); + CHECK(ctx.includeDependencies.count(path) == 1, "missing include graph entry"); + PASS(); +} + +void test_detects_naming_patterns_from_prior_types() { + TEST(detects_naming_patterns_from_prior_types); + auto ctx = PriorStepContextInjector::build({ + "editor/src/StepSpecExpander.h", + "editor/src/WorkflowState.h" + }); + bool hasSpec = false, hasState = false; + for (const auto& p : ctx.namingPatterns) { + if (p == "Suffix:Spec") hasSpec = true; + if (p == "Suffix:State") hasState = true; + } + CHECK(hasSpec, "expected Spec suffix pattern"); + CHECK(hasState, "expected State suffix pattern"); + PASS(); +} + +void test_condensed_brief_contains_type_and_pattern_summary() { + TEST(condensed_brief_contains_type_and_pattern_summary); + auto ctx = PriorStepContextInjector::build({"editor/src/StepSpecExpander.h"}); + auto brief = ctx.condensedBrief(); + CHECK(brief.find("types:") != std::string::npos, "missing types section"); + CHECK(brief.find("patterns:") != std::string::npos, "missing patterns section"); + PASS(); +} + +void test_method_signatures_are_declaration_level_not_implementation_blocks() { + TEST(method_signatures_are_declaration_level_not_implementation_blocks); + auto ctx = PriorStepContextInjector::build({"editor/src/StepSpecExpander.h"}); + CHECK(!ctx.priorApis.empty(), "missing api summaries"); + for (const auto& m : ctx.priorApis[0].methods) { + CHECK(m.signature.find("{") == std::string::npos, "unexpected implementation brace"); + } + PASS(); +} + +void test_missing_header_path_reports_warning() { + TEST(missing_header_path_reports_warning); + auto ctx = PriorStepContextInjector::build({"editor/src/DOES_NOT_EXIST_484.h"}); + CHECK(!ctx.warnings.empty(), "expected missing file warning"); + PASS(); +} + +void test_integration_multiple_headers_yield_nonempty_context_brief() { + TEST(integration_multiple_headers_yield_nonempty_context_brief); + auto ctx = PriorStepContextInjector::build({ + "editor/src/StepSpecExpander.h", + "editor/src/ProjectConventionAnalyzer.h", + "editor/src/PriorStepContextInjector.h" + }); + CHECK(ctx.priorApis.size() >= 3, "expected at least three API summaries"); + CHECK(!ctx.condensedBrief().empty(), "expected non-empty brief"); + PASS(); +} + +int main() { + std::cout << "Step 484: Prior-Step Context Injector Tests\n"; + + test_extracts_type_names_from_prior_headers(); // 1 + test_extracts_method_signatures_from_headers(); // 2 + test_builds_include_dependency_graph(); // 3 + test_detects_naming_patterns_from_prior_types(); // 4 + test_condensed_brief_contains_type_and_pattern_summary(); // 5 + test_method_signatures_are_declaration_level_not_implementation_blocks(); // 6 + test_missing_header_path_reports_warning(); // 7 + test_integration_multiple_headers_yield_nonempty_context_brief(); // 8 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) + << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index ab2d90b..6e8e1a7 100644 --- a/progress.md +++ b/progress.md @@ -6951,3 +6951,40 @@ worker output conformance. - `editor/src/ProjectConventionAnalyzer.h` within header-size limit (`223` <= `600`) - `editor/tests/step483_test.cpp` within test-file size guidance (`146` lines) - Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md` + +### Step 484: Prior-Step Context Injector +**Status:** PASS (8/8 tests) + +Adds prior-step API/context injection so worker-facing briefs can include +condensed type/method summaries and include dependencies instead of full +headers. + +**Files added:** +- `editor/src/PriorStepContextInjector.h` — context extraction layer: + - `ApiTypeSummary`, `ApiMethodSummary`, `StepContext` + - `build(headerPaths)`: + - parses class/struct names from prior headers + - extracts declaration/definition-level method signatures + - records include dependency graph from `#include "..."` lines + - infers naming patterns from type suffixes (`Spec`, `State`, `Report`) + - emits warnings for missing/unreadable headers + - `condensedBrief()` for prompt-sized worker context summaries +- `editor/tests/step484_test.cpp` — 8 tests (unit/integration) covering: + - type extraction from real prior-step headers + - method signature extraction and declaration-only shape checks + - include graph construction + - naming pattern detection + - condensed brief content + - missing-header warning behavior + - multi-header integration behavior +- `editor/CMakeLists.txt` — `step484_test` target + +**Verification run:** +- `cmake --build editor/build-native --target step484_test step483_test` — PASS +- `./editor/build-native/step484_test` — PASS (8/8) +- `./editor/build-native/step483_test` — PASS (9/9) regression coverage + +**Architecture gate check:** +- `editor/src/PriorStepContextInjector.h` within header-size limit (`125` <= `600`) +- `editor/tests/step484_test.cpp` within test-file size guidance (`120` lines) +- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`