Phase 10e WIP: environment layer core + host boundary nodes
EnvironmentSpec, CapabilityRequirement, HostCall, ScheduleTask, ModuleLoad classes created. All wired through Serialization.h, CompactAST.h, SidecarPersistence.h, HeadlessAgentRPCHandler.h (setEnvironment, getEnvironment, validateEnvironment, getLoweringHints RPCs). Tests for steps 284-285 written. Steps 286-289 tests remaining. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -60,7 +60,10 @@ struct AgentPermissionPolicy {
|
||||
method == "loadAnnotatedAST" ||
|
||||
method == "listAnnotatedFiles" ||
|
||||
method == "getSemanticAnnotations" ||
|
||||
method == "getUnannotatedNodes") {
|
||||
method == "getUnannotatedNodes" ||
|
||||
method == "getEnvironment" ||
|
||||
method == "validateEnvironment" ||
|
||||
method == "getLoweringHints") {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -81,7 +84,8 @@ struct AgentPermissionPolicy {
|
||||
method == "redo" ||
|
||||
method == "saveAnnotatedAST" ||
|
||||
method == "setSemanticAnnotation" ||
|
||||
method == "removeSemanticAnnotation") {
|
||||
method == "removeSemanticAnnotation" ||
|
||||
method == "setEnvironment") {
|
||||
return role == AgentRole::Refactor || role == AgentRole::Generator;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include "ast/ASTNode.h"
|
||||
#include "ast/Serialization.h"
|
||||
#include "ast/Annotation.h"
|
||||
#include "ast/HostBoundary.h"
|
||||
#include "EnvironmentSpec.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -319,6 +321,14 @@ inline json extractSemanticSummary(const ASTNode* node) {
|
||||
if (!da->selection.empty()) obj["selection"] = da->selection;
|
||||
if (!obj.empty()) sem["decision"] = obj;
|
||||
}
|
||||
// Environment Layer (Step 285)
|
||||
else if (a->conceptType == "CapabilityRequirement") {
|
||||
auto* cr = static_cast<const CapabilityRequirement*>(a);
|
||||
json obj;
|
||||
if (!cr->capability.empty()) obj["capability"] = cr->capability;
|
||||
obj["required"] = cr->required;
|
||||
sem["capabilityReq"] = obj;
|
||||
}
|
||||
}
|
||||
return sem.empty() ? json() : sem;
|
||||
}
|
||||
@@ -365,6 +375,13 @@ inline std::string getNodeName(const ASTNode* node) {
|
||||
if (ct == "BooleanLiteral")
|
||||
return static_cast<const BooleanLiteral*>(node)->value
|
||||
? "true" : "false";
|
||||
// Host Boundary (Step 288)
|
||||
if (ct == "HostCall")
|
||||
return static_cast<const HostCall*>(node)->name;
|
||||
if (ct == "ModuleLoad")
|
||||
return static_cast<const ModuleLoad*>(node)->moduleName;
|
||||
if (ct == "ScheduleTask")
|
||||
return static_cast<const ScheduleTask*>(node)->queue;
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
237
editor/src/EnvironmentSpec.h
Normal file
237
editor/src/EnvironmentSpec.h
Normal file
@@ -0,0 +1,237 @@
|
||||
#pragma once
|
||||
// Step 284-286: Environment Layer
|
||||
//
|
||||
// EnvironmentSpec — first-class environment model attached to modules.
|
||||
// Capability vocabulary, validation, and environment-aware diagnostics.
|
||||
|
||||
#include "ast/ASTNode.h"
|
||||
#include "ast/Annotation.h"
|
||||
#include "ast/Serialization.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// --- EnvironmentSpec AST Node ---
|
||||
class EnvironmentSpec : public ASTNode {
|
||||
public:
|
||||
std::string envId; // "posix_process", "browser", "jvm", etc.
|
||||
std::string envVersion;
|
||||
std::vector<std::string> capabilities;
|
||||
std::vector<std::string> constraints;
|
||||
std::string scheduler; // "event_loop"|"threads"|"fibers"|"coroutines"|"single_thread"
|
||||
std::string memory; // "manual"|"raii"|"refcount"|"tracing_gc"|"region"
|
||||
json bindingTimes; // per-feature: "compile"|"link"|"load"|"runtime"
|
||||
std::string exceptions; // "unwind"|"checked"|"typed"|"untyped"
|
||||
std::string ffi; // "c_abi"|"dynamic_linking"|"none"
|
||||
|
||||
EnvironmentSpec() {
|
||||
conceptType = "EnvironmentSpec";
|
||||
bindingTimes = json::object();
|
||||
}
|
||||
};
|
||||
|
||||
// --- CapabilityRequirement Annotation ---
|
||||
class CapabilityRequirement : public Annotation {
|
||||
public:
|
||||
std::string capability;
|
||||
bool required = true;
|
||||
CapabilityRequirement() { conceptType = "CapabilityRequirement"; }
|
||||
};
|
||||
|
||||
// --- Capability Vocabulary ---
|
||||
inline const std::set<std::string>& knownCapabilities() {
|
||||
static const std::set<std::string> caps = {
|
||||
"io.fs", "io.net", "io.stdinout",
|
||||
"clock.time", "timers",
|
||||
"event_loop", "threads", "process", "signals",
|
||||
"modules", "dynamic_loading", "reflection",
|
||||
"gc", "jit",
|
||||
"ui.dom", "editor.emacs", "host.imgui"
|
||||
};
|
||||
return caps;
|
||||
}
|
||||
|
||||
inline bool isKnownCapability(const std::string& cap) {
|
||||
return knownCapabilities().count(cap) > 0;
|
||||
}
|
||||
|
||||
// --- Capability Validation ---
|
||||
struct CapabilityDiagnostic {
|
||||
std::string severity; // "error" | "warning"
|
||||
std::string message;
|
||||
std::string nodeId;
|
||||
std::string capability;
|
||||
};
|
||||
|
||||
inline std::vector<CapabilityDiagnostic> validateCapabilities(
|
||||
const ASTNode* root, const EnvironmentSpec* env)
|
||||
{
|
||||
std::vector<CapabilityDiagnostic> diags;
|
||||
if (!root || !env) return diags;
|
||||
|
||||
std::set<std::string> envCaps(env->capabilities.begin(), env->capabilities.end());
|
||||
|
||||
// Collect all CapabilityRequirements from the tree
|
||||
std::vector<const ASTNode*> toVisit = {root};
|
||||
while (!toVisit.empty()) {
|
||||
const ASTNode* node = toVisit.back();
|
||||
toVisit.pop_back();
|
||||
for (const auto* child : node->allChildren()) {
|
||||
if (child->conceptType == "CapabilityRequirement") {
|
||||
auto* cr = static_cast<const CapabilityRequirement*>(child);
|
||||
if (cr->required && envCaps.find(cr->capability) == envCaps.end()) {
|
||||
diags.push_back({
|
||||
"error",
|
||||
"E0501: Unmet capability requirement: " + cr->capability,
|
||||
node->id,
|
||||
cr->capability
|
||||
});
|
||||
}
|
||||
}
|
||||
toVisit.push_back(child);
|
||||
}
|
||||
}
|
||||
return diags;
|
||||
}
|
||||
|
||||
// --- Environment-Incompatible Annotation Diagnostics (Step 286) ---
|
||||
inline std::vector<CapabilityDiagnostic> validateEnvAnnotations(
|
||||
const ASTNode* root, const EnvironmentSpec* env)
|
||||
{
|
||||
std::vector<CapabilityDiagnostic> diags;
|
||||
if (!root || !env) return diags;
|
||||
|
||||
std::vector<const ASTNode*> toVisit = {root};
|
||||
while (!toVisit.empty()) {
|
||||
const ASTNode* node = toVisit.back();
|
||||
toVisit.pop_back();
|
||||
for (const auto* child : node->allChildren()) {
|
||||
// ParallelAnnotation(data) in single_thread env
|
||||
if (child->conceptType == "ParallelAnnotation" &&
|
||||
env->scheduler == "single_thread") {
|
||||
auto* pa = static_cast<const ParallelAnnotation*>(child);
|
||||
diags.push_back({
|
||||
"error",
|
||||
"E0502: @Parallel(" + pa->kind + ") incompatible with single_thread scheduler",
|
||||
node->id, ""
|
||||
});
|
||||
}
|
||||
// ThreadModelAnnotation(os) in single_thread env
|
||||
if (child->conceptType == "ThreadModelAnnotation" &&
|
||||
env->scheduler == "single_thread") {
|
||||
diags.push_back({
|
||||
"error",
|
||||
"E0503: @ThreadModel incompatible with single_thread scheduler",
|
||||
node->id, ""
|
||||
});
|
||||
}
|
||||
// AtomicAnnotation in single_thread env (warning, not error)
|
||||
if (child->conceptType == "AtomicAnnotation" &&
|
||||
env->scheduler == "single_thread") {
|
||||
diags.push_back({
|
||||
"warning",
|
||||
"E0504: @Atomic unnecessary in single_thread environment",
|
||||
node->id, ""
|
||||
});
|
||||
}
|
||||
// ExecAnnotation(async) requires event_loop, threads, fibers, or coroutines
|
||||
if (child->conceptType == "ExecAnnotation") {
|
||||
auto* ea = static_cast<const ExecAnnotation*>(child);
|
||||
if (ea->mode == "async" && env->scheduler == "single_thread") {
|
||||
diags.push_back({
|
||||
"error",
|
||||
"E0505: @Exec(async) incompatible with single_thread scheduler",
|
||||
node->id, ""
|
||||
});
|
||||
}
|
||||
}
|
||||
toVisit.push_back(child);
|
||||
}
|
||||
}
|
||||
return diags;
|
||||
}
|
||||
|
||||
// --- Environment-Aware Lowering Hints (Step 287) ---
|
||||
struct LoweringHint {
|
||||
std::string pattern; // e.g. "callback", "std_async", "try_catch"
|
||||
std::string description;
|
||||
};
|
||||
|
||||
inline std::vector<LoweringHint> getLoweringHints(
|
||||
const ASTNode* node, const EnvironmentSpec* env)
|
||||
{
|
||||
std::vector<LoweringHint> hints;
|
||||
if (!node || !env) return hints;
|
||||
|
||||
for (const auto* child : node->getChildren("annotations")) {
|
||||
if (child->conceptType == "ExecAnnotation") {
|
||||
auto* ea = static_cast<const ExecAnnotation*>(child);
|
||||
if (ea->mode == "async") {
|
||||
if (env->scheduler == "event_loop")
|
||||
hints.push_back({"callback", "async → callback-based pattern"});
|
||||
else if (env->scheduler == "threads")
|
||||
hints.push_back({"std_async", "async → std::async/std::future"});
|
||||
else if (env->scheduler == "coroutines")
|
||||
hints.push_back({"coroutine", "async → co_await coroutine"});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Memory model hints
|
||||
if (env->memory == "tracing_gc")
|
||||
hints.push_back({"suppress_dealloc", "GC env → suppress explicit delete"});
|
||||
else if (env->memory == "manual")
|
||||
hints.push_back({"explicit_delete", "manual env → inject explicit delete"});
|
||||
else if (env->memory == "raii")
|
||||
hints.push_back({"raii_cleanup", "RAII env → destructor-based cleanup"});
|
||||
|
||||
// Exception model hints
|
||||
if (env->exceptions == "unwind")
|
||||
hints.push_back({"try_catch", "unwind → try/catch blocks"});
|
||||
else if (env->exceptions == "typed")
|
||||
hints.push_back({"expected", "typed → std::expected return types"});
|
||||
else if (env->exceptions == "checked")
|
||||
hints.push_back({"checked_throw", "checked → explicit throw declarations"});
|
||||
|
||||
return hints;
|
||||
}
|
||||
|
||||
// --- EnvironmentSpec JSON serialization helpers ---
|
||||
inline json envSpecToJson(const EnvironmentSpec* env) {
|
||||
if (!env) return json();
|
||||
json j;
|
||||
j["envId"] = env->envId;
|
||||
if (!env->envVersion.empty()) j["envVersion"] = env->envVersion;
|
||||
if (!env->capabilities.empty()) j["capabilities"] = env->capabilities;
|
||||
if (!env->constraints.empty()) j["constraints"] = env->constraints;
|
||||
if (!env->scheduler.empty()) j["scheduler"] = env->scheduler;
|
||||
if (!env->memory.empty()) j["memory"] = env->memory;
|
||||
if (!env->bindingTimes.empty()) j["bindingTimes"] = env->bindingTimes;
|
||||
if (!env->exceptions.empty()) j["exceptions"] = env->exceptions;
|
||||
if (!env->ffi.empty()) j["ffi"] = env->ffi;
|
||||
return j;
|
||||
}
|
||||
|
||||
inline void envSpecFromJson(EnvironmentSpec* env, const json& j) {
|
||||
if (!env) return;
|
||||
if (j.contains("envId")) env->envId = j["envId"].get<std::string>();
|
||||
if (j.contains("envVersion")) env->envVersion = j["envVersion"].get<std::string>();
|
||||
if (j.contains("capabilities") && j["capabilities"].is_array()) {
|
||||
env->capabilities.clear();
|
||||
for (const auto& c : j["capabilities"])
|
||||
if (c.is_string()) env->capabilities.push_back(c.get<std::string>());
|
||||
}
|
||||
if (j.contains("constraints") && j["constraints"].is_array()) {
|
||||
env->constraints.clear();
|
||||
for (const auto& c : j["constraints"])
|
||||
if (c.is_string()) env->constraints.push_back(c.get<std::string>());
|
||||
}
|
||||
if (j.contains("scheduler")) env->scheduler = j["scheduler"].get<std::string>();
|
||||
if (j.contains("memory")) env->memory = j["memory"].get<std::string>();
|
||||
if (j.contains("bindingTimes")) env->bindingTimes = j["bindingTimes"];
|
||||
if (j.contains("exceptions")) env->exceptions = j["exceptions"].get<std::string>();
|
||||
if (j.contains("ffi")) env->ffi = j["ffi"].get<std::string>();
|
||||
}
|
||||
@@ -1716,6 +1716,8 @@ inline json handleHeadlessAgentRequest(HeadlessEditorState& state,
|
||||
else if (type == "tradeoff") conceptType = "TradeoffAnnotation";
|
||||
else if (type == "choice") conceptType = "ChoiceAnnotation";
|
||||
else if (type == "decision") conceptType = "DecisionAnnotation";
|
||||
// Environment Layer (Step 285)
|
||||
else if (type == "capabilityRequirement") conceptType = "CapabilityRequirement";
|
||||
else return headlessRpcError(id, -32602, "Unknown annotation type: " + type);
|
||||
|
||||
// Remove existing annotation of same type (update semantics)
|
||||
@@ -1970,5 +1972,118 @@ inline json handleHeadlessAgentRequest(HeadlessEditorState& state,
|
||||
{{"nodes", nodes}, {"count", (int)nodes.size()}});
|
||||
}
|
||||
|
||||
// --- setEnvironment ---
|
||||
if (method == "setEnvironment") {
|
||||
if (!AgentPermissionPolicy::canInvoke(role, method))
|
||||
return headlessRpcError(id, -32031, "Role not permitted");
|
||||
auto params = request.contains("params") ? request["params"]
|
||||
: json::object();
|
||||
if (!state.activeBuffer)
|
||||
return headlessRpcError(id, -32602, "No active buffer");
|
||||
Module* ast = state.activeAST();
|
||||
if (!ast)
|
||||
return headlessRpcError(id, -32602, "No AST available");
|
||||
|
||||
// Remove existing EnvironmentSpec if any
|
||||
for (auto* child : ast->getChildren("environment")) {
|
||||
ast->removeChild(child);
|
||||
break;
|
||||
}
|
||||
|
||||
auto* env = new EnvironmentSpec();
|
||||
envSpecFromJson(env, params);
|
||||
ast->addChild("environment", env);
|
||||
|
||||
return headlessRpcResult(id, {{"success", true},
|
||||
{"envId", env->envId}});
|
||||
}
|
||||
|
||||
// --- getEnvironment ---
|
||||
if (method == "getEnvironment") {
|
||||
if (!AgentPermissionPolicy::canInvoke(role, method))
|
||||
return headlessRpcError(id, -32031, "Role not permitted");
|
||||
if (!state.activeBuffer)
|
||||
return headlessRpcError(id, -32602, "No active buffer");
|
||||
Module* ast = state.activeAST();
|
||||
if (!ast)
|
||||
return headlessRpcError(id, -32602, "No AST available");
|
||||
|
||||
auto envChildren = ast->getChildren("environment");
|
||||
if (envChildren.empty())
|
||||
return headlessRpcResult(id, {{"hasEnvironment", false}});
|
||||
|
||||
auto* env = static_cast<EnvironmentSpec*>(envChildren[0]);
|
||||
json envJson = envSpecToJson(env);
|
||||
envJson["hasEnvironment"] = true;
|
||||
return headlessRpcResult(id, envJson);
|
||||
}
|
||||
|
||||
// --- validateEnvironment ---
|
||||
if (method == "validateEnvironment") {
|
||||
if (!AgentPermissionPolicy::canInvoke(role, method))
|
||||
return headlessRpcError(id, -32031, "Role not permitted");
|
||||
if (!state.activeBuffer)
|
||||
return headlessRpcError(id, -32602, "No active buffer");
|
||||
Module* ast = state.activeAST();
|
||||
if (!ast)
|
||||
return headlessRpcError(id, -32602, "No AST available");
|
||||
|
||||
auto envChildren = ast->getChildren("environment");
|
||||
if (envChildren.empty())
|
||||
return headlessRpcError(id, -32602, "No EnvironmentSpec set on module");
|
||||
|
||||
auto* env = static_cast<EnvironmentSpec*>(envChildren[0]);
|
||||
auto capDiags = validateCapabilities(ast, env);
|
||||
auto annoDiags = validateEnvAnnotations(ast, env);
|
||||
|
||||
json diagsJson = json::array();
|
||||
for (const auto& d : capDiags) {
|
||||
diagsJson.push_back({{"severity", d.severity},
|
||||
{"message", d.message}, {"nodeId", d.nodeId},
|
||||
{"capability", d.capability}});
|
||||
}
|
||||
for (const auto& d : annoDiags) {
|
||||
diagsJson.push_back({{"severity", d.severity},
|
||||
{"message", d.message}, {"nodeId", d.nodeId}});
|
||||
}
|
||||
|
||||
return headlessRpcResult(id, {
|
||||
{"diagnostics", diagsJson},
|
||||
{"count", (int)diagsJson.size()}
|
||||
});
|
||||
}
|
||||
|
||||
// --- getLoweringHints ---
|
||||
if (method == "getLoweringHints") {
|
||||
if (!AgentPermissionPolicy::canInvoke(role, method))
|
||||
return headlessRpcError(id, -32031, "Role not permitted");
|
||||
auto params = request.contains("params") ? request["params"]
|
||||
: json::object();
|
||||
std::string nodeId = params.value("nodeId", "");
|
||||
if (!state.activeBuffer)
|
||||
return headlessRpcError(id, -32602, "No active buffer");
|
||||
Module* ast = state.activeAST();
|
||||
if (!ast)
|
||||
return headlessRpcError(id, -32602, "No AST available");
|
||||
|
||||
auto envChildren = ast->getChildren("environment");
|
||||
if (envChildren.empty())
|
||||
return headlessRpcError(id, -32602, "No EnvironmentSpec set");
|
||||
|
||||
auto* env = static_cast<EnvironmentSpec*>(envChildren[0]);
|
||||
ASTNode* target = nodeId.empty() ? ast : findNodeById(ast, nodeId);
|
||||
if (!target)
|
||||
return headlessRpcError(id, -32602, "Node not found: " + nodeId);
|
||||
|
||||
auto hints = getLoweringHints(target, env);
|
||||
json hintsJson = json::array();
|
||||
for (const auto& h : hints) {
|
||||
hintsJson.push_back({{"pattern", h.pattern},
|
||||
{"description", h.description}});
|
||||
}
|
||||
return headlessRpcResult(id, {{"hints", hintsJson},
|
||||
{"count", (int)hintsJson.size()}});
|
||||
}
|
||||
|
||||
return headlessRpcError(id, -32601, "Method not found");
|
||||
}
|
||||
|
||||
@@ -91,7 +91,9 @@ inline bool isSemanticAnnotation(const std::string& conceptType) {
|
||||
conceptType == "CandidateAnnotation" ||
|
||||
conceptType == "TradeoffAnnotation" ||
|
||||
conceptType == "ChoiceAnnotation" ||
|
||||
conceptType == "DecisionAnnotation";
|
||||
conceptType == "DecisionAnnotation" ||
|
||||
// Environment Layer (Step 285)
|
||||
conceptType == "CapabilityRequirement";
|
||||
}
|
||||
|
||||
// --- Count semantic annotations in an AST ---
|
||||
|
||||
39
editor/src/ast/HostBoundary.h
Normal file
39
editor/src/ast/HostBoundary.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
// Step 288: Host Boundary AST Nodes
|
||||
//
|
||||
// Explicit nodes for host-environment interactions:
|
||||
// HostCall, ScheduleTask, ModuleLoad
|
||||
|
||||
#include "ASTNode.h"
|
||||
#include "Expression.h"
|
||||
#include "Statement.h"
|
||||
#include <string>
|
||||
|
||||
// HostCall — capability-namespaced host function call
|
||||
class HostCall : public Expression {
|
||||
public:
|
||||
std::string capability; // e.g. "io.fs", "io.net"
|
||||
std::string name; // e.g. "readFile", "listen"
|
||||
|
||||
HostCall() { conceptType = "HostCall"; }
|
||||
HostCall(const std::string& cap, const std::string& n)
|
||||
: capability(cap), name(n) { conceptType = "HostCall"; }
|
||||
};
|
||||
|
||||
// ScheduleTask — dispatches work to a queue
|
||||
class ScheduleTask : public Statement {
|
||||
public:
|
||||
std::string queue; // "microtask" | "macrotask" | "thread"
|
||||
|
||||
ScheduleTask() { conceptType = "ScheduleTask"; }
|
||||
// Body statements via children role "body"
|
||||
};
|
||||
|
||||
// ModuleLoad — static or dynamic module import
|
||||
class ModuleLoad : public Statement {
|
||||
public:
|
||||
std::string moduleName;
|
||||
std::string mode; // "static" | "dynamic"
|
||||
|
||||
ModuleLoad() { conceptType = "ModuleLoad"; }
|
||||
};
|
||||
@@ -9,6 +9,8 @@
|
||||
#include "Expression.h"
|
||||
#include "Type.h"
|
||||
#include "Annotation.h"
|
||||
#include "HostBoundary.h"
|
||||
#include "../EnvironmentSpec.h"
|
||||
#include "Import.h"
|
||||
#include "ExternalModule.h"
|
||||
#include "TypeSignature.h"
|
||||
@@ -395,6 +397,27 @@ inline json propertiesToJson(const ASTNode* node) {
|
||||
if (!n->author.empty()) props["author"] = n->author;
|
||||
if (!n->reason.empty()) props["reason"] = n->reason;
|
||||
}
|
||||
// Host Boundary (Step 288)
|
||||
else if (ct == "HostCall") {
|
||||
auto* n = static_cast<const HostCall*>(node);
|
||||
if (!n->capability.empty()) props["capability"] = n->capability;
|
||||
if (!n->name.empty()) props["name"] = n->name;
|
||||
}
|
||||
else if (ct == "ScheduleTask") {
|
||||
auto* n = static_cast<const ScheduleTask*>(node);
|
||||
if (!n->queue.empty()) props["queue"] = n->queue;
|
||||
}
|
||||
else if (ct == "ModuleLoad") {
|
||||
auto* n = static_cast<const ModuleLoad*>(node);
|
||||
if (!n->moduleName.empty()) props["moduleName"] = n->moduleName;
|
||||
if (!n->mode.empty()) props["mode"] = n->mode;
|
||||
}
|
||||
// Environment Layer (Step 284-285)
|
||||
else if (ct == "CapabilityRequirement") {
|
||||
auto* n = static_cast<const CapabilityRequirement*>(node);
|
||||
if (!n->capability.empty()) props["capability"] = n->capability;
|
||||
props["required"] = n->required;
|
||||
}
|
||||
// NullLiteral, ListLiteral, IndexAccess, Block, Assignment, IfStatement,
|
||||
// WhileLoop, Return, ExpressionStatement, ListType, SetType, MapType,
|
||||
// TupleType, ArrayType, OptionalType — no extra properties
|
||||
@@ -535,6 +558,13 @@ inline ASTNode* createNode(const std::string& conceptName) {
|
||||
if (conceptName == "TradeoffAnnotation") return new TradeoffAnnotation();
|
||||
if (conceptName == "ChoiceAnnotation") return new ChoiceAnnotation();
|
||||
if (conceptName == "DecisionAnnotation") return new DecisionAnnotation();
|
||||
// Host Boundary (Step 288)
|
||||
if (conceptName == "HostCall") return new HostCall();
|
||||
if (conceptName == "ScheduleTask") return new ScheduleTask();
|
||||
if (conceptName == "ModuleLoad") return new ModuleLoad();
|
||||
// Environment Layer (Step 284-285)
|
||||
if (conceptName == "EnvironmentSpec") return new EnvironmentSpec();
|
||||
if (conceptName == "CapabilityRequirement") return new CapabilityRequirement();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -939,6 +969,27 @@ inline void setPropertiesFromJson(ASTNode* node, const json& props) {
|
||||
if (props.contains("author")) n->author = props["author"].get<std::string>();
|
||||
if (props.contains("reason")) n->reason = props["reason"].get<std::string>();
|
||||
}
|
||||
// Host Boundary (Step 288)
|
||||
else if (ct == "HostCall") {
|
||||
auto* n = static_cast<HostCall*>(node);
|
||||
if (props.contains("capability")) n->capability = props["capability"].get<std::string>();
|
||||
if (props.contains("name")) n->name = props["name"].get<std::string>();
|
||||
}
|
||||
else if (ct == "ScheduleTask") {
|
||||
auto* n = static_cast<ScheduleTask*>(node);
|
||||
if (props.contains("queue")) n->queue = props["queue"].get<std::string>();
|
||||
}
|
||||
else if (ct == "ModuleLoad") {
|
||||
auto* n = static_cast<ModuleLoad*>(node);
|
||||
if (props.contains("moduleName")) n->moduleName = props["moduleName"].get<std::string>();
|
||||
if (props.contains("mode")) n->mode = props["mode"].get<std::string>();
|
||||
}
|
||||
// Environment Layer (Step 284-285)
|
||||
else if (ct == "CapabilityRequirement") {
|
||||
auto* n = static_cast<CapabilityRequirement*>(node);
|
||||
if (props.contains("capability")) n->capability = props["capability"].get<std::string>();
|
||||
if (props.contains("required")) n->required = props["required"].get<bool>();
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string generateNodeId() {
|
||||
|
||||
220
editor/tests/step284_test.cpp
Normal file
220
editor/tests/step284_test.cpp
Normal file
@@ -0,0 +1,220 @@
|
||||
// Step 284: EnvironmentSpec Schema & AST Node (12 tests)
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include "ast/ASTNode.h"
|
||||
#include "ast/Module.h"
|
||||
#include "ast/Serialization.h"
|
||||
#include "EnvironmentSpec.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
static int passed = 0, failed = 0;
|
||||
static void check(bool c, const std::string& n) {
|
||||
if (c) { std::cout << " PASS: " << n << "\n"; ++passed; }
|
||||
else { std::cout << " FAIL: " << n << "\n"; ++failed; }
|
||||
}
|
||||
|
||||
// Test 1: EnvironmentSpec default construction
|
||||
static void test_env_defaults() {
|
||||
EnvironmentSpec env;
|
||||
check(env.conceptType == "EnvironmentSpec", "conceptType is EnvironmentSpec");
|
||||
check(env.envId.empty(), "envId default empty");
|
||||
check(env.capabilities.empty(), "capabilities default empty");
|
||||
check(env.scheduler.empty(), "scheduler default empty");
|
||||
check(env.memory.empty(), "memory default empty");
|
||||
check(env.bindingTimes.is_object(), "bindingTimes default is json object");
|
||||
}
|
||||
|
||||
// Test 2: EnvironmentSpec field assignment
|
||||
static void test_env_fields() {
|
||||
EnvironmentSpec env;
|
||||
env.envId = "posix_process";
|
||||
env.envVersion = "4.18";
|
||||
env.capabilities = {"io.fs", "io.net", "threads"};
|
||||
env.constraints = {"no_jit"};
|
||||
env.scheduler = "threads";
|
||||
env.memory = "manual";
|
||||
env.exceptions = "unwind";
|
||||
env.ffi = "c_abi";
|
||||
env.bindingTimes = {{"types", "compile"}, {"dispatch", "runtime"}};
|
||||
|
||||
check(env.envId == "posix_process", "envId set");
|
||||
check(env.capabilities.size() == 3, "3 capabilities");
|
||||
check(env.scheduler == "threads", "scheduler set");
|
||||
check(env.memory == "manual", "memory set");
|
||||
check(env.exceptions == "unwind", "exceptions set");
|
||||
check(env.ffi == "c_abi", "ffi set");
|
||||
}
|
||||
|
||||
// Test 3: EnvironmentSpec JSON roundtrip via envSpecToJson/envSpecFromJson
|
||||
static void test_env_json_roundtrip() {
|
||||
EnvironmentSpec env1;
|
||||
env1.envId = "browser";
|
||||
env1.envVersion = "es2022";
|
||||
env1.capabilities = {"ui.dom", "io.net", "event_loop"};
|
||||
env1.scheduler = "event_loop";
|
||||
env1.memory = "tracing_gc";
|
||||
env1.exceptions = "untyped";
|
||||
env1.ffi = "none";
|
||||
env1.bindingTimes = {{"modules", "load"}};
|
||||
|
||||
json j = envSpecToJson(&env1);
|
||||
check(j["envId"] == "browser", "JSON envId");
|
||||
check(j["scheduler"] == "event_loop", "JSON scheduler");
|
||||
check(j["capabilities"].size() == 3, "JSON capabilities count");
|
||||
|
||||
EnvironmentSpec env2;
|
||||
envSpecFromJson(&env2, j);
|
||||
check(env2.envId == "browser", "roundtrip envId");
|
||||
check(env2.scheduler == "event_loop", "roundtrip scheduler");
|
||||
check(env2.memory == "tracing_gc", "roundtrip memory");
|
||||
check(env2.capabilities.size() == 3, "roundtrip capabilities count");
|
||||
}
|
||||
|
||||
// Test 4: EnvironmentSpec attachment to Module
|
||||
static void test_env_attach_to_module() {
|
||||
auto* mod = new Module(); mod->id = "mod1"; mod->name = "test";
|
||||
auto* env = new EnvironmentSpec();
|
||||
env->envId = "jvm";
|
||||
env->scheduler = "threads";
|
||||
mod->addChild("environment", env);
|
||||
|
||||
auto envChildren = mod->getChildren("environment");
|
||||
check(envChildren.size() == 1, "module has 1 environment child");
|
||||
check(envChildren[0]->conceptType == "EnvironmentSpec", "child is EnvironmentSpec");
|
||||
auto* retrieved = static_cast<EnvironmentSpec*>(envChildren[0]);
|
||||
check(retrieved->envId == "jvm", "retrieved envId matches");
|
||||
delete mod;
|
||||
}
|
||||
|
||||
// Test 5: EnvironmentSpec createNode
|
||||
static void test_env_create_node() {
|
||||
ASTNode* node = createNode("EnvironmentSpec");
|
||||
check(node != nullptr, "createNode returns non-null");
|
||||
check(node->conceptType == "EnvironmentSpec", "created node type");
|
||||
delete node;
|
||||
}
|
||||
|
||||
// Test 6: EnvironmentSpec via AST JSON roundtrip (toJson/fromJson)
|
||||
static void test_env_ast_roundtrip() {
|
||||
auto* mod = new Module(); mod->id = "mod1"; mod->name = "envtest";
|
||||
auto* env = new EnvironmentSpec();
|
||||
env->id = "env1";
|
||||
env->envId = "python_cpython";
|
||||
env->scheduler = "event_loop";
|
||||
env->memory = "tracing_gc";
|
||||
mod->addChild("environment", env);
|
||||
|
||||
json j = toJson(mod);
|
||||
check(j["children"].contains("environment"), "JSON has environment children");
|
||||
check(j["children"]["environment"].size() == 1, "1 environment in JSON");
|
||||
|
||||
ASTNode* restored = fromJson(j);
|
||||
check(restored != nullptr, "fromJson returns non-null");
|
||||
auto envKids = restored->getChildren("environment");
|
||||
check(envKids.size() == 1, "restored has 1 environment");
|
||||
check(envKids[0]->conceptType == "EnvironmentSpec", "restored type correct");
|
||||
delete mod;
|
||||
delete restored;
|
||||
}
|
||||
|
||||
// Test 7: envSpecToJson omits empty fields
|
||||
static void test_env_json_sparse() {
|
||||
EnvironmentSpec env;
|
||||
env.envId = "minimal";
|
||||
json j = envSpecToJson(&env);
|
||||
check(j.contains("envId"), "has envId");
|
||||
check(!j.contains("scheduler"), "no scheduler when empty");
|
||||
check(!j.contains("memory"), "no memory when empty");
|
||||
check(!j.contains("capabilities"), "no capabilities when empty");
|
||||
}
|
||||
|
||||
// Test 8: envSpecFromJson handles missing keys gracefully
|
||||
static void test_env_json_partial() {
|
||||
json j = {{"envId", "partial"}, {"scheduler", "fibers"}};
|
||||
EnvironmentSpec env;
|
||||
envSpecFromJson(&env, j);
|
||||
check(env.envId == "partial", "partial envId");
|
||||
check(env.scheduler == "fibers", "partial scheduler");
|
||||
check(env.memory.empty(), "missing memory stays empty");
|
||||
check(env.capabilities.empty(), "missing capabilities stays empty");
|
||||
}
|
||||
|
||||
// Test 9: Multiple EnvironmentSpec values
|
||||
static void test_env_values() {
|
||||
std::vector<std::string> schedulers = {"event_loop", "threads", "fibers", "coroutines", "single_thread"};
|
||||
std::vector<std::string> memories = {"manual", "raii", "refcount", "tracing_gc", "region"};
|
||||
int ok = 0;
|
||||
for (const auto& s : schedulers) {
|
||||
EnvironmentSpec env;
|
||||
env.scheduler = s;
|
||||
json j = envSpecToJson(&env);
|
||||
if (j["scheduler"] == s) ++ok;
|
||||
}
|
||||
for (const auto& m : memories) {
|
||||
EnvironmentSpec env;
|
||||
env.memory = m;
|
||||
json j = envSpecToJson(&env);
|
||||
if (j["memory"] == m) ++ok;
|
||||
}
|
||||
check(ok == 10, "all scheduler/memory values roundtrip");
|
||||
}
|
||||
|
||||
// Test 10: Binding times JSON object
|
||||
static void test_binding_times() {
|
||||
EnvironmentSpec env;
|
||||
env.envId = "bt_test";
|
||||
env.bindingTimes = {{"types", "compile"}, {"dispatch", "runtime"}, {"generics", "link"}};
|
||||
json j = envSpecToJson(&env);
|
||||
check(j["bindingTimes"].size() == 3, "3 binding times");
|
||||
check(j["bindingTimes"]["types"] == "compile", "types binding time");
|
||||
|
||||
EnvironmentSpec env2;
|
||||
envSpecFromJson(&env2, j);
|
||||
check(env2.bindingTimes["dispatch"] == "runtime", "roundtrip dispatch binding");
|
||||
}
|
||||
|
||||
// Test 11: envSpecToJson null safety
|
||||
static void test_null_safety() {
|
||||
json j = envSpecToJson(nullptr);
|
||||
check(j.is_null(), "null env → null json");
|
||||
EnvironmentSpec env;
|
||||
envSpecFromJson(nullptr, {{"envId", "x"}});
|
||||
envSpecFromJson(&env, json());
|
||||
check(env.envId.empty(), "empty json → no change");
|
||||
}
|
||||
|
||||
// Test 12: EnvironmentSpec properties via propertiesToJson
|
||||
static void test_env_properties() {
|
||||
// EnvironmentSpec uses its own envSpecToJson, but propertiesToJson
|
||||
// should at least not crash (returns empty for unknown types handled by the EnvironmentSpec's own serialization)
|
||||
EnvironmentSpec env;
|
||||
env.envId = "test";
|
||||
// propertiesToJson won't have a specific case for EnvironmentSpec (uses envSpecToJson instead)
|
||||
// but it shouldn't crash
|
||||
json props = propertiesToJson(&env);
|
||||
// EnvironmentSpec is not in propertiesToJson dispatch, so props will be empty — that's OK
|
||||
// The test verifies no crash and that envSpecToJson works as the canonical path
|
||||
json envJ = envSpecToJson(&env);
|
||||
check(envJ["envId"] == "test", "envSpecToJson canonical path works");
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "=== Step 284: EnvironmentSpec Schema & AST Node ===\n";
|
||||
test_env_defaults();
|
||||
test_env_fields();
|
||||
test_env_json_roundtrip();
|
||||
test_env_attach_to_module();
|
||||
test_env_create_node();
|
||||
test_env_ast_roundtrip();
|
||||
test_env_json_sparse();
|
||||
test_env_json_partial();
|
||||
test_env_values();
|
||||
test_binding_times();
|
||||
test_null_safety();
|
||||
test_env_properties();
|
||||
std::cout << "\nResults: " << passed << "/" << (passed+failed) << "\n";
|
||||
return failed > 0 ? 1 : 0;
|
||||
}
|
||||
206
editor/tests/step285_test.cpp
Normal file
206
editor/tests/step285_test.cpp
Normal file
@@ -0,0 +1,206 @@
|
||||
// Step 285: Capability Vocabulary & Validation (12 tests)
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include "ast/ASTNode.h"
|
||||
#include "ast/Module.h"
|
||||
#include "ast/Function.h"
|
||||
#include "ast/Serialization.h"
|
||||
#include "EnvironmentSpec.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
static int passed = 0, failed = 0;
|
||||
static void check(bool c, const std::string& n) {
|
||||
if (c) { std::cout << " PASS: " << n << "\n"; ++passed; }
|
||||
else { std::cout << " FAIL: " << n << "\n"; ++failed; }
|
||||
}
|
||||
|
||||
// Test 1: knownCapabilities returns expected set
|
||||
static void test_known_capabilities() {
|
||||
const auto& caps = knownCapabilities();
|
||||
check(caps.count("io.fs") > 0, "io.fs is known");
|
||||
check(caps.count("io.net") > 0, "io.net is known");
|
||||
check(caps.count("threads") > 0, "threads is known");
|
||||
check(caps.count("event_loop") > 0, "event_loop is known");
|
||||
check(caps.count("gc") > 0, "gc is known");
|
||||
check(caps.count("ui.dom") > 0, "ui.dom is known");
|
||||
check(caps.size() >= 17, "at least 17 capabilities");
|
||||
}
|
||||
|
||||
// Test 2: isKnownCapability checks
|
||||
static void test_is_known_capability() {
|
||||
check(isKnownCapability("io.fs"), "io.fs recognized");
|
||||
check(isKnownCapability("threads"), "threads recognized");
|
||||
check(!isKnownCapability("quantum_computing"), "quantum_computing not known");
|
||||
check(!isKnownCapability(""), "empty string not known");
|
||||
}
|
||||
|
||||
// Test 3: CapabilityRequirement construction
|
||||
static void test_cap_requirement() {
|
||||
CapabilityRequirement cr;
|
||||
check(cr.conceptType == "CapabilityRequirement", "conceptType");
|
||||
check(cr.required == true, "default required is true");
|
||||
check(cr.capability.empty(), "default capability empty");
|
||||
|
||||
cr.capability = "io.fs";
|
||||
cr.required = true;
|
||||
check(cr.capability == "io.fs", "capability set");
|
||||
}
|
||||
|
||||
// Test 4: CapabilityRequirement createNode
|
||||
static void test_cap_requirement_create_node() {
|
||||
ASTNode* node = createNode("CapabilityRequirement");
|
||||
check(node != nullptr, "createNode non-null");
|
||||
check(node->conceptType == "CapabilityRequirement", "correct type");
|
||||
delete node;
|
||||
}
|
||||
|
||||
// Test 5: CapabilityRequirement JSON roundtrip
|
||||
static void test_cap_requirement_roundtrip() {
|
||||
auto* cr = new CapabilityRequirement();
|
||||
cr->id = "cr1";
|
||||
cr->capability = "io.net";
|
||||
cr->required = true;
|
||||
|
||||
json j = toJson(cr);
|
||||
check(j["concept"] == "CapabilityRequirement", "JSON concept");
|
||||
check(j["properties"]["capability"] == "io.net", "JSON capability");
|
||||
check(j["properties"]["required"] == true, "JSON required");
|
||||
|
||||
ASTNode* restored = fromJson(j);
|
||||
check(restored != nullptr, "fromJson non-null");
|
||||
auto* rcr = static_cast<CapabilityRequirement*>(restored);
|
||||
check(rcr->capability == "io.net", "roundtrip capability");
|
||||
check(rcr->required == true, "roundtrip required");
|
||||
delete cr;
|
||||
delete restored;
|
||||
}
|
||||
|
||||
// Test 6: validateCapabilities — all met
|
||||
static void test_validate_all_met() {
|
||||
auto* mod = new Module(); mod->id = "m1"; mod->name = "test";
|
||||
auto* fn = new Function(); fn->id = "f1"; fn->name = "readFile";
|
||||
auto* cr = new CapabilityRequirement();
|
||||
cr->capability = "io.fs"; cr->required = true;
|
||||
fn->addChild("annotations", cr);
|
||||
mod->addChild("functions", fn);
|
||||
|
||||
EnvironmentSpec env;
|
||||
env.envId = "posix";
|
||||
env.capabilities = {"io.fs", "io.net", "threads"};
|
||||
|
||||
auto diags = validateCapabilities(mod, &env);
|
||||
check(diags.empty(), "no diagnostics when all caps met");
|
||||
delete mod;
|
||||
}
|
||||
|
||||
// Test 7: validateCapabilities — unmet requirement
|
||||
static void test_validate_unmet() {
|
||||
auto* mod = new Module(); mod->id = "m1"; mod->name = "test";
|
||||
auto* fn = new Function(); fn->id = "f1"; fn->name = "useGPU";
|
||||
auto* cr = new CapabilityRequirement();
|
||||
cr->capability = "gpu.compute"; cr->required = true;
|
||||
fn->addChild("annotations", cr);
|
||||
mod->addChild("functions", fn);
|
||||
|
||||
EnvironmentSpec env;
|
||||
env.envId = "basic";
|
||||
env.capabilities = {"io.fs"};
|
||||
|
||||
auto diags = validateCapabilities(mod, &env);
|
||||
check(diags.size() == 1, "1 diagnostic for unmet cap");
|
||||
check(diags[0].severity == "error", "severity is error");
|
||||
check(diags[0].message.find("E0501") != std::string::npos, "E0501 code");
|
||||
check(diags[0].capability == "gpu.compute", "diagnostic has capability");
|
||||
delete mod;
|
||||
}
|
||||
|
||||
// Test 8: validateCapabilities — multiple unmet
|
||||
static void test_validate_multiple_unmet() {
|
||||
auto* mod = new Module(); mod->id = "m1"; mod->name = "test";
|
||||
auto* fn1 = new Function(); fn1->id = "f1"; fn1->name = "serve";
|
||||
auto* cr1 = new CapabilityRequirement(); cr1->capability = "io.net";
|
||||
fn1->addChild("annotations", cr1);
|
||||
auto* fn2 = new Function(); fn2->id = "f2"; fn2->name = "reflectTypes";
|
||||
auto* cr2 = new CapabilityRequirement(); cr2->capability = "reflection";
|
||||
fn2->addChild("annotations", cr2);
|
||||
mod->addChild("functions", fn1);
|
||||
mod->addChild("functions", fn2);
|
||||
|
||||
EnvironmentSpec env;
|
||||
env.envId = "minimal";
|
||||
env.capabilities = {};
|
||||
|
||||
auto diags = validateCapabilities(mod, &env);
|
||||
check(diags.size() == 2, "2 diagnostics for 2 unmet caps");
|
||||
delete mod;
|
||||
}
|
||||
|
||||
// Test 9: validateCapabilities — non-required is OK
|
||||
static void test_validate_optional() {
|
||||
auto* mod = new Module(); mod->id = "m1"; mod->name = "test";
|
||||
auto* fn = new Function(); fn->id = "f1"; fn->name = "maybeNet";
|
||||
auto* cr = new CapabilityRequirement();
|
||||
cr->capability = "io.net"; cr->required = false;
|
||||
fn->addChild("annotations", cr);
|
||||
mod->addChild("functions", fn);
|
||||
|
||||
EnvironmentSpec env;
|
||||
env.envId = "basic";
|
||||
env.capabilities = {};
|
||||
|
||||
auto diags = validateCapabilities(mod, &env);
|
||||
check(diags.empty(), "optional requirement → no diagnostic");
|
||||
delete mod;
|
||||
}
|
||||
|
||||
// Test 10: validateCapabilities null safety
|
||||
static void test_validate_null() {
|
||||
auto diags1 = validateCapabilities(nullptr, nullptr);
|
||||
check(diags1.empty(), "null root → empty");
|
||||
|
||||
auto* mod = new Module(); mod->id = "m1";
|
||||
auto diags2 = validateCapabilities(mod, nullptr);
|
||||
check(diags2.empty(), "null env → empty");
|
||||
delete mod;
|
||||
}
|
||||
|
||||
// Test 11: CapabilityRequirement in isSemanticAnnotation
|
||||
static void test_cap_is_semantic() {
|
||||
check(isSemanticAnnotation("CapabilityRequirement"),
|
||||
"CapabilityRequirement is semantic annotation");
|
||||
}
|
||||
|
||||
// Test 12: CapabilityRequirement in compact AST semantic summary
|
||||
static void test_cap_compact_summary() {
|
||||
auto* fn = new Function(); fn->id = "f1"; fn->name = "netOp";
|
||||
auto* cr = new CapabilityRequirement();
|
||||
cr->capability = "io.net"; cr->required = true;
|
||||
fn->addChild("annotations", cr);
|
||||
|
||||
json sem = extractSemanticSummary(fn);
|
||||
check(sem.contains("capabilityReq"), "semantic has capabilityReq");
|
||||
check(sem["capabilityReq"]["capability"] == "io.net", "capabilityReq.capability");
|
||||
check(sem["capabilityReq"]["required"] == true, "capabilityReq.required");
|
||||
delete fn;
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "=== Step 285: Capability Vocabulary & Validation ===\n";
|
||||
test_known_capabilities();
|
||||
test_is_known_capability();
|
||||
test_cap_requirement();
|
||||
test_cap_requirement_create_node();
|
||||
test_cap_requirement_roundtrip();
|
||||
test_validate_all_met();
|
||||
test_validate_unmet();
|
||||
test_validate_multiple_unmet();
|
||||
test_validate_optional();
|
||||
test_validate_null();
|
||||
test_cap_is_semantic();
|
||||
test_cap_compact_summary();
|
||||
std::cout << "\nResults: " << passed << "/" << (passed+failed) << "\n";
|
||||
return failed > 0 ? 1 : 0;
|
||||
}
|
||||
87
progress.md
87
progress.md
@@ -682,3 +682,90 @@ and redos, and that saveAllBuffers respects mixed undo states across buffers.
|
||||
- Save + undo/redo integration validated end-to-end
|
||||
- 34 MCP tools total
|
||||
- Sprint 9 complete: all 5 phases pass (244/244 tests across steps 245–265)
|
||||
|
||||
---
|
||||
|
||||
# Sprint 10 Progress — Semantic Annotation Taxonomy & Environment Layer
|
||||
|
||||
## Phase 10a: Semantic Annotation Core + Sidecar Persistence (Steps 266-268)
|
||||
|
||||
**Status:** PASS (32/32 tests) — committed as `976161d`
|
||||
|
||||
5 semantic annotation types (IntentAnnotation, ComplexityAnnotation, RiskAnnotation, ContractAnnotation, SemanticTagAnnotation) with JSON roundtrip, compact AST semantic summary, and sidecar persistence (.whetstone/<file>.ast.json).
|
||||
|
||||
**Key files:**
|
||||
- `editor/src/ast/Annotation.h` — 5 annotation class definitions
|
||||
- `editor/src/ast/Serialization.h` — propertiesToJson/createNode/setPropertiesFromJson
|
||||
- `editor/src/CompactAST.h` — extractSemanticSummary()
|
||||
- `editor/src/SidecarPersistence.h` — sidecarPath, save/load, isSemanticAnnotation
|
||||
|
||||
---
|
||||
|
||||
## Phase 10b: Annotation Agent Interface (Steps 269-271)
|
||||
|
||||
**Status:** PASS (32/32 tests) — committed as `a049d60`
|
||||
|
||||
High-level RPC methods for agent annotation workflow: setSemanticAnnotation, getSemanticAnnotations, removeSemanticAnnotation, getUnannotatedNodes. MCP prompt templates for annotation guidance.
|
||||
|
||||
**Key additions:**
|
||||
- `HeadlessAgentRPCHandler.h` — 4 new RPC methods
|
||||
- `MCPServer.h` — 4 MCP tools + 5 annotation prompt templates
|
||||
- `AgentPermissionPolicy.h` — read/write permission mapping
|
||||
|
||||
---
|
||||
|
||||
## Phase 10c: Type System, Execution & Scope Annotations (Steps 272-277)
|
||||
|
||||
**Status:** PASS (171/171 tests) — committed as `e2d1872`
|
||||
|
||||
24 new annotation classes across Subjects 2-4:
|
||||
- **Subject 2 (Type System):** BitWidth, Endian, Layout, Nullability, Variance, Identity, Mut, TypeState
|
||||
- **Subject 3 (Concurrency):** Atomic, Sync, ThreadModel, MemoryBarrier, Exec, Blocking, Parallel, Trap, Exception, Panic
|
||||
- **Subject 4 (Scope):** Binding, Lookup, Capture, Visibility, Namespace, Scope
|
||||
|
||||
All wired through Serialization.h (3 dispatch points), CompactAST.h, SidecarPersistence.h, and HeadlessAgentRPCHandler.h setSemanticAnnotation type mapping.
|
||||
|
||||
---
|
||||
|
||||
## Phase 10d: Shims, Optimization, Meta-Programming & Policy Annotations (Steps 278-283)
|
||||
|
||||
**Status:** PASS (117/117 tests) — committed as `090320f`
|
||||
|
||||
29 new annotation classes across Subjects 5-8:
|
||||
- **Subject 5 (Shims):** Intrinsic, Raw, CallingConv, Link, Shim, PointerArithmetic, Opaque, Target, Feature, Original, Mapping
|
||||
- **Subject 6 (Optimization):** TailCall, Loop, Data, Align, Pack, BoundsCheck, Overflow
|
||||
- **Subject 7 (Meta-Programming):** Meta, Symbol, Evaluate, Template, Synthetic
|
||||
- **Subject 8 (Policy):** Policy, Ambiguity, Candidate, Tradeoff, Choice, Decision
|
||||
|
||||
Generic fallback added to getSemanticAnnotations RPC for extensible annotation type support.
|
||||
|
||||
---
|
||||
|
||||
## Phase 10e: Environment Layer (Steps 284-289)
|
||||
|
||||
**Status:** IN PROGRESS
|
||||
|
||||
### Completed so far:
|
||||
|
||||
**New files created:**
|
||||
- `editor/src/EnvironmentSpec.h` — EnvironmentSpec AST node (envId, envVersion, capabilities, constraints, scheduler, memory, bindingTimes, exceptions, ffi), CapabilityRequirement annotation, capability vocabulary (17 known capabilities), validateCapabilities() (E0501 diagnostics), validateEnvAnnotations() (E0502-E0505 diagnostics), getLoweringHints() (env-aware lowering patterns), envSpecToJson/envSpecFromJson helpers
|
||||
- `editor/src/ast/HostBoundary.h` — HostCall (Expression), ScheduleTask (Statement), ModuleLoad (Statement)
|
||||
|
||||
**Core files updated:**
|
||||
- `Serialization.h` — propertiesToJson/createNode/setPropertiesFromJson for HostCall, ScheduleTask, ModuleLoad, EnvironmentSpec, CapabilityRequirement
|
||||
- `CompactAST.h` — CapabilityRequirement in extractSemanticSummary, host boundary nodes in getNodeName
|
||||
- `SidecarPersistence.h` — CapabilityRequirement in isSemanticAnnotation
|
||||
- `HeadlessAgentRPCHandler.h` — capabilityRequirement type in setSemanticAnnotation, 4 new RPCs: setEnvironment, getEnvironment, validateEnvironment, getLoweringHints
|
||||
- `AgentPermissionPolicy.h` — getEnvironment/validateEnvironment/getLoweringHints (read-only), setEnvironment (mutation)
|
||||
|
||||
**Test files written:**
|
||||
- `step284_test.cpp` — 12 tests (EnvironmentSpec schema, JSON roundtrip, module attachment)
|
||||
- `step285_test.cpp` — 12 tests (capability vocabulary, validation, CapabilityRequirement)
|
||||
|
||||
### Remaining:
|
||||
- Write step286_test.cpp (environment-aware pipeline hooks, 12 tests)
|
||||
- Write step287_test.cpp (environment-aware lowering decisions, 12 tests)
|
||||
- Write step288_test.cpp (host boundary AST nodes, 12 tests)
|
||||
- Write step289_test.cpp (Phase 10e integration tests, 8 tests)
|
||||
- Add CMake targets for steps 284-289
|
||||
- Build, test, fix, commit
|
||||
|
||||
Reference in New Issue
Block a user