diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 469e2c2..a201e06 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -3820,4 +3820,13 @@ target_link_libraries(step548_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step549_test tests/step549_test.cpp) +target_include_directories(step549_test PRIVATE src) +target_link_libraries(step549_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/CostReductionSuggestionEngine.h b/editor/src/CostReductionSuggestionEngine.h new file mode 100644 index 0000000..4e047b9 --- /dev/null +++ b/editor/src/CostReductionSuggestionEngine.h @@ -0,0 +1,68 @@ +#pragma once +// Step 549: Suggestion Engine for Cost Reductions + +#include +#include +#include + +struct CostReductionInput { + int candidateOperationCount = 0; + int candidateSymbolCount = 0; + bool deterministicTemplateAvailable = false; + bool batchingPossible = false; + bool narrowContextPossible = false; + int stepTokens = 0; + int stepTokenCeiling = 0; + int workflowTokens = 0; + int workflowTokenCeiling = 0; +}; + +struct CostSuggestion { + std::string type; // template_substitution/batching/context_narrowing + std::string rationale; + int estimatedTokenSavings = 0; + int priority = 0; +}; + +class CostReductionSuggestionEngine { +public: + static std::vector suggest(const CostReductionInput& input) { + std::vector out; + const int stepOver = std::max(0, input.stepTokens - input.stepTokenCeiling); + const int workflowOver = std::max(0, input.workflowTokens - input.workflowTokenCeiling); + const int pressure = stepOver + workflowOver; + + if (input.deterministicTemplateAvailable) { + CostSuggestion s; + s.type = "template_substitution"; + s.rationale = "deterministic template can replace free-form generation"; + s.estimatedTokenSavings = std::max(20, input.candidateOperationCount * 8); + s.priority = 80 + (pressure > 0 ? 10 : 0); + out.push_back(s); + } + + if (input.batchingPossible && input.candidateOperationCount >= 2) { + CostSuggestion s; + s.type = "batching"; + s.rationale = "multiple compatible operations can be executed in one pass"; + s.estimatedTokenSavings = std::max(15, input.candidateOperationCount * 5); + s.priority = 70 + (pressure > 0 ? 15 : 0); + out.push_back(s); + } + + if (input.narrowContextPossible && input.candidateSymbolCount > 3) { + CostSuggestion s; + s.type = "context_narrowing"; + s.rationale = "reduce context width to contract-relevant symbols only"; + s.estimatedTokenSavings = std::max(10, (input.candidateSymbolCount - 3) * 6); + s.priority = 65 + (pressure > 0 ? 20 : 0); + out.push_back(s); + } + + std::sort(out.begin(), out.end(), [](const CostSuggestion& a, const CostSuggestion& b) { + if (a.priority != b.priority) return a.priority > b.priority; + return a.estimatedTokenSavings > b.estimatedTokenSavings; + }); + return out; + } +}; diff --git a/editor/tests/step549_test.cpp b/editor/tests/step549_test.cpp new file mode 100644 index 0000000..8d8530a --- /dev/null +++ b/editor/tests/step549_test.cpp @@ -0,0 +1,146 @@ +// Step 549: Suggestion Engine for Cost Reductions (12 tests) + +#include "CostReductionSuggestionEngine.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 {} + +static bool hasType(const std::vector& s, const std::string& type) { + for (const auto& x : s) if (x.type == type) return true; + return false; +} + +static int priorityOf(const std::vector& s, const std::string& type) { + for (const auto& x : s) if (x.type == type) return x.priority; + return -1; +} + +void test_template_suggestion_emitted_when_available() { + TEST(template_suggestion_emitted_when_available); + auto s = CostReductionSuggestionEngine::suggest({5, 6, true, false, false, 100, 200, 500, 1000}); + CHECK(hasType(s, "template_substitution"), "template suggestion expected"); + PASS(); +} + +void test_batching_suggestion_emitted_when_possible() { + TEST(batching_suggestion_emitted_when_possible); + auto s = CostReductionSuggestionEngine::suggest({4, 2, false, true, false, 100, 200, 500, 1000}); + CHECK(hasType(s, "batching"), "batching suggestion expected"); + PASS(); +} + +void test_context_narrowing_suggestion_emitted_when_possible() { + TEST(context_narrowing_suggestion_emitted_when_possible); + auto s = CostReductionSuggestionEngine::suggest({1, 10, false, false, true, 100, 200, 500, 1000}); + CHECK(hasType(s, "context_narrowing"), "context narrowing suggestion expected"); + PASS(); +} + +void test_no_suggestions_when_no_optimization_paths_available() { + TEST(no_suggestions_when_no_optimization_paths_available); + auto s = CostReductionSuggestionEngine::suggest({1, 2, false, false, false, 100, 200, 500, 1000}); + CHECK(s.empty(), "no suggestions expected"); + PASS(); +} + +void test_over_budget_increases_priorities() { + TEST(over_budget_increases_priorities); + auto low = CostReductionSuggestionEngine::suggest({4, 8, true, true, true, 100, 200, 500, 1000}); + auto high = CostReductionSuggestionEngine::suggest({4, 8, true, true, true, 400, 200, 1500, 1000}); + CHECK(priorityOf(high, "template_substitution") > priorityOf(low, "template_substitution"), + "over-budget should raise template priority"); + CHECK(priorityOf(high, "batching") > priorityOf(low, "batching"), + "over-budget should raise batching priority"); + CHECK(priorityOf(high, "context_narrowing") > priorityOf(low, "context_narrowing"), + "over-budget should raise context priority"); + PASS(); +} + +void test_template_savings_scale_with_operation_count() { + TEST(template_savings_scale_with_operation_count); + auto a = CostReductionSuggestionEngine::suggest({2, 4, true, false, false, 100, 200, 500, 1000}); + auto b = CostReductionSuggestionEngine::suggest({8, 4, true, false, false, 100, 200, 500, 1000}); + int sa = 0, sb = 0; + for (const auto& x : a) if (x.type == "template_substitution") sa = x.estimatedTokenSavings; + for (const auto& x : b) if (x.type == "template_substitution") sb = x.estimatedTokenSavings; + CHECK(sb > sa, "template savings should scale with operations"); + PASS(); +} + +void test_context_savings_scale_with_symbol_count() { + TEST(context_savings_scale_with_symbol_count); + auto a = CostReductionSuggestionEngine::suggest({1, 5, false, false, true, 100, 200, 500, 1000}); + auto b = CostReductionSuggestionEngine::suggest({1, 12, false, false, true, 100, 200, 500, 1000}); + int sa = 0, sb = 0; + for (const auto& x : a) if (x.type == "context_narrowing") sa = x.estimatedTokenSavings; + for (const auto& x : b) if (x.type == "context_narrowing") sb = x.estimatedTokenSavings; + CHECK(sb > sa, "context savings should scale with symbol count"); + PASS(); +} + +void test_batching_requires_multiple_operations() { + TEST(batching_requires_multiple_operations); + auto s = CostReductionSuggestionEngine::suggest({1, 5, false, true, false, 100, 200, 500, 1000}); + CHECK(!hasType(s, "batching"), "batching should require >=2 operations"); + PASS(); +} + +void test_context_narrowing_requires_more_than_three_symbols() { + TEST(context_narrowing_requires_more_than_three_symbols); + auto s = CostReductionSuggestionEngine::suggest({2, 3, false, false, true, 100, 200, 500, 1000}); + CHECK(!hasType(s, "context_narrowing"), "context narrowing should require >3 symbols"); + PASS(); +} + +void test_suggestions_sorted_by_priority_descending() { + TEST(suggestions_sorted_by_priority_descending); + auto s = CostReductionSuggestionEngine::suggest({5, 10, true, true, true, 400, 200, 1500, 1000}); + CHECK(s.size() >= 2, "need at least two suggestions"); + CHECK(s[0].priority >= s[1].priority, "suggestions should be priority sorted"); + PASS(); +} + +void test_tiebreaker_by_estimated_savings() { + TEST(tiebreaker_by_estimated_savings); + auto s = CostReductionSuggestionEngine::suggest({4, 10, true, true, false, 100, 200, 500, 1000}); + CHECK(s.size() >= 2, "need at least two suggestions"); + if (s[0].priority == s[1].priority) { + CHECK(s[0].estimatedTokenSavings >= s[1].estimatedTokenSavings, + "tie should break by estimated savings"); + } + PASS(); +} + +void test_rationales_are_present() { + TEST(rationales_are_present); + auto s = CostReductionSuggestionEngine::suggest({5, 10, true, true, true, 100, 200, 500, 1000}); + for (const auto& x : s) { + CHECK(!x.rationale.empty(), "rationale should be present"); + } + PASS(); +} + +int main() { + std::cout << "Step 549: Suggestion Engine for Cost Reductions\n"; + + test_template_suggestion_emitted_when_available(); // 1 + test_batching_suggestion_emitted_when_possible(); // 2 + test_context_narrowing_suggestion_emitted_when_possible(); // 3 + test_no_suggestions_when_no_optimization_paths_available();// 4 + test_over_budget_increases_priorities(); // 5 + test_template_savings_scale_with_operation_count(); // 6 + test_context_savings_scale_with_symbol_count(); // 7 + test_batching_requires_multiple_operations(); // 8 + test_context_narrowing_requires_more_than_three_symbols(); // 9 + test_suggestions_sorted_by_priority_descending(); // 10 + test_tiebreaker_by_estimated_savings(); // 11 + test_rationales_are_present(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index 0ec58b2..0e44848 100644 --- a/progress.md +++ b/progress.md @@ -9550,3 +9550,40 @@ decision flow. - **Steps completed:** 5 - **New tests in phase plan:** 56/56 passing - **Routing/context/cost optimization integration:** PASS + +### Step 549: Suggestion Engine for Cost Reductions +**Status:** PASS (12/12 tests) + +Implements cost-reduction suggestion generation for constrained orchestration, +including template substitution, batching, and context narrowing opportunities +with priority and savings estimates. + +**Files added:** +- `editor/src/CostReductionSuggestionEngine.h` - cost suggestion module: + - emits template substitution recommendations when deterministic paths exist + - emits batching recommendations for compatible multi-op workloads + - emits context narrowing recommendations for wide symbol candidate sets + - computes estimated token savings and priority by budget pressure + - deterministic sorting by priority and estimated savings +- `editor/tests/step549_test.cpp` - 12 tests covering: + - suggestion emission behavior per optimization path + - no-suggestion baseline behavior + - over-budget priority amplification behavior + - savings scaling behavior by op/symbol breadth + - path eligibility thresholds for batching/context narrowing + - ordering/tiebreak behavior + - rationale presence requirements + +**Files modified:** +- `editor/CMakeLists.txt` - `step549_test` target + +**Verification run:** +- `cmake -S editor -B editor/build-native` - PASS +- `cmake --build editor/build-native --target step549_test step548_test` - PASS +- `./editor/build-native/step549_test` - PASS (12/12) +- `./editor/build-native/step548_test` - PASS (8/8) regression coverage + +**Architecture gate check:** +- `editor/src/CostReductionSuggestionEngine.h` within header-size limit (`68` <= `600`) +- `editor/tests/step549_test.cpp` within test-file size guidance (`146` lines) +- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`