Step 538: integrate phase 28a legal-choice execution flow

This commit is contained in:
Bill
2026-02-17 09:37:03 -07:00
parent 3f62fb1ce9
commit 1c798f9323
4 changed files with 320 additions and 0 deletions

View File

@@ -3721,4 +3721,13 @@ target_link_libraries(step537_test PRIVATE
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
add_executable(step538_test tests/step538_test.cpp)
target_include_directories(step538_test PRIVATE src)
target_link_libraries(step538_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,118 @@
#pragma once
// Step 538: Phase 28a Integration
#include <algorithm>
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
#include "ArgumentShapeValidator.h"
#include "ConstrainedExecutionTelemetry.h"
#include "OperationSelectorAPI.h"
#include "SymbolSelectorAPI.h"
using json = nlohmann::json;
struct Phase28aRequest {
TaskitemContract contract;
std::string nodeKind;
ScopeSnapshot scope;
std::string requestedOperation;
std::string requestedSymbol;
json args;
int constrainedTokenCount = 0;
int baselineTokenCount = 0;
};
struct Phase28aResult {
bool menuReady = false;
bool executed = false;
std::vector<std::string> errors;
OperationSelectionResult operationMenu;
SymbolSelectionResult symbolMenu;
TelemetrySummary telemetrySummary;
};
class Phase28aIntegration {
public:
static Phase28aResult run(const Phase28aRequest& request,
const LegalOperationGraph& graph,
ConstrainedExecutionTelemetry& telemetry) {
Phase28aResult result;
result.operationMenu = OperationSelectorAPI::select(
request.contract, request.nodeKind, request.scope, graph);
result.symbolMenu = SymbolSelectorAPI::select(request.contract, request.scope);
result.menuReady = result.operationMenu.supported && result.symbolMenu.supported;
TelemetryEvent event;
event.taskitemId = request.contract.id;
event.candidateOperationCount = (int)result.operationMenu.candidates.size();
event.candidateSymbolCount = (int)result.symbolMenu.candidates.size();
event.baselineTokenCount = request.baselineTokenCount;
event.constrainedTokenCount = request.constrainedTokenCount;
if (!result.menuReady) {
result.errors.push_back("menu_unavailable");
event.rejectionReasons.push_back("menu_unavailable");
telemetry.record(event);
result.telemetrySummary = telemetry.summarize();
return result;
}
if (!hasOperation(result.operationMenu, request.requestedOperation)) {
result.errors.push_back("operation_not_in_legal_menu");
event.rejectionReasons.push_back("operation_not_in_legal_menu");
telemetry.record(event);
result.telemetrySummary = telemetry.summarize();
return result;
}
if (!hasSymbol(result.symbolMenu, request.requestedSymbol)) {
result.errors.push_back("symbol_not_in_legal_menu");
event.rejectionReasons.push_back("symbol_not_in_legal_menu");
telemetry.record(event);
result.telemetrySummary = telemetry.summarize();
return result;
}
auto shape = ArgumentShapeValidator::validate(request.contract,
request.requestedOperation,
request.requestedSymbol,
request.args);
if (!shape.valid) {
result.errors.insert(result.errors.end(), shape.errors.begin(), shape.errors.end());
event.rejectionReasons.push_back("invalid_argument_shape");
telemetry.record(event);
result.telemetrySummary = telemetry.summarize();
return result;
}
event.selectedOperation = request.requestedOperation;
event.success = true;
telemetry.record(event);
result.executed = true;
result.telemetrySummary = telemetry.summarize();
return result;
}
private:
static bool hasOperation(const OperationSelectionResult& menu,
const std::string& operation) {
for (const auto& c : menu.candidates) {
if (c.operation == operation) return true;
}
return false;
}
static bool hasSymbol(const SymbolSelectionResult& menu,
const std::string& symbol) {
for (const auto& c : menu.candidates) {
if (c.name == symbol) return true;
}
return false;
}
};

View File

@@ -0,0 +1,154 @@
// Step 538: Phase 28a Integration (8 tests)
#include "Phase28aIntegration.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 TaskitemContract contract() {
TaskitemContract c;
c.id = "ti-538";
c.nodeId = "n-1";
c.language = "cpp";
c.allowedTargets = {"Function"};
c.allowedOps = {"rename", "update"};
c.allowedSymbols = {"cursor", "count"};
c.forbiddenSymbols = {"unsafeGlobal"};
c.expectedDiagnosticsAdd = {"none"};
return c;
}
static ScopeSnapshot scope() {
ScopeSnapshot s;
s.supported = true;
s.candidates = {
{"cursor", "Variable", 3},
{"count", "Parameter", 3},
{"unsafeGlobal", "Variable", 0}
};
return s;
}
void test_valid_legal_choice_executes_successfully() {
TEST(valid_legal_choice_executes_successfully);
LegalOperationGraph graph;
ConstrainedExecutionTelemetry telemetry;
Phase28aRequest req{contract(), "Function", scope(), "rename", "cursor",
json{{"newName", "cursor2"}}, 20, 40};
auto result = Phase28aIntegration::run(req, graph, telemetry);
CHECK(result.menuReady, "menu should be ready");
CHECK(result.executed, "valid legal choice should execute");
PASS();
}
void test_operation_not_in_menu_is_rejected() {
TEST(operation_not_in_menu_is_rejected);
LegalOperationGraph graph;
ConstrainedExecutionTelemetry telemetry;
Phase28aRequest req{contract(), "Function", scope(), "delete", "cursor",
json{{"confirm", true}}, 20, 40};
auto result = Phase28aIntegration::run(req, graph, telemetry);
CHECK(!result.executed, "illegal op should not execute");
CHECK(!result.errors.empty() && result.errors[0] == "operation_not_in_legal_menu",
"operation rejection expected");
PASS();
}
void test_symbol_not_in_menu_is_rejected() {
TEST(symbol_not_in_menu_is_rejected);
LegalOperationGraph graph;
ConstrainedExecutionTelemetry telemetry;
Phase28aRequest req{contract(), "Function", scope(), "rename", "ghost",
json{{"newName", "ghost2"}}, 20, 40};
auto result = Phase28aIntegration::run(req, graph, telemetry);
CHECK(!result.executed, "illegal symbol should not execute");
CHECK(!result.errors.empty() && result.errors[0] == "symbol_not_in_legal_menu",
"symbol rejection expected");
PASS();
}
void test_invalid_argument_shape_is_rejected() {
TEST(invalid_argument_shape_is_rejected);
LegalOperationGraph graph;
ConstrainedExecutionTelemetry telemetry;
Phase28aRequest req{contract(), "Function", scope(), "rename", "cursor",
json::object(), 20, 40};
auto result = Phase28aIntegration::run(req, graph, telemetry);
CHECK(!result.executed, "invalid args should not execute");
CHECK(!result.errors.empty(), "shape errors expected");
PASS();
}
void test_menu_unavailable_when_scope_unsupported() {
TEST(menu_unavailable_when_scope_unsupported);
LegalOperationGraph graph;
ConstrainedExecutionTelemetry telemetry;
auto s = scope();
s.supported = false;
Phase28aRequest req{contract(), "Function", s, "rename", "cursor",
json{{"newName", "cursor2"}}, 20, 40};
auto result = Phase28aIntegration::run(req, graph, telemetry);
CHECK(!result.menuReady, "menu should be unavailable");
CHECK(!result.executed, "execution should be blocked");
PASS();
}
void test_telemetry_records_candidate_breadth() {
TEST(telemetry_records_candidate_breadth);
LegalOperationGraph graph;
ConstrainedExecutionTelemetry telemetry;
Phase28aRequest req{contract(), "Function", scope(), "rename", "cursor",
json{{"newName", "cursor2"}}, 20, 40};
auto result = Phase28aIntegration::run(req, graph, telemetry);
CHECK(result.telemetrySummary.events == 1, "one telemetry event expected");
CHECK(result.telemetrySummary.avgCandidateOperationCount > 0,
"operation candidate breadth expected");
CHECK(result.telemetrySummary.avgCandidateSymbolCount > 0,
"symbol candidate breadth expected");
PASS();
}
void test_telemetry_shows_token_savings() {
TEST(telemetry_shows_token_savings);
LegalOperationGraph graph;
ConstrainedExecutionTelemetry telemetry;
Phase28aRequest req{contract(), "Function", scope(), "rename", "cursor",
json{{"newName", "cursor2"}}, 18, 40};
auto result = Phase28aIntegration::run(req, graph, telemetry);
CHECK(result.telemetrySummary.tokenSavings == 22, "token savings should be recorded");
PASS();
}
void test_rejection_path_records_failure_telemetry() {
TEST(rejection_path_records_failure_telemetry);
LegalOperationGraph graph;
ConstrainedExecutionTelemetry telemetry;
Phase28aRequest req{contract(), "Function", scope(), "delete", "cursor",
json{{"confirm", true}}, 20, 40};
auto result = Phase28aIntegration::run(req, graph, telemetry);
CHECK(result.telemetrySummary.failureCount == 1, "failure telemetry expected");
CHECK(result.telemetrySummary.rejectionHistogram["operation_not_in_legal_menu"] == 1,
"rejection reason histogram mismatch");
PASS();
}
int main() {
std::cout << "Step 538: Phase 28a Integration\n";
test_valid_legal_choice_executes_successfully(); // 1
test_operation_not_in_menu_is_rejected(); // 2
test_symbol_not_in_menu_is_rejected(); // 3
test_invalid_argument_shape_is_rejected(); // 4
test_menu_unavailable_when_scope_unsupported(); // 5
test_telemetry_records_candidate_breadth(); // 6
test_telemetry_shows_token_savings(); // 7
test_rejection_path_records_failure_telemetry(); // 8
std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n";
return failed == 0 ? 0 : 1;
}

View File

@@ -9110,3 +9110,42 @@ baselines.
- `editor/src/ConstrainedExecutionTelemetry.h` within header-size limit (`75` <= `600`)
- `editor/tests/step537_test.cpp` within test-file size guidance (`175` lines)
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
### Step 538: Phase 28a Integration
**Status:** PASS (8/8 tests)
Integrates legal-choice operation/symbol menus, argument-shape validation, and
telemetry into a single constrained execution surface for agent-facing editing.
**Files added:**
- `editor/src/Phase28aIntegration.h` - Phase 28a integration gate:
- builds operation and symbol legal-choice menus
- enforces selection membership (operation + symbol must come from legal menus)
- validates selected argument shape before execution
- records per-attempt telemetry for success and rejection paths
- returns integrated execution result + aggregate telemetry summary
- `editor/tests/step538_test.cpp` - 8 tests covering:
- valid legal-choice execution path
- illegal operation/symbol menu rejection paths
- argument-shape rejection path
- menu unavailability failure path
- telemetry candidate-breadth, token-savings, and rejection histogram behavior
**Files modified:**
- `editor/CMakeLists.txt` - `step538_test` target
**Verification run:**
- `cmake -S editor -B editor/build-native` - PASS
- `cmake --build editor/build-native --target step538_test step537_test` - PASS
- `./editor/build-native/step538_test` - PASS (8/8)
- `./editor/build-native/step537_test` - PASS (12/12) regression coverage
**Architecture gate check:**
- `editor/src/Phase28aIntegration.h` within header-size limit (`118` <= `600`)
- `editor/tests/step538_test.cpp` within test-file size guidance (`154` lines)
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
**Phase 28a totals (534-538):**
- **Steps completed:** 5
- **New tests in phase plan:** 56/56 passing
- **Legal-choice API + validation + telemetry integration:** PASS