Step 137: composition builder

This commit is contained in:
Bill
2026-02-09 17:32:17 -07:00
parent 60f3131ed9
commit 4bfaaa6cce
8 changed files with 238 additions and 2 deletions

View File

@@ -382,7 +382,7 @@ vcpkg's imgui 1.91.9 removed the `sdl2-binding` feature (only `sdl3-binding` exi
## What's Next
Sprint 5 in progress. Step 136 (agent library-aware mode) done. Next: Step 137 (function composition builder).
Sprint 5 in progress. Step 137 (function composition builder) done. Next: Step 138 (type-aware code generation).
---
@@ -482,5 +482,6 @@ Sprint 5 in progress. Step 136 (agent library-aware mode) done. Next: Step 137 (
| 2026-02-09 | Codex | Step 134: PrimitivesRegistry aggregating builtins, imports, and in-scope symbols. 6/6 tests pass. |
| 2026-02-09 | Codex | Step 135: Library-aware completion ordering with auto-import hints for non-primitive symbols. 5/5 tests pass. |
| 2026-02-09 | Codex | Step 136: Agent preferImports/strictMode policy checks on mutations with warnings or blocking. 4/4 tests pass. |
| 2026-02-09 | Codex | Step 137: Composition builder panel with pipeline generation and code insertion. 4/4 tests pass. |
| 2026-02-09 | Codex | Added FEATURE_REQUESTS.md to track security vulnerability awareness and semantic library annotations for future sprints. |
| 2026-02-09 | Codex | Added LLM tooling + MCP bridge feature request to FEATURE_REQUESTS.md. |

View File

@@ -762,6 +762,10 @@ add_executable(step136_test tests/step136_test.cpp)
target_include_directories(step136_test PRIVATE src)
target_link_libraries(step136_test PRIVATE nlohmann_json::nlohmann_json)
add_executable(step137_test tests/step137_test.cpp)
target_include_directories(step137_test PRIVATE src)
target_link_libraries(step137_test PRIVATE nlohmann_json::nlohmann_json)
find_package(SDL2 CONFIG REQUIRED)
find_package(OpenGL REQUIRED)
find_package(glad CONFIG REQUIRED)

View File

@@ -0,0 +1,72 @@
#pragma once
#include "PrimitivesRegistry.h"
#include "ast/Expression.h"
#include "ast/Statement.h"
#include "ast/Function.h"
#include "ast/Module.h"
#include <string>
#include <vector>
#include <sstream>
#include <algorithm>
#include <cctype>
struct CompositionStep {
std::string symbol;
std::string inputType;
std::string outputType;
};
struct CompositionState {
std::vector<CompositionStep> steps;
std::string inputType;
std::string outputType;
};
static inline std::string inferTypeHint(const std::string& name) {
std::string lower = name;
std::transform(lower.begin(), lower.end(), lower.begin(),
[](unsigned char c) { return (char)std::tolower(c); });
if (lower.find("string") != std::string::npos || lower.find("str") != std::string::npos) return "string";
if (lower.find("json") != std::string::npos) return "json";
if (lower.find("list") != std::string::npos || lower.find("array") != std::string::npos) return "list";
if (lower.find("map") != std::string::npos || lower.find("dict") != std::string::npos) return "map";
if (lower.find("int") != std::string::npos || lower.find("count") != std::string::npos) return "int";
return "any";
}
static inline std::vector<CompositionStep> suggestCompositionSteps(
const std::vector<PrimitiveSymbol>& primitives,
const std::string& desiredInput,
const std::string& desiredOutput) {
std::vector<CompositionStep> out;
for (const auto& prim : primitives) {
if (prim.kind != "function") continue;
CompositionStep step;
step.symbol = prim.name;
step.inputType = desiredInput.empty() ? inferTypeHint(prim.name) : desiredInput;
step.outputType = desiredOutput.empty() ? inferTypeHint(prim.name) : desiredOutput;
out.push_back(step);
}
return out;
}
static inline std::string buildCompositionCode(const std::vector<CompositionStep>& steps,
const std::string& inputVar) {
if (steps.empty()) return "";
std::string expr = inputVar;
for (const auto& step : steps) {
expr = step.symbol + "(" + expr + ")";
}
return expr;
}
static inline std::string buildCompositionFunction(const std::vector<CompositionStep>& steps,
const std::string& functionName,
const std::string& inputVar) {
std::string expr = buildCompositionCode(steps, inputVar);
if (expr.empty()) return "";
std::ostringstream ss;
ss << "def " << functionName << "(" << inputVar << "):\n";
ss << " return " << expr << "\n";
return ss.str();
}

View File

@@ -0,0 +1,94 @@
#pragma once
#include "imgui.h"
#include "CompositionBuilder.h"
#include <string>
#include <cstdio>
struct CompositionPanelState {
char inputType[64] = {};
char outputType[64] = {};
char functionName[64] = "compose_pipeline";
std::string inputVar = "x";
int selected = -1;
};
static bool renderCompositionPanel(CompositionPanelState& state,
const std::vector<PrimitiveSymbol>& primitives,
std::string& outCode,
std::string& outputLog) {
outCode.clear();
ImGui::InputText("Input Type", state.inputType, sizeof(state.inputType));
ImGui::InputText("Output Type", state.outputType, sizeof(state.outputType));
ImGui::InputText("Function Name", state.functionName, sizeof(state.functionName));
char inputVarBuf[64];
std::snprintf(inputVarBuf, sizeof(inputVarBuf), "%s", state.inputVar.c_str());
if (ImGui::InputText("Input Var", inputVarBuf, sizeof(inputVarBuf))) {
state.inputVar = inputVarBuf;
}
std::string input = state.inputType;
std::string output = state.outputType;
auto suggestions = suggestCompositionSteps(primitives, input, output);
ImGui::Separator();
ImGui::TextDisabled("Pick functions to build a pipeline:");
ImGui::BeginChild("##composeList", ImVec2(0, 180), true);
for (int i = 0; i < (int)suggestions.size(); ++i) {
std::string label = suggestions[i].symbol + " (" + suggestions[i].inputType +
" -> " + suggestions[i].outputType + ")";
if (ImGui::Selectable(label.c_str(), state.selected == i)) {
state.selected = i;
}
}
ImGui::EndChild();
ImGui::Separator();
if (state.selected >= 0 && state.selected < (int)suggestions.size()) {
if (ImGui::Button("Add Step")) {
if (state.steps.size() >= 8) {
outputLog += "[compose] Max 8 steps.\n";
} else {
state.steps.push_back(suggestions[state.selected]);
}
}
ImGui::SameLine();
if (ImGui::Button("Clear")) {
state.steps.clear();
}
} else {
if (ImGui::Button("Clear")) {
state.steps.clear();
}
}
ImGui::Separator();
ImGui::TextDisabled("Pipeline:");
for (size_t i = 0; i < state.steps.size(); ++i) {
ImGui::Text("%zu. %s", i + 1, state.steps[i].symbol.c_str());
}
if (!state.steps.empty()) {
if (ImGui::Button("Remove Last")) {
state.steps.pop_back();
}
ImGui::SameLine();
}
std::string funcName = state.functionName;
if (funcName.empty()) funcName = "compose_pipeline";
std::string code = buildCompositionFunction(state.steps, funcName, state.inputVar);
if (!code.empty()) {
ImGui::Separator();
ImGui::TextDisabled("Generated:");
ImGui::BeginChild("##composeCode", ImVec2(0, 120), true);
ImGui::TextUnformatted(code.c_str());
ImGui::EndChild();
if (ImGui::Button("Insert Code")) {
outCode = code;
outputLog += "[compose] Inserted pipeline.\n";
return true;
}
}
return false;
}

View File

@@ -49,6 +49,7 @@
#include "ImportManager.h"
#include "PrimitivesRegistry.h"
#include "AgentLibraryPolicy.h"
#include "CompositionPanel.h"
#include "IncrementalOptimizer.h"
#include "ast/Serialization.h"
#include "ast/Generator.h"
@@ -173,6 +174,8 @@ struct EditorState {
DependencyPanelState dependencyPanel;
bool showLibraryBrowserPanel = true;
LibraryBrowserState libraryBrowser;
bool showCompositionPanel = false;
CompositionPanelState compositionPanel;
struct LibraryIndexRequest {
std::string name;
std::string version;

View File

@@ -307,6 +307,7 @@ int main(int, char**) {
&state.showTerminalPanel);
ImGui::MenuItem("Dependencies", nullptr, &state.showDependencyPanel);
ImGui::MenuItem("Libraries", nullptr, &state.showLibraryBrowserPanel);
ImGui::MenuItem("Compose", nullptr, &state.showCompositionPanel);
ImGui::MenuItem("Settings", nullptr, &state.showSettingsPanel);
ImGui::MenuItem("LSP Servers...", nullptr, &state.showLspSettings);
if (state.active()) {
@@ -588,6 +589,27 @@ int main(int, char**) {
ImGui::End();
}
if (state.showCompositionPanel) {
ImGui::Begin("Compose", &state.showCompositionPanel);
ImGui::PushFont(uiFont);
std::string nodeId;
if (state.activeAST()) {
ASTNode* scopeNode = findNodeAtPosition(state.activeAST(),
std::max(0, state.active()->cursorLine - 1),
std::max(0, state.active()->cursorCol - 1));
if (scopeNode) nodeId = scopeNode->id;
}
std::vector<PrimitiveSymbol> primitives;
auto funcs = state.primitives.getAvailableFunctions(nodeId);
primitives.insert(primitives.end(), funcs.begin(), funcs.end());
std::string code;
if (renderCompositionPanel(state.compositionPanel, primitives, code, state.outputLog)) {
state.insertTextAtCursor(code);
}
ImGui::PopFont();
ImGui::End();
}
// ---------------------------------------------------------------
// Find / Replace bar (floating at top of editor)
// ---------------------------------------------------------------

View File

@@ -0,0 +1,40 @@
// Step 137 TDD Test: Composition builder
#include "CompositionBuilder.h"
#include <iostream>
static void expect(bool cond, const std::string& name, int& passed, int& failed) {
if (cond) {
std::cout << "Test " << (passed + failed + 1) << " PASS: " << name << "\n";
++passed;
} else {
std::cout << "Test " << (passed + failed + 1) << " FAIL: " << name << "\n";
++failed;
}
}
int main() {
int passed = 0;
int failed = 0;
std::vector<PrimitiveSymbol> prims = {
{"normalize", "function", "import", "lib"},
{"tokenize", "function", "import", "lib"}
};
auto steps = suggestCompositionSteps(prims, "string", "string");
expect(!steps.empty(), "suggest steps", passed, failed);
std::vector<CompositionStep> pipeline = {
{"normalize", "string", "string"},
{"tokenize", "string", "list"}
};
std::string code = buildCompositionCode(pipeline, "input");
expect(code == "tokenize(normalize(input))", "compose nested calls", passed, failed);
std::string func = buildCompositionFunction(pipeline, "pipe", "input");
expect(func.find("def pipe") != std::string::npos, "function header", passed, failed);
expect(func.find("return tokenize(normalize(input))") != std::string::npos,
"function return", passed, failed);
std::cout << "\n=== Step 137 Results: " << passed << " passed, " << failed << " failed ===\n";
return failed == 0 ? 0 : 1;
}

View File

@@ -131,7 +131,7 @@ what's already available.
hard enforcement, but it defaults to off.
*Modifies:* `ASTMutationAPI.h`, `WebSocketServer.h`
- [ ] **Step 137: Function composition builder**
- [x] **Step 137: Function composition builder**
New UI mode: "Compose". Shows a visual flow of available library functions.
User picks a function → sees its inputs/outputs → picks the next function
whose input matches the previous output. Builds a pipeline/chain visually.