Step 136: agent library-aware mode
This commit is contained in:
@@ -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 135 (library-aware completion) done. Next: Step 136 (agent library-aware mode).
|
||||
Sprint 5 in progress. Step 136 (agent library-aware mode) done. Next: Step 137 (function composition builder).
|
||||
|
||||
---
|
||||
|
||||
@@ -481,5 +481,6 @@ Sprint 5 in progress. Step 135 (library-aware completion) done. Next: Step 136 (
|
||||
| 2026-02-09 | Codex | Step 133: Import statement generation + unused import warnings across Python/JS/Rust/Go/Elisp with auto-insert on library symbol use. 7/7 tests pass. |
|
||||
| 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 | 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. |
|
||||
|
||||
@@ -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)
|
||||
|
||||
104
editor/src/AgentLibraryPolicy.h
Normal file
104
editor/src/AgentLibraryPolicy.h
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
49
editor/tests/step136_test.cpp
Normal file
49
editor/tests/step136_test.cpp
Normal 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;
|
||||
}
|
||||
@@ -120,7 +120,7 @@ what's already available.
|
||||
parameter types from `TypeSignature`.
|
||||
*Modifies:* `CodeEditorWidget.h`, `LSPClient.h`
|
||||
|
||||
- [ ] **Step 136: Agent library-aware mode**
|
||||
- [x] **Step 136: Agent library-aware mode**
|
||||
New agent capability flag: `preferImports`. When set, the agent
|
||||
(via WebSocket API) **prefers** inserting code that references symbols in
|
||||
`PrimitivesRegistry`, and warns (not rejects) when using symbols outside
|
||||
|
||||
Reference in New Issue
Block a user