Files
whetstone_DSL/editor/src/BufferOpsLifecycle.h
Bill 976161dc4a 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>
2026-02-12 16:01:56 +00:00

131 lines
4.7 KiB
C++

#pragma once
// --- BufferOpsLifecycle.h ---
// Extracted from BufferOps.h (Sprint 8 cleanup).
// Session metadata and lifecycle initialization helpers.
// Included from BufferOps.h after the EditorState struct definition.
inline std::string EditorState::osLabel() {
#ifdef _WIN32
return "windows";
#elif __APPLE__
return "macos";
#else
return "linux";
#endif
}
inline std::string EditorState::detectProjectType() const {
std::filesystem::path root(workspaceRoot);
std::error_code ec;
auto exists = [&](const std::string& name) {
return std::filesystem::exists(root / name, ec);
};
if (exists("CMakeLists.txt")) return "cmake";
if (exists("package.json")) return "node";
if (exists("pyproject.toml") || exists("requirements.txt")) return "python";
if (exists("Cargo.toml")) return "rust";
if (exists("go.mod")) return "go";
if (exists("pom.xml") || exists("build.gradle")) return "java";
return "unknown";
}
inline json EditorState::buildSessionMetadata(bool autoRecording) const {
json meta;
meta["editorVersion"] = welcome.getVersion();
meta["os"] = osLabel();
meta["language"] = activeBuffer ? activeBuffer->language : std::string("unknown");
meta["projectType"] = detectProjectType();
meta["autoRecording"] = autoRecording;
return meta;
}
inline void EditorState::startSessionRecording(const std::string& name, bool autoRecording) {
WorkflowRecorder::RecordingConfig cfg;
cfg.recordAllSessions = true;
cfg.recordControlMethods = true;
cfg.recordEditorEvents = true;
cfg.autoRecording = autoRecording;
agent.workflowRecorder.startRecording(name, "", cfg, buildSessionMetadata(autoRecording));
agent.workflowRecorder.recordEvent("session_start", {{"name", name}, {"autoRecording", autoRecording}});
notify(NotificationLevel::Info, "Session recording started.");
}
inline void EditorState::stopSessionRecording() {
if (!agent.workflowRecorder.isRecording()) return;
agent.workflowRecorder.recordEvent("session_stop", json::object());
agent.workflowRecorder.stopRecording();
notify(NotificationLevel::Info, "Session recording stopped.");
}
inline void EditorState::toggleSessionRecording() {
if (agent.workflowRecorder.isRecording()) stopSessionRecording();
else startSessionRecording("session", false);
}
inline void EditorState::recordUIEvent(const UIEvent& ev) {
json payload = {
{"path", ev.path},
{"detail", ev.detail},
{"uiTimestamp", ev.timestamp}
};
agent.workflowRecorder.recordEvent(uiEventLabel(ev.type), payload);
}
inline void EditorState::recordEditorEvent(const std::string& type, const json& payload) {
agent.workflowRecorder.recordEvent(type, payload);
}
inline void EditorState::init() {
notify(NotificationLevel::Info, "Whetstone Editor ready.");
workspaceRoot = std::filesystem::current_path().string();
search.projectSearch.setRoot(workspaceRoot);
fileTreeDirty = true;
loadRecentFiles();
loadFeatureHints(featureHints, workspaceRoot);
loadSettingsFromDisk();
if (settings.getAutoRecordSessions() && !agent.workflowRecorder.isRecording()) {
startSessionRecording("auto-session", true);
}
registerCommands();
library.vulnDb.startBackgroundRefresh();
library.semanticTags.load();
events.subscribe(UIEventType::BufferSwitched, [this](const UIEvent& ev) {
if (!active()) return;
library.primitives.setRoot(activeAST());
library.primitives.setLanguage(active()->language);
symbolsPending = true;
symbolsLastChange = ImGui::GetTime();
if (lsp) lsp->clearDocumentSymbols();
if (active()->language == "elisp") {
emacsState.emacsFunctionIndexDirty = true;
}
recordUIEvent(ev);
});
events.subscribe(UIEventType::ASTChanged, [this](const UIEvent&) {
if (!active()) return;
library.primitives.setRoot(activeAST());
library.primitives.setLanguage(active()->language);
symbolsPending = true;
symbolsLastChange = ImGui::GetTime();
if (lsp) lsp->clearDocumentSymbols();
});
events.subscribe(UIEventType::SettingsChanged, [this](const UIEvent& ev) {
applySettingsToState();
recordUIEvent(ev);
});
events.subscribe(UIEventType::ThemeChanged, [this](const UIEvent& ev) {
recordUIEvent(ev);
});
events.subscribe(UIEventType::FileModified, [this](const UIEvent& ev) {
recordUIEvent(ev);
});
events.subscribe(UIEventType::NotificationPosted, [this](const UIEvent& ev) {
recordUIEvent(ev);
});
startEmacsDaemonFromSettings();
initAgentServer();
refreshBuildSystem();
telemetry.initialize(workspaceRoot, settings.getTelemetryOptIn());
telemetry.installCrashHandler();
}