Step 136: agent library-aware mode

This commit is contained in:
Bill
2026-02-09 17:28:51 -07:00
parent 4fd61c5df2
commit 60f3131ed9
6 changed files with 175 additions and 3 deletions

View File

@@ -758,6 +758,10 @@ add_executable(step135_test tests/step135_test.cpp)
target_include_directories(step135_test PRIVATE src)
target_link_libraries(step135_test PRIVATE nlohmann_json::nlohmann_json)
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)
find_package(SDL2 CONFIG REQUIRED)
find_package(OpenGL REQUIRED)
find_package(glad CONFIG REQUIRED)

View File

@@ -0,0 +1,104 @@
#pragma once
#include "ast/Module.h"
#include "ast/Function.h"
#include "ast/ExternalModule.h"
#include "ast/TypeSignature.h"
#include "ast/Expression.h"
#include <string>
#include <vector>
#include <unordered_set>
#include <algorithm>
struct LibraryPolicyResult {
bool ok = true;
std::string warning;
std::string error;
std::vector<std::string> unknownFunctions;
};
static inline void collectFunctionCalls(ASTNode* node, std::vector<std::string>& out) {
if (!node) return;
if (node->conceptType == "FunctionCall") {
auto* call = static_cast<FunctionCall*>(node);
if (!call->functionName.empty()) out.push_back(call->functionName);
}
for (auto* child : node->allChildren()) {
collectFunctionCalls(child, out);
}
}
static inline bool matchesAllowed(const std::unordered_set<std::string>& allowed,
const std::string& name) {
if (allowed.count(name) > 0) return true;
for (const auto& item : allowed) {
if (item.size() > name.size()) {
if (item.rfind("." + name) == item.size() - name.size() - 1) return true;
if (item.rfind("::" + name) == item.size() - name.size() - 2) return true;
}
}
return false;
}
static inline std::vector<std::string> collectAllowedFunctions(Module* ast) {
std::vector<std::string> out;
if (!ast) return out;
for (auto* fnNode : ast->getChildren("functions")) {
if (fnNode->conceptType != "Function") continue;
auto* fn = static_cast<Function*>(fnNode);
if (!fn->name.empty()) out.push_back(fn->name);
}
for (auto* extNode : ast->getChildren("externalModules")) {
if (extNode->conceptType != "ExternalModule") continue;
auto* ext = static_cast<ExternalModule*>(extNode);
for (auto* sigNode : ext->getChildren("signatures")) {
if (sigNode->conceptType != "TypeSignature") continue;
auto* sig = static_cast<TypeSignature*>(sigNode);
if (!sig->name.empty()) out.push_back(sig->name);
}
}
return out;
}
static inline LibraryPolicyResult checkMutationLibraryPolicy(ASTNode* node,
Module* ast,
bool preferImports,
bool strictMode) {
LibraryPolicyResult res;
if (!preferImports) return res;
auto allowedList = collectAllowedFunctions(ast);
std::unordered_set<std::string> allowed(allowedList.begin(), allowedList.end());
std::vector<std::string> calls;
collectFunctionCalls(node, calls);
std::vector<std::string> unknown;
for (const auto& name : calls) {
if (!matchesAllowed(allowed, name)) {
if (std::find(unknown.begin(), unknown.end(), name) == unknown.end()) {
unknown.push_back(name);
}
}
}
if (!unknown.empty()) {
res.unknownFunctions = unknown;
std::string alt;
for (size_t i = 0; i < std::min<size_t>(allowedList.size(), 5); ++i) {
if (!alt.empty()) alt += ", ";
alt += allowedList[i];
}
std::string message = "References functions outside imports: ";
for (size_t i = 0; i < unknown.size(); ++i) {
if (i > 0) message += ", ";
message += unknown[i];
}
if (!alt.empty()) message += " | available: " + alt;
if (strictMode) {
res.ok = false;
res.error = message;
} else {
res.warning = message;
}
}
return res;
}

View File

@@ -48,6 +48,7 @@
#include "LibraryBrowserPanel.h"
#include "ImportManager.h"
#include "PrimitivesRegistry.h"
#include "AgentLibraryPolicy.h"
#include "IncrementalOptimizer.h"
#include "ast/Serialization.h"
#include "ast/Generator.h"
@@ -989,6 +990,8 @@ struct EditorState {
}
auto params = request.contains("params") ? request["params"] : json::object();
std::string type = params.value("type", "");
bool preferImports = params.value("preferImports", false);
bool strictMode = params.value("strictMode", false);
Module* ast = mutationAST();
if (!ast) {
response["error"] = {{"code", -32001}, {"message", "AST unavailable"}};
@@ -997,6 +1000,7 @@ struct EditorState {
ASTMutationAPI mut;
mut.setRoot(ast);
ASTMutationAPI::MutationResult res;
LibraryPolicyResult policy;
if (type == "setProperty") {
res = mut.setProperty(params.value("nodeId", ""),
@@ -1017,6 +1021,14 @@ struct EditorState {
if (params.contains("node")) {
node = fromJson(params["node"]);
}
if (node) {
policy = checkMutationLibraryPolicy(node, ast, preferImports, strictMode);
if (!policy.ok) {
response["error"] = {{"code", -32011}, {"message", policy.error}};
deleteTree(node);
return response;
}
}
res = mut.insertNode(params.value("parentId", ""),
params.value("role", ""), node);
if (!res.success && node) {
@@ -1035,7 +1047,9 @@ struct EditorState {
applyOrchestratorToActive();
response["result"] = {
{"success", true},
{"warning", res.warning}
{"warning", res.warning},
{"libraryWarning", policy.warning},
{"unknownFunctions", policy.unknownFunctions}
};
return response;
}

View File

@@ -0,0 +1,49 @@
// Step 136 TDD Test: Agent library-aware mode policy
#include "AgentLibraryPolicy.h"
#include "ast/Function.h"
#include "ast/ExternalModule.h"
#include "ast/TypeSignature.h"
#include "ast/Expression.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;
Module mod("m1", "Module", "python");
auto* fn = new Function("fn1", "user_fn");
mod.addChild("functions", fn);
auto* ext = new ExternalModule("ext_numpy", "numpy", "python", "1.0");
ext->addChild("signatures", new TypeSignature("sig_1", "numpy.array"));
mod.addChild("externalModules", ext);
auto* callKnown = new FunctionCall();
callKnown->functionName = "numpy.array";
auto resKnown = checkMutationLibraryPolicy(callKnown, &mod, true, false);
expect(resKnown.warning.empty(), "known function no warning", passed, failed);
auto* callUnknown = new FunctionCall();
callUnknown->functionName = "mystery";
auto resWarn = checkMutationLibraryPolicy(callUnknown, &mod, true, false);
expect(!resWarn.warning.empty(), "unknown function warning", passed, failed);
expect(!resWarn.unknownFunctions.empty(), "unknown list set", passed, failed);
auto resStrict = checkMutationLibraryPolicy(callUnknown, &mod, true, true);
expect(!resStrict.ok, "strict mode blocks unknown", passed, failed);
delete callKnown;
delete callUnknown;
std::cout << "\n=== Step 136 Results: " << passed << " passed, " << failed << " failed ===\n";
return failed == 0 ? 0 : 1;
}