Step 433: add inline code annotation badges model

This commit is contained in:
Bill
2026-02-16 17:20:11 -07:00
parent 2ac44a66f7
commit ab7b8a862b
4 changed files with 284 additions and 0 deletions

View File

@@ -2776,4 +2776,13 @@ target_link_libraries(step432_test PRIVATE
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
add_executable(step433_test tests/step433_test.cpp)
target_include_directories(step433_test PRIVATE src)
target_link_libraries(step433_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)

View File

@@ -0,0 +1,88 @@
#pragma once
#include <algorithm>
#include <map>
#include <string>
#include <vector>
struct AnnotationBadgeInput {
int line = 0;
std::string type;
std::string detail;
std::string nodeId;
};
struct InlineBadge {
int line = 0;
std::string type;
std::string icon;
std::string colorHex;
std::string detail;
std::string nodeId;
};
struct LineBadgeStack {
int line = 0;
std::vector<InlineBadge> badges;
};
class CodeAnnotationBadges {
public:
static std::vector<InlineBadge> buildBadges(const std::vector<AnnotationBadgeInput>& in) {
std::vector<InlineBadge> out;
for (const auto& a : in) {
InlineBadge b;
b.line = a.line;
b.type = a.type;
b.icon = iconForType(a.type);
b.colorHex = colorForType(a.type);
b.detail = a.detail;
b.nodeId = a.nodeId;
out.push_back(b);
}
std::sort(out.begin(), out.end(),
[](const InlineBadge& a, const InlineBadge& b) {
if (a.line != b.line) return a.line < b.line;
return a.type < b.type;
});
return out;
}
static std::vector<LineBadgeStack> stackByLine(const std::vector<InlineBadge>& badges) {
std::map<int, std::vector<InlineBadge>> grouped;
for (const auto& b : badges) grouped[b.line].push_back(b);
std::vector<LineBadgeStack> out;
for (auto& [line, lineBadges] : grouped) {
out.push_back({line, lineBadges});
}
std::sort(out.begin(), out.end(),
[](const LineBadgeStack& a, const LineBadgeStack& b) {
return a.line < b.line;
});
return out;
}
static std::string expandDetail(const InlineBadge& badge) {
return badge.type + " [" + badge.nodeId + "]: " + badge.detail;
}
static std::string iconForType(const std::string& type) {
if (type.find("Intent") != std::string::npos) return "I";
if (type.find("Complexity") != std::string::npos) return "C";
if (type.find("Risk") != std::string::npos) return "R";
if (type.find("Contract") != std::string::npos) return "K";
if (type.find("Review") != std::string::npos) return "V";
if (type.find("Priority") != std::string::npos) return "P";
return "A";
}
static std::string colorForType(const std::string& type) {
if (type.find("Intent") != std::string::npos) return "#1ABC9C";
if (type.find("Complexity") != std::string::npos) return "#F39C12";
if (type.find("Risk") != std::string::npos) return "#E74C3C";
if (type.find("Contract") != std::string::npos) return "#3498DB";
if (type.find("Review") != std::string::npos) return "#9B59B6";
if (type.find("Priority") != std::string::npos) return "#2ECC71";
return "#95A5A6";
}
};

View File

@@ -0,0 +1,141 @@
// Step 433: Inline Annotation Badges Tests (12 tests)
#include "CodeAnnotationBadges.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 std::vector<AnnotationBadgeInput> sampleInputs() {
return {
{10, "IntentAnnotation", "Parse auth tokens", "n1"},
{10, "RiskAnnotation", "High external exposure", "n1"},
{20, "ComplexityAnnotation", "Cyclomatic=12", "n2"},
{30, "ReviewAnnotation", "Human review required", "n3"}
};
}
void test_build_badges_count_matches_input() {
TEST(build_badges_count_matches_input);
auto badges = CodeAnnotationBadges::buildBadges(sampleInputs());
CHECK(badges.size() == sampleInputs().size(), "badge count mismatch");
PASS();
}
void test_badges_sorted_by_line_then_type() {
TEST(badges_sorted_by_line_then_type);
auto badges = CodeAnnotationBadges::buildBadges({
{30, "RiskAnnotation", "r", "n3"},
{10, "IntentAnnotation", "i", "n1"},
{10, "ComplexityAnnotation", "c", "n1"}
});
CHECK(badges[0].line == 10 && badges[1].line == 10 && badges[2].line == 30,
"line sort mismatch");
PASS();
}
void test_icon_mapping_for_known_types() {
TEST(icon_mapping_for_known_types);
CHECK(CodeAnnotationBadges::iconForType("IntentAnnotation") == "I", "intent icon");
CHECK(CodeAnnotationBadges::iconForType("ComplexityAnnotation") == "C", "complexity icon");
CHECK(CodeAnnotationBadges::iconForType("RiskAnnotation") == "R", "risk icon");
PASS();
}
void test_color_mapping_for_known_types() {
TEST(color_mapping_for_known_types);
CHECK(CodeAnnotationBadges::colorForType("IntentAnnotation") == "#1ABC9C", "intent color");
CHECK(CodeAnnotationBadges::colorForType("RiskAnnotation") == "#E74C3C", "risk color");
PASS();
}
void test_unknown_type_fallback_icon_and_color() {
TEST(unknown_type_fallback_icon_and_color);
CHECK(CodeAnnotationBadges::iconForType("UnknownAnnotation") == "A", "fallback icon");
CHECK(CodeAnnotationBadges::colorForType("UnknownAnnotation") == "#95A5A6", "fallback color");
PASS();
}
void test_stack_by_line_groups_multiple_badges_same_line() {
TEST(stack_by_line_groups_multiple_badges_same_line);
auto badges = CodeAnnotationBadges::buildBadges(sampleInputs());
auto stacks = CodeAnnotationBadges::stackByLine(badges);
int line10Count = 0;
for (const auto& s : stacks) if (s.line == 10) line10Count = (int)s.badges.size();
CHECK(line10Count == 2, "line 10 should have two stacked badges");
PASS();
}
void test_stack_by_line_sorted_by_line() {
TEST(stack_by_line_sorted_by_line);
auto badges = CodeAnnotationBadges::buildBadges(sampleInputs());
auto stacks = CodeAnnotationBadges::stackByLine(badges);
CHECK(stacks.size() >= 3, "expected three lines");
CHECK(stacks[0].line < stacks[1].line, "stacks should be sorted by line");
PASS();
}
void test_expand_detail_contains_type_node_and_detail() {
TEST(expand_detail_contains_type_node_and_detail);
auto badges = CodeAnnotationBadges::buildBadges(sampleInputs());
auto text = CodeAnnotationBadges::expandDetail(badges[0]);
CHECK(text.find(badges[0].type) != std::string::npos, "missing type");
CHECK(text.find(badges[0].nodeId) != std::string::npos, "missing node");
CHECK(text.find(badges[0].detail) != std::string::npos, "missing detail");
PASS();
}
void test_badge_preserves_line_and_node_id() {
TEST(badge_preserves_line_and_node_id);
auto badges = CodeAnnotationBadges::buildBadges(sampleInputs());
CHECK(badges[0].line > 0, "line missing");
CHECK(!badges[0].nodeId.empty(), "node id missing");
PASS();
}
void test_empty_input_returns_empty_badges() {
TEST(empty_input_returns_empty_badges);
auto badges = CodeAnnotationBadges::buildBadges({});
CHECK(badges.empty(), "expected empty badges");
PASS();
}
void test_empty_badges_return_empty_stacks() {
TEST(empty_badges_return_empty_stacks);
auto stacks = CodeAnnotationBadges::stackByLine({});
CHECK(stacks.empty(), "expected empty stacks");
PASS();
}
void test_review_annotation_has_review_icon() {
TEST(review_annotation_has_review_icon);
auto badges = CodeAnnotationBadges::buildBadges({{5, "ReviewAnnotation", "review me", "n1"}});
CHECK(!badges.empty(), "badge missing");
CHECK(badges[0].icon == "V", "review icon mismatch");
PASS();
}
int main() {
std::cout << "Step 433: Inline Annotation Badges Tests\n";
test_build_badges_count_matches_input(); // 1
test_badges_sorted_by_line_then_type(); // 2
test_icon_mapping_for_known_types(); // 3
test_color_mapping_for_known_types(); // 4
test_unknown_type_fallback_icon_and_color(); // 5
test_stack_by_line_groups_multiple_badges_same_line();// 6
test_stack_by_line_sorted_by_line(); // 7
test_expand_detail_contains_type_node_and_detail(); // 8
test_badge_preserves_line_and_node_id(); // 9
test_empty_input_returns_empty_badges(); // 10
test_empty_badges_return_empty_stacks(); // 11
test_review_annotation_has_review_icon(); // 12
std::cout << "\nResults: " << passed << "/" << (passed + failed)
<< " passed\n";
return failed == 0 ? 0 : 1;
}

View File

@@ -4994,6 +4994,52 @@ changes and review lifecycle transitions.
- `editor/src/MCPServer.h` (`1940` > `600`)
- `editor/src/HeadlessAgentRPCHandler.h` (`2768` > `600`)
## Phase 19b: Code Annotation Overlay
### Step 433: Inline Annotation Badges
**Status:** PASS (12/12 tests)
Added an inline-annotation badge model for editor gutter overlays, including
type-specific icon/color mapping, stacked line badges, and expandable detail text.
**Files created:**
- `editor/src/CodeAnnotationBadges.h` — inline badge support:
- annotation input -> badge projection
- type->icon mapping (intent/complexity/risk/contract/review/priority)
- type->color mapping for visual encoding
- deterministic line/type sorting
- line-based stacking for multiple annotations on one line
- expandable detail formatter
- `editor/tests/step433_test.cpp` — 12 tests covering:
1. input->badge count preservation
2. sort stability by line/type
3. known icon mapping
4. known color mapping
5. unknown-type fallback icon/color
6. multi-badge same-line stacking
7. stack ordering by line
8. expanded detail payload content
9. line/node identity preservation
10. empty input edge case
11. empty stack edge case
12. review-annotation icon mapping
**Files modified:**
- `editor/CMakeLists.txt``step433_test` target
**Verification run:**
- `step433_test` — PASS (12/12) new step coverage
- `step432_test` — PASS (8/8) regression coverage
- `step431_test` — PASS (12/12) regression coverage
**Architecture gate check:**
- `editor/src/CodeAnnotationBadges.h` within header-size limit (`88` <= `600`)
- `editor/tests/step433_test.cpp` within test-file size guidance (`141` lines)
- Legacy oversized headers persist:
- `editor/src/ast/Serialization.h` (`1427` > `600`)
- `editor/src/MCPServer.h` (`1940` > `600`)
- `editor/src/HeadlessAgentRPCHandler.h` (`2768` > `600`)
# Roadmap Planning — Sprints 12-25+
## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)