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>
131 lines
4.7 KiB
C++
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();
|
|
}
|