Step 545: add contract-aware context bundle minimizer
This commit is contained in:
@@ -3784,4 +3784,13 @@ target_link_libraries(step544_test PRIVATE
|
||||
tree_sitter_javascript tree_sitter_typescript
|
||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||
|
||||
add_executable(step545_test tests/step545_test.cpp)
|
||||
target_include_directories(step545_test PRIVATE src)
|
||||
target_link_libraries(step545_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)
|
||||
|
||||
88
editor/src/ContextBundleMinimizer.h
Normal file
88
editor/src/ContextBundleMinimizer.h
Normal file
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
// Step 545: Context Bundle Minimizer
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct ContextBundleInput {
|
||||
std::string taskitemId;
|
||||
std::vector<std::string> contractTargets;
|
||||
std::vector<std::string> contractSymbols;
|
||||
std::vector<std::string> contractOps;
|
||||
std::vector<std::string> fileContext;
|
||||
std::vector<std::string> projectContext;
|
||||
std::vector<std::string> dependencyContext;
|
||||
bool narrowOperation = true;
|
||||
};
|
||||
|
||||
struct ContextBundleOutput {
|
||||
std::vector<std::string> minimalContext;
|
||||
std::vector<std::string> removedContext;
|
||||
};
|
||||
|
||||
class ContextBundleMinimizer {
|
||||
public:
|
||||
static ContextBundleOutput minimize(const ContextBundleInput& input) {
|
||||
ContextBundleOutput out;
|
||||
|
||||
auto mustKeep = seedMustKeep(input);
|
||||
appendFiltered(input.fileContext, mustKeep, out.minimalContext, out.removedContext);
|
||||
|
||||
if (!input.narrowOperation) {
|
||||
appendFiltered(input.projectContext, mustKeep, out.minimalContext, out.removedContext);
|
||||
appendFiltered(input.dependencyContext, mustKeep, out.minimalContext, out.removedContext);
|
||||
} else {
|
||||
appendFiltered(input.projectContext, mustKeep, out.minimalContext, out.removedContext,
|
||||
/*strict=*/true);
|
||||
appendFiltered(input.dependencyContext, mustKeep, out.minimalContext, out.removedContext,
|
||||
/*strict=*/true);
|
||||
}
|
||||
|
||||
dedupe(out.minimalContext);
|
||||
dedupe(out.removedContext);
|
||||
return out;
|
||||
}
|
||||
|
||||
private:
|
||||
static std::set<std::string> seedMustKeep(const ContextBundleInput& input) {
|
||||
std::set<std::string> keys;
|
||||
for (const auto& t : input.contractTargets) keys.insert(t);
|
||||
for (const auto& s : input.contractSymbols) keys.insert(s);
|
||||
for (const auto& o : input.contractOps) keys.insert(o);
|
||||
return keys;
|
||||
}
|
||||
|
||||
static void appendFiltered(const std::vector<std::string>& context,
|
||||
const std::set<std::string>& keys,
|
||||
std::vector<std::string>& keep,
|
||||
std::vector<std::string>& drop,
|
||||
bool strict = false) {
|
||||
for (const auto& item : context) {
|
||||
bool relevant = containsAny(item, keys);
|
||||
if (relevant || (!strict && looksStructural(item))) {
|
||||
keep.push_back(item);
|
||||
} else {
|
||||
drop.push_back(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool containsAny(const std::string& item,
|
||||
const std::set<std::string>& keys) {
|
||||
for (const auto& k : keys) {
|
||||
if (!k.empty() && item.find(k) != std::string::npos) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool looksStructural(const std::string& item) {
|
||||
return item.find("file:") == 0 || item.find("ast:") == 0 || item.find("diag:") == 0;
|
||||
}
|
||||
|
||||
static void dedupe(std::vector<std::string>& items) {
|
||||
std::sort(items.begin(), items.end());
|
||||
items.erase(std::unique(items.begin(), items.end()), items.end());
|
||||
}
|
||||
};
|
||||
169
editor/tests/step545_test.cpp
Normal file
169
editor/tests/step545_test.cpp
Normal file
@@ -0,0 +1,169 @@
|
||||
// Step 545: Context Bundle Minimizer (12 tests)
|
||||
|
||||
#include "ContextBundleMinimizer.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
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 contains(const std::vector<std::string>& v, const std::string& needle) {
|
||||
for (const auto& x : v) if (x == needle) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static ContextBundleInput sampleInput(bool narrow = true) {
|
||||
ContextBundleInput i;
|
||||
i.taskitemId = "ti-545";
|
||||
i.contractTargets = {"Function"};
|
||||
i.contractSymbols = {"cursor", "EditorState"};
|
||||
i.contractOps = {"rename"};
|
||||
i.fileContext = {
|
||||
"file:main.cpp",
|
||||
"ast:Function cursor update block",
|
||||
"diag:none",
|
||||
"note:unrelated_ui_theme"
|
||||
};
|
||||
i.projectContext = {
|
||||
"module:EditorState contract cursor",
|
||||
"module:Theme purple gradient",
|
||||
"module:Function signatures"
|
||||
};
|
||||
i.dependencyContext = {
|
||||
"dep:tree_sitter Function",
|
||||
"dep:sdl2 window",
|
||||
"dep:cursor_utils"
|
||||
};
|
||||
i.narrowOperation = narrow;
|
||||
return i;
|
||||
}
|
||||
|
||||
void test_narrow_mode_keeps_relevant_file_context() {
|
||||
TEST(narrow_mode_keeps_relevant_file_context);
|
||||
auto out = ContextBundleMinimizer::minimize(sampleInput(true));
|
||||
CHECK(contains(out.minimalContext, "ast:Function cursor update block"), "relevant file context should remain");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_narrow_mode_drops_unrelated_project_context() {
|
||||
TEST(narrow_mode_drops_unrelated_project_context);
|
||||
auto out = ContextBundleMinimizer::minimize(sampleInput(true));
|
||||
CHECK(contains(out.removedContext, "module:Theme purple gradient"), "unrelated project context should drop");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_narrow_mode_drops_unrelated_dependency_context() {
|
||||
TEST(narrow_mode_drops_unrelated_dependency_context);
|
||||
auto out = ContextBundleMinimizer::minimize(sampleInput(true));
|
||||
CHECK(contains(out.removedContext, "dep:sdl2 window"), "unrelated dependency context should drop");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_nonnarrow_mode_keeps_structural_project_context() {
|
||||
TEST(nonnarrow_mode_keeps_structural_project_context);
|
||||
auto in = sampleInput(false);
|
||||
auto out = ContextBundleMinimizer::minimize(in);
|
||||
CHECK(contains(out.minimalContext, "module:Theme purple gradient") == false,
|
||||
"non-structural unrelated module should still drop");
|
||||
CHECK(contains(out.minimalContext, "module:Function signatures"),
|
||||
"relevant project module should remain");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_nonnarrow_mode_keeps_structural_file_entries() {
|
||||
TEST(nonnarrow_mode_keeps_structural_file_entries);
|
||||
auto in = sampleInput(false);
|
||||
auto out = ContextBundleMinimizer::minimize(in);
|
||||
CHECK(contains(out.minimalContext, "file:main.cpp"), "file structural entry should remain");
|
||||
CHECK(contains(out.minimalContext, "diag:none"), "diag structural entry should remain");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_contract_symbols_drive_relevance() {
|
||||
TEST(contract_symbols_drive_relevance);
|
||||
auto in = sampleInput(true);
|
||||
in.projectContext.push_back("module:EditorState snapshots");
|
||||
auto out = ContextBundleMinimizer::minimize(in);
|
||||
CHECK(contains(out.minimalContext, "module:EditorState snapshots"), "symbol-linked module should remain");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_contract_ops_drive_relevance() {
|
||||
TEST(contract_ops_drive_relevance);
|
||||
auto in = sampleInput(true);
|
||||
in.projectContext.push_back("module:rename strategy docs");
|
||||
auto out = ContextBundleMinimizer::minimize(in);
|
||||
CHECK(contains(out.minimalContext, "module:rename strategy docs"), "op-linked module should remain");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_dedupe_applies_to_minimal_context() {
|
||||
TEST(dedupe_applies_to_minimal_context);
|
||||
auto in = sampleInput(true);
|
||||
in.fileContext.push_back("ast:Function cursor update block");
|
||||
auto out = ContextBundleMinimizer::minimize(in);
|
||||
int count = 0;
|
||||
for (const auto& x : out.minimalContext) if (x == "ast:Function cursor update block") ++count;
|
||||
CHECK(count == 1, "minimal context should be deduped");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_dedupe_applies_to_removed_context() {
|
||||
TEST(dedupe_applies_to_removed_context);
|
||||
auto in = sampleInput(true);
|
||||
in.projectContext.push_back("module:Theme purple gradient");
|
||||
auto out = ContextBundleMinimizer::minimize(in);
|
||||
int count = 0;
|
||||
for (const auto& x : out.removedContext) if (x == "module:Theme purple gradient") ++count;
|
||||
CHECK(count == 1, "removed context should be deduped");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_empty_context_inputs_return_empty_outputs() {
|
||||
TEST(empty_context_inputs_return_empty_outputs);
|
||||
ContextBundleInput in;
|
||||
auto out = ContextBundleMinimizer::minimize(in);
|
||||
CHECK(out.minimalContext.empty(), "minimal should be empty");
|
||||
CHECK(out.removedContext.empty(), "removed should be empty");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_irrelevant_file_note_dropped_even_if_nonnarrow() {
|
||||
TEST(irrelevant_file_note_dropped_even_if_nonnarrow);
|
||||
auto in = sampleInput(false);
|
||||
auto out = ContextBundleMinimizer::minimize(in);
|
||||
CHECK(contains(out.removedContext, "note:unrelated_ui_theme"), "irrelevant file note should drop");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_dependency_relevance_by_symbol_match() {
|
||||
TEST(dependency_relevance_by_symbol_match);
|
||||
auto in = sampleInput(true);
|
||||
in.dependencyContext.push_back("dep:EditorState_helpers");
|
||||
auto out = ContextBundleMinimizer::minimize(in);
|
||||
CHECK(contains(out.minimalContext, "dep:EditorState_helpers"), "symbol-matched dependency should remain");
|
||||
PASS();
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "Step 545: Context Bundle Minimizer\n";
|
||||
|
||||
test_narrow_mode_keeps_relevant_file_context(); // 1
|
||||
test_narrow_mode_drops_unrelated_project_context(); // 2
|
||||
test_narrow_mode_drops_unrelated_dependency_context(); // 3
|
||||
test_nonnarrow_mode_keeps_structural_project_context(); // 4
|
||||
test_nonnarrow_mode_keeps_structural_file_entries(); // 5
|
||||
test_contract_symbols_drive_relevance(); // 6
|
||||
test_contract_ops_drive_relevance(); // 7
|
||||
test_dedupe_applies_to_minimal_context(); // 8
|
||||
test_dedupe_applies_to_removed_context(); // 9
|
||||
test_empty_context_inputs_return_empty_outputs(); // 10
|
||||
test_irrelevant_file_note_dropped_even_if_nonnarrow(); // 11
|
||||
test_dependency_relevance_by_symbol_match(); // 12
|
||||
|
||||
std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n";
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
37
progress.md
37
progress.md
@@ -9396,3 +9396,40 @@ post-apply failure escalation.
|
||||
- `editor/src/ConstrainedRoutingRulesetExtension.h` within header-size limit (`60` <= `600`)
|
||||
- `editor/tests/step544_test.cpp` within test-file size guidance (`126` lines)
|
||||
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
|
||||
|
||||
### Step 545: Context Bundle Minimizer
|
||||
**Status:** PASS (12/12 tests)
|
||||
|
||||
Implements minimal-sufficient context bundling for constrained taskitems,
|
||||
retaining contract-relevant context while dropping unrelated project and
|
||||
dependency noise for narrow operations.
|
||||
|
||||
**Files added:**
|
||||
- `editor/src/ContextBundleMinimizer.h` - context minimization module:
|
||||
- seeds relevance keys from contract targets/symbols/ops
|
||||
- filters file/project/dependency context by relevance and structural heuristics
|
||||
- applies stricter pruning for narrow operations
|
||||
- returns explicit kept vs removed context sets
|
||||
- deterministic dedupe normalization for outputs
|
||||
- `editor/tests/step545_test.cpp` - 12 tests covering:
|
||||
- narrow-mode relevance retention/drop behavior
|
||||
- non-narrow structural keep behavior
|
||||
- symbol/op-driven relevance behavior
|
||||
- dedupe behavior for kept/removed sets
|
||||
- empty-input edge case
|
||||
- irrelevant-note dropping behavior
|
||||
- dependency relevance by symbol-match behavior
|
||||
|
||||
**Files modified:**
|
||||
- `editor/CMakeLists.txt` - `step545_test` target
|
||||
|
||||
**Verification run:**
|
||||
- `cmake -S editor -B editor/build-native` - PASS
|
||||
- `cmake --build editor/build-native --target step545_test step544_test` - PASS
|
||||
- `./editor/build-native/step545_test` - PASS (12/12)
|
||||
- `./editor/build-native/step544_test` - PASS (12/12) regression coverage
|
||||
|
||||
**Architecture gate check:**
|
||||
- `editor/src/ContextBundleMinimizer.h` within header-size limit (`88` <= `600`)
|
||||
- `editor/tests/step545_test.cpp` within test-file size guidance (`169` lines)
|
||||
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
|
||||
|
||||
Reference in New Issue
Block a user