Step 538: integrate phase 28a legal-choice execution flow
This commit is contained in:
@@ -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)
|
||||
|
||||
118
editor/src/Phase28aIntegration.h
Normal file
118
editor/src/Phase28aIntegration.h
Normal 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;
|
||||
}
|
||||
};
|
||||
154
editor/tests/step538_test.cpp
Normal file
154
editor/tests/step538_test.cpp
Normal 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;
|
||||
}
|
||||
39
progress.md
39
progress.md
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user