Steps 266-268: Phase 10a — semantic annotation core + sidecar persistence (32/32 tests)

Step 266: 5 semantic annotation AST types (Intent, Complexity, Risk,
Contract, SemanticTag) with JSON roundtrip and compact AST integration.
Step 267: Sidecar AST persistence (.whetstone/<file>.ast.json) with
save/load/list RPC methods, MCP tools, and permission enforcement.
Step 268: Phase 10a integration tests — multi-file sidecar workflow,
all 5 types in compact view, idempotent roundtrip, source isolation.

Also includes Sprint 10 plan, GUI/installer polish, and Emacs integration fixes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Bill
2026-02-12 16:01:56 +00:00
parent c36fd92045
commit 976161dc4a
36 changed files with 2999 additions and 69 deletions

View File

@@ -1,4 +1,5 @@
#pragma once
#include <algorithm>
#include <string>
#include <vector>
#include <map>

View File

@@ -141,3 +141,44 @@ class ConstExprAnnotation : public Annotation {
public:
ConstExprAnnotation() { conceptType = "ConstExprAnnotation"; }
};
// Semantic annotations for AI guidance (Sprint 10, Step 266)
class IntentAnnotation : public Annotation {
public:
std::string summary; // 1-sentence description of what/why
std::string category; // "validation", "transformation", "io",
// "coordination", "computation", "initialization"
IntentAnnotation() { conceptType = "IntentAnnotation"; }
};
class ComplexityAnnotation : public Annotation {
public:
std::string timeComplexity; // "O(1)", "O(n)", "O(n^2)", etc.
int cognitiveComplexity = 0; // 1-10 scale
int linesOfLogic = 0;
ComplexityAnnotation() { conceptType = "ComplexityAnnotation"; }
};
class RiskAnnotation : public Annotation {
public:
std::string level; // "low", "medium", "high", "critical"
std::string reason;
int dependentCount = 0; // how many callers/consumers
RiskAnnotation() { conceptType = "RiskAnnotation"; }
};
class ContractAnnotation : public Annotation {
public:
std::string preconditions;
std::string postconditions;
std::string returnShape; // human-readable type/shape description
std::string sideEffects; // "none", "io", "mutation", "network"
ContractAnnotation() { conceptType = "ContractAnnotation"; }
};
class SemanticTagAnnotation : public Annotation {
public:
std::vector<std::string> tags; // e.g. ["@serialize", "@validation"]
SemanticTagAnnotation() { conceptType = "SemanticTagAnnotation"; }
};

View File

@@ -134,6 +134,35 @@ inline json propertiesToJson(const ASTNode* node) {
if (!n->semanticHint.empty()) props["semanticHint"] = n->semanticHint;
if (!n->position.empty()) props["position"] = n->position;
}
// Semantic annotations (Sprint 10)
else if (ct == "IntentAnnotation") {
auto* n = static_cast<const IntentAnnotation*>(node);
if (!n->summary.empty()) props["summary"] = n->summary;
if (!n->category.empty()) props["category"] = n->category;
}
else if (ct == "ComplexityAnnotation") {
auto* n = static_cast<const ComplexityAnnotation*>(node);
if (!n->timeComplexity.empty()) props["timeComplexity"] = n->timeComplexity;
props["cognitiveComplexity"] = n->cognitiveComplexity;
props["linesOfLogic"] = n->linesOfLogic;
}
else if (ct == "RiskAnnotation") {
auto* n = static_cast<const RiskAnnotation*>(node);
if (!n->level.empty()) props["level"] = n->level;
if (!n->reason.empty()) props["reason"] = n->reason;
props["dependentCount"] = n->dependentCount;
}
else if (ct == "ContractAnnotation") {
auto* n = static_cast<const ContractAnnotation*>(node);
if (!n->preconditions.empty()) props["preconditions"] = n->preconditions;
if (!n->postconditions.empty()) props["postconditions"] = n->postconditions;
if (!n->returnShape.empty()) props["returnShape"] = n->returnShape;
if (!n->sideEffects.empty()) props["sideEffects"] = n->sideEffects;
}
else if (ct == "SemanticTagAnnotation") {
auto* n = static_cast<const SemanticTagAnnotation*>(node);
if (!n->tags.empty()) props["tags"] = n->tags;
}
// NullLiteral, ListLiteral, IndexAccess, Block, Assignment, IfStatement,
// WhileLoop, Return, ExpressionStatement, ListType, SetType, MapType,
// TupleType, ArrayType, OptionalType — no extra properties
@@ -206,6 +235,11 @@ inline ASTNode* createNode(const std::string& conceptName) {
if (conceptName == "DerefStrategy") return new DerefStrategy();
if (conceptName == "OptimizationLock") return new OptimizationLock();
if (conceptName == "LangSpecific") return new LangSpecific();
if (conceptName == "IntentAnnotation") return new IntentAnnotation();
if (conceptName == "ComplexityAnnotation") return new ComplexityAnnotation();
if (conceptName == "RiskAnnotation") return new RiskAnnotation();
if (conceptName == "ContractAnnotation") return new ContractAnnotation();
if (conceptName == "SemanticTagAnnotation") return new SemanticTagAnnotation();
return nullptr;
}
@@ -328,13 +362,53 @@ inline void setPropertiesFromJson(ASTNode* node, const json& props) {
if (props.contains("semanticHint")) n->semanticHint = props["semanticHint"].get<std::string>();
if (props.contains("position")) n->position = props["position"].get<std::string>();
}
// Semantic annotations (Sprint 10)
else if (ct == "IntentAnnotation") {
auto* n = static_cast<IntentAnnotation*>(node);
if (props.contains("summary")) n->summary = props["summary"].get<std::string>();
if (props.contains("category")) n->category = props["category"].get<std::string>();
}
else if (ct == "ComplexityAnnotation") {
auto* n = static_cast<ComplexityAnnotation*>(node);
if (props.contains("timeComplexity")) n->timeComplexity = props["timeComplexity"].get<std::string>();
if (props.contains("cognitiveComplexity")) n->cognitiveComplexity = props["cognitiveComplexity"].get<int>();
if (props.contains("linesOfLogic")) n->linesOfLogic = props["linesOfLogic"].get<int>();
}
else if (ct == "RiskAnnotation") {
auto* n = static_cast<RiskAnnotation*>(node);
if (props.contains("level")) n->level = props["level"].get<std::string>();
if (props.contains("reason")) n->reason = props["reason"].get<std::string>();
if (props.contains("dependentCount")) n->dependentCount = props["dependentCount"].get<int>();
}
else if (ct == "ContractAnnotation") {
auto* n = static_cast<ContractAnnotation*>(node);
if (props.contains("preconditions")) n->preconditions = props["preconditions"].get<std::string>();
if (props.contains("postconditions")) n->postconditions = props["postconditions"].get<std::string>();
if (props.contains("returnShape")) n->returnShape = props["returnShape"].get<std::string>();
if (props.contains("sideEffects")) n->sideEffects = props["sideEffects"].get<std::string>();
}
else if (ct == "SemanticTagAnnotation") {
auto* n = static_cast<SemanticTagAnnotation*>(node);
if (props.contains("tags") && props["tags"].is_array()) {
n->tags.clear();
for (const auto& tag : props["tags"]) {
if (tag.is_string()) n->tags.push_back(tag.get<std::string>());
}
}
}
}
inline std::string generateNodeId() {
static int counter = 0;
return "node_" + std::to_string(++counter);
}
inline ASTNode* fromJson(const json& j) {
ASTNode* node = createNode(j["concept"].get<std::string>());
if (!node) return nullptr;
node->id = j["id"].get<std::string>();
node->id = j.contains("id") ? j["id"].get<std::string>()
: generateNodeId();
if (j.contains("span")) {
const auto& span = j["span"];
if (span.contains("start") && span.contains("end")) {