@bniladridas/cursor 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.clang-tidy +28 -0
- package/.dockerignore +56 -0
- package/.env.example +29 -0
- package/.github/CODEOWNERS +2 -0
- package/.github/ISSUE_TEMPLATE/blank.md +27 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
- package/.github/SECURITY.md +24 -0
- package/.github/codeql/codeql-config.yml +8 -0
- package/.github/dependabot.yml +14 -0
- package/.github/labeler.yml +50 -0
- package/.github/packaging/brand-cursor.png +0 -0
- package/.github/packaging/database/init.sql +48 -0
- package/.github/packaging/docker/Dockerfile +111 -0
- package/.github/packaging/docker/docker-compose.yml +56 -0
- package/.github/packaging/scripts/preflight.sh +413 -0
- package/.github/packaging/scripts/prepare-release.sh +141 -0
- package/.github/packaging/scripts/release.sh +22 -0
- package/.github/packaging/scripts/setup-git-hooks.sh +73 -0
- package/.github/pull_request_template.md +31 -0
- package/.github/signed.json +9 -0
- package/.github/workflows/README.md +23 -0
- package/.github/workflows/ci.yml +181 -0
- package/.github/workflows/cla.yml +33 -0
- package/.github/workflows/formula-sha.yml +63 -0
- package/.github/workflows/issue-response.yml +44 -0
- package/.github/workflows/labeler.yml +42 -0
- package/.github/workflows/pr-body.yml +49 -0
- package/.github/workflows/release.yml +176 -0
- package/.github/workflows/security.yml +94 -0
- package/.github/workflows/stale.yml +38 -0
- package/AGENTS.md +49 -0
- package/CHANGELOG.md +3 -0
- package/CMakeLists.txt +646 -0
- package/Formula/cursor.rb +46 -0
- package/LICENSE +201 -0
- package/Makefile +28 -0
- package/README.md +46 -0
- package/cli.js +16 -0
- package/include/agent.h +86 -0
- package/include/agent_mode.h +17 -0
- package/include/memory_manager.h +102 -0
- package/include/services/ai_service.h +31 -0
- package/include/services/auth_service.h +87 -0
- package/include/services/checkpoint_service.h +69 -0
- package/include/services/codebase_service.h +38 -0
- package/include/services/command_service.h +23 -0
- package/include/services/context_service.h +74 -0
- package/include/services/database_service.h +56 -0
- package/include/services/error_service.h +106 -0
- package/include/services/file_service.h +51 -0
- package/include/services/git_service.h +29 -0
- package/include/services/github_service.h +85 -0
- package/include/services/mcp_service.h +85 -0
- package/include/services/multi_file_service.h +93 -0
- package/include/services/sandbox_service.h +96 -0
- package/include/services/theme_service.h +67 -0
- package/include/services/web_service.h +52 -0
- package/include/utils/config.h +68 -0
- package/include/utils/memory_utils.h +79 -0
- package/include/utils/platform.h +56 -0
- package/include/utils/ui.h +43 -0
- package/include/utils/validation.h +63 -0
- package/include/utils/version.h.in +17 -0
- package/install.js +49 -0
- package/package.json +16 -0
- package/release/checksums.txt +3 -0
- package/release/cursor-linux/cursor_v0.1.7_linux_amd64.tar.gz +0 -0
- package/release/cursor-macos/cursor_v0.1.7_darwin_arm64.tar.gz +0 -0
- package/release/cursor-windows/cursor__windows_amd64.zip +0 -0
- package/src/agent.cpp +2026 -0
- package/src/main.cpp +97 -0
- package/src/memory_manager.cpp +814 -0
- package/src/services/ai_service.cpp +366 -0
- package/src/services/auth_service.cpp +779 -0
- package/src/services/checkpoint_service.cpp +465 -0
- package/src/services/codebase_service.cpp +233 -0
- package/src/services/command_service.cpp +82 -0
- package/src/services/context_service.cpp +348 -0
- package/src/services/database_service.cpp +148 -0
- package/src/services/error_service.cpp +438 -0
- package/src/services/file_service.cpp +349 -0
- package/src/services/git_service.cpp +148 -0
- package/src/services/github_service.cpp +435 -0
- package/src/services/mcp_service.cpp +481 -0
- package/src/services/multi_file_service.cpp +591 -0
- package/src/services/sandbox_service.cpp +678 -0
- package/src/services/theme_service.cpp +429 -0
- package/src/services/web_service.cpp +532 -0
- package/src/utils/config.cpp +77 -0
- package/src/utils/memory_utils.cpp +93 -0
- package/src/utils/ui.cpp +307 -0
- package/src/utils/validation.cpp +306 -0
- package/src/utils/version.cpp +175 -0
- package/tests/e2e/docker-compose.yml +195 -0
- package/tests/e2e/run_e2e_tests.sh +70 -0
- package/tests/e2e/run_tests_in_docker.sh +115 -0
- package/tests/main_test.cpp +16 -0
- package/tests/mocks/mock_ollama.py +98 -0
- package/tests/mocks/start_nginx.sh +64 -0
|
@@ -0,0 +1,814 @@
|
|
|
1
|
+
#include "memory_manager.h"
|
|
2
|
+
#include "services/file_service.h"
|
|
3
|
+
#include <filesystem>
|
|
4
|
+
#include <fstream>
|
|
5
|
+
#include <stdexcept>
|
|
6
|
+
|
|
7
|
+
#include <algorithm>
|
|
8
|
+
#include <chrono>
|
|
9
|
+
#include <cstdlib>
|
|
10
|
+
#include <iomanip>
|
|
11
|
+
#include <iostream>
|
|
12
|
+
#include <regex>
|
|
13
|
+
#include <sstream>
|
|
14
|
+
#include <unordered_set>
|
|
15
|
+
|
|
16
|
+
namespace Data {
|
|
17
|
+
|
|
18
|
+
MemoryManager::MemoryManager(const std::string &filename)
|
|
19
|
+
: memory_file_(filename),
|
|
20
|
+
last_file_check_(std::chrono::steady_clock::now()), cached_file_size_(0) {
|
|
21
|
+
ensure_memory_directory();
|
|
22
|
+
global_memory_file_ = get_global_memory_path();
|
|
23
|
+
|
|
24
|
+
// Initialize cache
|
|
25
|
+
entry_cache_.reserve(lru_cache_size);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
void MemoryManager::ensure_memory_directory() const {
|
|
29
|
+
try {
|
|
30
|
+
// Ensure directory exists if a path is provided
|
|
31
|
+
std::filesystem::path file_path(memory_file_);
|
|
32
|
+
if (file_path.has_parent_path()) {
|
|
33
|
+
std::filesystem::create_directories(file_path.parent_path());
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Ensure global memory directory exists
|
|
37
|
+
std::filesystem::path global_path(get_global_memory_path());
|
|
38
|
+
if (global_path.has_parent_path()) {
|
|
39
|
+
std::filesystem::create_directories(global_path.parent_path());
|
|
40
|
+
}
|
|
41
|
+
} catch (const std::filesystem::filesystem_error &e) {
|
|
42
|
+
throw std::runtime_error("Failed to create memory directory: " +
|
|
43
|
+
std::string(e.what()));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
std::string MemoryManager::get_timestamp() const {
|
|
48
|
+
auto now = std::chrono::system_clock::now();
|
|
49
|
+
auto time_t = std::chrono::system_clock::to_time_t(now);
|
|
50
|
+
std::stringstream ss;
|
|
51
|
+
ss << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S");
|
|
52
|
+
return ss.str();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
std::string MemoryManager::get_global_memory_path() const {
|
|
56
|
+
// Get home directory
|
|
57
|
+
const char *home = std::getenv("HOME");
|
|
58
|
+
if (!home) {
|
|
59
|
+
home = std::getenv("USERPROFILE"); // Windows
|
|
60
|
+
}
|
|
61
|
+
if (!home) {
|
|
62
|
+
return "global_memory.md"; // Fallback to current directory
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
std::filesystem::path global_path =
|
|
66
|
+
std::filesystem::path(home) / ".cursor" / "CURSOR.md";
|
|
67
|
+
return global_path.string();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
std::vector<std::string> MemoryManager::load_memory() const {
|
|
71
|
+
// Legacy method - convert from optimized load_recent_entries_only()
|
|
72
|
+
std::vector<MemoryEntry> entries = load_recent_entries_only();
|
|
73
|
+
std::vector<std::string> memory;
|
|
74
|
+
memory.reserve(entries.size());
|
|
75
|
+
|
|
76
|
+
for (const auto &entry : entries) {
|
|
77
|
+
memory.push_back(entry.content);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return memory;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
void MemoryManager::save_interaction(const std::string &user_input,
|
|
84
|
+
const std::string &response) {
|
|
85
|
+
std::ofstream file(memory_file_, std::ios::out | std::ios::app);
|
|
86
|
+
if (!file.is_open()) {
|
|
87
|
+
throw std::runtime_error("Unable to open memory file for writing: " +
|
|
88
|
+
memory_file_);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
file << "User: " << user_input << "\n";
|
|
92
|
+
file << "Assistant: " << response << "\n";
|
|
93
|
+
file << "---\n"; // Separator for better readability
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
void MemoryManager::clear_memory() {
|
|
97
|
+
std::ofstream file(memory_file_, std::ios::out | std::ios::trunc);
|
|
98
|
+
if (!file.is_open()) {
|
|
99
|
+
throw std::runtime_error("Unable to open memory file for clearing: " +
|
|
100
|
+
memory_file_);
|
|
101
|
+
}
|
|
102
|
+
// File is now empty
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
std::string MemoryManager::get_context_string() const {
|
|
106
|
+
// Use optimized recent entries loading with StringBuilder
|
|
107
|
+
std::vector<MemoryEntry> recent_entries = load_recent_entries_only();
|
|
108
|
+
|
|
109
|
+
// Use StringBuilder for efficient string concatenation
|
|
110
|
+
Utils::Memory::StringBuilder context_builder(4096);
|
|
111
|
+
|
|
112
|
+
// Limit context to last 20 interactions
|
|
113
|
+
constexpr size_t max_interactions = 20;
|
|
114
|
+
size_t interaction_count = 0;
|
|
115
|
+
|
|
116
|
+
// Process entries in reverse order to get most recent first
|
|
117
|
+
for (auto it = recent_entries.rbegin();
|
|
118
|
+
it != recent_entries.rend() && interaction_count < max_interactions;
|
|
119
|
+
++it) {
|
|
120
|
+
if (it->type == "interaction") {
|
|
121
|
+
context_builder.append(it->content).append("\n");
|
|
122
|
+
interaction_count++;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Add global context efficiently
|
|
127
|
+
std::string global_context = get_global_context();
|
|
128
|
+
if (!global_context.empty()) {
|
|
129
|
+
std::string context_content = std::move(context_builder).build();
|
|
130
|
+
Utils::Memory::StringBuilder final_builder(global_context.size() +
|
|
131
|
+
context_content.size() + 50);
|
|
132
|
+
final_builder.append(global_context)
|
|
133
|
+
.append("\n\nRecent interactions:\n")
|
|
134
|
+
.append(context_content);
|
|
135
|
+
return std::move(final_builder).build();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return std::move(context_builder).build();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
size_t MemoryManager::get_memory_size() const {
|
|
142
|
+
// Use cached file size instead of loading entire file
|
|
143
|
+
if (!Services::FileService::file_exists(memory_file_)) {
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
// Update cached size if needed
|
|
149
|
+
auto now = std::chrono::steady_clock::now();
|
|
150
|
+
if (now - last_file_check_ > std::chrono::seconds(5)) {
|
|
151
|
+
cached_file_size_ = std::filesystem::file_size(memory_file_);
|
|
152
|
+
last_file_check_ = now;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Approximate line count based on average line size (80 chars)
|
|
156
|
+
return cached_file_size_ / 80;
|
|
157
|
+
} catch (const std::exception &) {
|
|
158
|
+
return load_recent_entries_only().size(); // Fallback
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
void MemoryManager::save_fact(const std::string &fact) {
|
|
163
|
+
std::ofstream file(memory_file_, std::ios::out | std::ios::app);
|
|
164
|
+
if (!file.is_open()) {
|
|
165
|
+
throw std::runtime_error("Unable to open memory file for writing: " +
|
|
166
|
+
memory_file_);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
file << "Fact [" << get_timestamp() << "]: " << fact << "\n";
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
void MemoryManager::save_preference(const std::string &preference) {
|
|
173
|
+
std::ofstream file(memory_file_, std::ios::out | std::ios::app);
|
|
174
|
+
if (!file.is_open()) {
|
|
175
|
+
throw std::runtime_error("Unable to open memory file for writing: " +
|
|
176
|
+
memory_file_);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
file << "Preference [" << get_timestamp() << "]: " << preference << "\n";
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
std::vector<MemoryEntry> MemoryManager::load_structured_memory() const {
|
|
183
|
+
// Use optimized recent entries loading - already parsed
|
|
184
|
+
return load_recent_entries_only();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
std::string MemoryManager::get_facts_context() const {
|
|
188
|
+
std::vector<MemoryEntry> entries = load_structured_memory();
|
|
189
|
+
std::string facts_context;
|
|
190
|
+
|
|
191
|
+
for (const auto &entry : entries) {
|
|
192
|
+
if (entry.type == "fact") {
|
|
193
|
+
facts_context += "- " + entry.content + "\n";
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return facts_context.empty() ? "" : "Known facts:\n" + facts_context;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
std::string MemoryManager::get_preferences_context() const {
|
|
201
|
+
std::vector<MemoryEntry> entries = load_structured_memory();
|
|
202
|
+
std::string prefs_context;
|
|
203
|
+
|
|
204
|
+
for (const auto &entry : entries) {
|
|
205
|
+
if (entry.type == "preference") {
|
|
206
|
+
prefs_context += "- " + entry.content + "\n";
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return prefs_context.empty() ? "" : "User preferences:\n" + prefs_context;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
void MemoryManager::save_global_fact(const std::string &fact) {
|
|
214
|
+
try {
|
|
215
|
+
std::string content;
|
|
216
|
+
|
|
217
|
+
// Read existing content
|
|
218
|
+
if (Services::FileService::file_exists(global_memory_file_)) {
|
|
219
|
+
std::ifstream file(global_memory_file_);
|
|
220
|
+
if (file.is_open()) {
|
|
221
|
+
std::ostringstream buffer;
|
|
222
|
+
buffer << file.rdbuf();
|
|
223
|
+
content = buffer.str();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Find or create the memories section
|
|
228
|
+
const std::string header = "## Cursor Added Memories";
|
|
229
|
+
size_t header_pos = content.find(header);
|
|
230
|
+
|
|
231
|
+
if (header_pos == std::string::npos) {
|
|
232
|
+
// Add header and fact
|
|
233
|
+
if (!content.empty() && content.back() != '\n') {
|
|
234
|
+
content += "\n";
|
|
235
|
+
}
|
|
236
|
+
content += "\n" + header + "\n- " + fact + "\n";
|
|
237
|
+
} else {
|
|
238
|
+
// Find insertion point after header
|
|
239
|
+
size_t insert_pos = header_pos + header.length();
|
|
240
|
+
size_t next_header = content.find("\n## ", insert_pos);
|
|
241
|
+
|
|
242
|
+
if (next_header == std::string::npos) {
|
|
243
|
+
// Insert at end
|
|
244
|
+
content += "\n- " + fact;
|
|
245
|
+
} else {
|
|
246
|
+
// Insert before next header
|
|
247
|
+
content.insert(next_header, "\n- " + fact);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Write back to file
|
|
252
|
+
std::ofstream file(global_memory_file_);
|
|
253
|
+
if (file.is_open()) {
|
|
254
|
+
file << content;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
} catch (const std::exception &) {
|
|
258
|
+
throw std::runtime_error("Failed to save global fact");
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
std::string MemoryManager::get_global_context() const {
|
|
263
|
+
if (!Services::FileService::file_exists(global_memory_file_)) {
|
|
264
|
+
return "";
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
std::ifstream file(global_memory_file_);
|
|
269
|
+
if (!file.is_open()) {
|
|
270
|
+
return "";
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
std::ostringstream buffer;
|
|
274
|
+
buffer << file.rdbuf();
|
|
275
|
+
std::string content = buffer.str();
|
|
276
|
+
|
|
277
|
+
// Extract memories section
|
|
278
|
+
const std::string header = "## Cursor Added Memories";
|
|
279
|
+
size_t header_pos = content.find(header);
|
|
280
|
+
|
|
281
|
+
if (header_pos == std::string::npos) {
|
|
282
|
+
return "";
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
size_t start_pos = header_pos + header.length();
|
|
286
|
+
size_t end_pos = content.find("\n## ", start_pos);
|
|
287
|
+
|
|
288
|
+
if (end_pos == std::string::npos) {
|
|
289
|
+
end_pos = content.length();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
std::string memories = content.substr(start_pos, end_pos - start_pos);
|
|
293
|
+
return memories.empty() ? "" : "Global memories:\n" + memories;
|
|
294
|
+
|
|
295
|
+
} catch (const std::exception &) {
|
|
296
|
+
return "";
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
void MemoryManager::clear_global_memory() {
|
|
301
|
+
try {
|
|
302
|
+
if (Services::FileService::file_exists(global_memory_file_)) {
|
|
303
|
+
std::filesystem::remove(global_memory_file_);
|
|
304
|
+
}
|
|
305
|
+
} catch (const std::exception &) {
|
|
306
|
+
throw std::runtime_error("Failed to clear global memory");
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
std::vector<MemoryEntry>
|
|
311
|
+
MemoryManager::search_memory(const std::string &query) const {
|
|
312
|
+
rebuild_index();
|
|
313
|
+
|
|
314
|
+
std::vector<MemoryEntry> results;
|
|
315
|
+
std::unordered_set<size_t> result_indices;
|
|
316
|
+
|
|
317
|
+
// Convert query to lowercase for case-insensitive search
|
|
318
|
+
std::string lower_query(query);
|
|
319
|
+
std::transform(lower_query.begin(), lower_query.end(), lower_query.begin(),
|
|
320
|
+
::tolower);
|
|
321
|
+
|
|
322
|
+
// Tokenize query and search index (O(1) lookup per word)
|
|
323
|
+
std::istringstream iss(lower_query);
|
|
324
|
+
std::string word;
|
|
325
|
+
while (iss >> word) {
|
|
326
|
+
// Remove punctuation
|
|
327
|
+
word.erase(std::remove_if(word.begin(), word.end(), ::ispunct), word.end());
|
|
328
|
+
|
|
329
|
+
if (word.length() > 2) {
|
|
330
|
+
auto it = content_index_.find(word);
|
|
331
|
+
if (it != content_index_.end()) {
|
|
332
|
+
for (size_t idx : it->second) {
|
|
333
|
+
result_indices.insert(idx);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// If no indexed results, fall back to linear search on recent entries
|
|
340
|
+
if (result_indices.empty()) {
|
|
341
|
+
std::vector<MemoryEntry> recent_entries = load_recent_entries_only();
|
|
342
|
+
for (const auto &entry : recent_entries) {
|
|
343
|
+
std::string lower_content = entry.content;
|
|
344
|
+
std::transform(lower_content.begin(), lower_content.end(),
|
|
345
|
+
lower_content.begin(), ::tolower);
|
|
346
|
+
|
|
347
|
+
if (lower_content.find(lower_query) != std::string::npos) {
|
|
348
|
+
results.push_back(entry);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
} else {
|
|
352
|
+
// Convert indices to entries
|
|
353
|
+
std::vector<MemoryEntry> recent_entries = load_recent_entries_only();
|
|
354
|
+
results.reserve(result_indices.size());
|
|
355
|
+
|
|
356
|
+
for (size_t idx : result_indices) {
|
|
357
|
+
if (idx < recent_entries.size()) {
|
|
358
|
+
results.push_back(recent_entries[idx]);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return results;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
void MemoryManager::export_memory(const std::string &filename) const {
|
|
367
|
+
std::vector<MemoryEntry> entries = load_structured_memory();
|
|
368
|
+
|
|
369
|
+
std::ofstream file(filename);
|
|
370
|
+
if (!file.is_open()) {
|
|
371
|
+
throw std::runtime_error("Unable to open export file: " + filename);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
file << "# Cursor Memory Export\n\n";
|
|
375
|
+
file << "Export Date: " << get_timestamp() << "\n\n";
|
|
376
|
+
|
|
377
|
+
for (const auto &entry : entries) {
|
|
378
|
+
file << "## " << entry.type << " [" << entry.timestamp << "]\n";
|
|
379
|
+
file << entry.content << "\n\n";
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
bool MemoryManager::import_memory(const std::string &filename) {
|
|
384
|
+
if (!Services::FileService::file_exists(filename)) {
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
std::ifstream file(filename);
|
|
390
|
+
if (!file.is_open()) {
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
std::string line;
|
|
395
|
+
while (std::getline(file, line)) {
|
|
396
|
+
if (!line.empty() && line != "# Cursor Memory Export" &&
|
|
397
|
+
line.find("Export Date:") == std::string::npos) {
|
|
398
|
+
|
|
399
|
+
std::ofstream memory_file(memory_file_, std::ios::out | std::ios::app);
|
|
400
|
+
if (memory_file.is_open()) {
|
|
401
|
+
memory_file << line << "\n";
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return true;
|
|
407
|
+
} catch (const std::exception &) {
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
void MemoryManager::save_conversation_state(const std::string &tag) {
|
|
413
|
+
try {
|
|
414
|
+
// Create conversations directory
|
|
415
|
+
std::filesystem::path conv_dir =
|
|
416
|
+
std::filesystem::path(memory_file_).parent_path() / "conversations";
|
|
417
|
+
std::filesystem::create_directories(conv_dir);
|
|
418
|
+
|
|
419
|
+
// Save current memory to tagged file
|
|
420
|
+
std::filesystem::path conv_file = conv_dir / (tag + ".txt");
|
|
421
|
+
std::filesystem::copy_file(
|
|
422
|
+
memory_file_, conv_file,
|
|
423
|
+
std::filesystem::copy_options::overwrite_existing);
|
|
424
|
+
|
|
425
|
+
} catch (const std::exception &) {
|
|
426
|
+
throw std::runtime_error("Failed to save conversation state");
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
bool MemoryManager::resume_conversation_state(const std::string &tag) {
|
|
431
|
+
try {
|
|
432
|
+
std::filesystem::path conv_dir =
|
|
433
|
+
std::filesystem::path(memory_file_).parent_path() / "conversations";
|
|
434
|
+
std::filesystem::path conv_file = conv_dir / (tag + ".txt");
|
|
435
|
+
|
|
436
|
+
if (!std::filesystem::exists(conv_file)) {
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Replace current memory with saved state
|
|
441
|
+
std::filesystem::copy_file(
|
|
442
|
+
conv_file, memory_file_,
|
|
443
|
+
std::filesystem::copy_options::overwrite_existing);
|
|
444
|
+
return true;
|
|
445
|
+
|
|
446
|
+
} catch (const std::exception &) {
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
std::vector<std::string> MemoryManager::list_conversation_states() const {
|
|
452
|
+
std::vector<std::string> conversations;
|
|
453
|
+
|
|
454
|
+
try {
|
|
455
|
+
std::filesystem::path conv_dir =
|
|
456
|
+
std::filesystem::path(memory_file_).parent_path() / "conversations";
|
|
457
|
+
|
|
458
|
+
if (!std::filesystem::exists(conv_dir)) {
|
|
459
|
+
return conversations;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
for (const auto &entry : std::filesystem::directory_iterator(conv_dir)) {
|
|
463
|
+
if (entry.is_regular_file() && entry.path().extension() == ".txt") {
|
|
464
|
+
conversations.push_back(entry.path().stem().string());
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
std::sort(conversations.begin(), conversations.end());
|
|
469
|
+
|
|
470
|
+
} catch (const std::exception &) {
|
|
471
|
+
// Return empty vector on error
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return conversations;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
void MemoryManager::delete_conversation_state(const std::string &tag) {
|
|
478
|
+
try {
|
|
479
|
+
std::filesystem::path conv_dir =
|
|
480
|
+
std::filesystem::path(memory_file_).parent_path() / "conversations";
|
|
481
|
+
std::filesystem::path conv_file = conv_dir / (tag + ".txt");
|
|
482
|
+
|
|
483
|
+
if (std::filesystem::exists(conv_file)) {
|
|
484
|
+
std::filesystem::remove(conv_file);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
} catch (const std::exception &) {
|
|
488
|
+
throw std::runtime_error("Failed to delete conversation state");
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
std::string MemoryManager::get_compressible_context() const {
|
|
493
|
+
std::vector<MemoryEntry> entries = load_structured_memory();
|
|
494
|
+
|
|
495
|
+
// Only compress interaction entries, preserve facts and preferences
|
|
496
|
+
std::string compressible_content;
|
|
497
|
+
int interaction_count = 0;
|
|
498
|
+
|
|
499
|
+
for (const auto &entry : entries) {
|
|
500
|
+
if (entry.type == "interaction") {
|
|
501
|
+
compressible_content +=
|
|
502
|
+
"[" + entry.timestamp + "] " + entry.content + "\n";
|
|
503
|
+
interaction_count++;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (interaction_count < 5) {
|
|
508
|
+
return ""; // Not enough content to compress
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return compressible_content;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
void MemoryManager::compress_memory(const std::string &compressed_summary) {
|
|
515
|
+
try {
|
|
516
|
+
// Load current memory entries
|
|
517
|
+
std::vector<MemoryEntry> entries = load_structured_memory();
|
|
518
|
+
|
|
519
|
+
// Create backup before compression
|
|
520
|
+
std::string backup_file = memory_file_ + ".backup." + get_timestamp();
|
|
521
|
+
std::filesystem::copy_file(memory_file_, backup_file);
|
|
522
|
+
|
|
523
|
+
// Clear current memory file
|
|
524
|
+
std::ofstream file(memory_file_, std::ios::trunc);
|
|
525
|
+
if (!file.is_open()) {
|
|
526
|
+
throw std::runtime_error("Could not open memory file for compression");
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Write compressed summary as a single entry
|
|
530
|
+
file << "[" << get_timestamp()
|
|
531
|
+
<< "] COMPRESSED_SUMMARY: " << compressed_summary << std::endl;
|
|
532
|
+
|
|
533
|
+
// Preserve recent interactions (last 3), facts, and preferences
|
|
534
|
+
int recent_interactions = 0;
|
|
535
|
+
for (auto it = entries.rbegin(); it != entries.rend(); ++it) {
|
|
536
|
+
const auto &entry = *it;
|
|
537
|
+
|
|
538
|
+
if (entry.type == "interaction" && recent_interactions < 3) {
|
|
539
|
+
file << "[" << entry.timestamp << "] " << entry.content << std::endl;
|
|
540
|
+
recent_interactions++;
|
|
541
|
+
} else if (entry.type == "fact" || entry.type == "preference") {
|
|
542
|
+
file << "[" << entry.timestamp << "] " << entry.type << ": "
|
|
543
|
+
<< entry.content << std::endl;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
file.close();
|
|
548
|
+
|
|
549
|
+
// Clear cache after compression
|
|
550
|
+
entry_cache_.clear();
|
|
551
|
+
lru_order_.clear();
|
|
552
|
+
content_index_.clear();
|
|
553
|
+
index_dirty_ = true;
|
|
554
|
+
current_memory_usage_ = 0;
|
|
555
|
+
|
|
556
|
+
} catch (const std::exception &) {
|
|
557
|
+
throw std::runtime_error("Failed to compress memory");
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// ============================================================================
|
|
562
|
+
// MEMORY OPTIMIZATION IMPLEMENTATIONS
|
|
563
|
+
// ============================================================================
|
|
564
|
+
|
|
565
|
+
bool MemoryManager::is_cache_valid() const {
|
|
566
|
+
if (!Services::FileService::file_exists(memory_file_)) {
|
|
567
|
+
return entry_cache_.empty();
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Check if file has been modified since last cache
|
|
571
|
+
auto now = std::chrono::steady_clock::now();
|
|
572
|
+
if (now - last_file_check_ >
|
|
573
|
+
std::chrono::seconds(5)) { // Check every 5 seconds
|
|
574
|
+
try {
|
|
575
|
+
size_t current_size = std::filesystem::file_size(memory_file_);
|
|
576
|
+
last_file_check_ = now;
|
|
577
|
+
|
|
578
|
+
if (current_size != cached_file_size_) {
|
|
579
|
+
cached_file_size_ = current_size;
|
|
580
|
+
return false; // Cache invalid
|
|
581
|
+
}
|
|
582
|
+
} catch (const std::exception &) {
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return true;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
void MemoryManager::update_lru_access(size_t entry_id) const {
|
|
591
|
+
// Remove from current position
|
|
592
|
+
auto it = std::find(lru_order_.begin(), lru_order_.end(), entry_id);
|
|
593
|
+
if (it != lru_order_.end()) {
|
|
594
|
+
lru_order_.erase(it);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Add to front (most recently used)
|
|
598
|
+
lru_order_.push_front(entry_id);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
void MemoryManager::evict_lru_entries() const {
|
|
602
|
+
while (entry_cache_.size() > lru_cache_size && !lru_order_.empty()) {
|
|
603
|
+
size_t lru_id = lru_order_.back();
|
|
604
|
+
lru_order_.pop_back();
|
|
605
|
+
|
|
606
|
+
auto it = entry_cache_.find(lru_id);
|
|
607
|
+
if (it != entry_cache_.end()) {
|
|
608
|
+
current_memory_usage_ -= calculate_entry_size(it->second);
|
|
609
|
+
entry_cache_.erase(it);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
MemoryEntry
|
|
615
|
+
MemoryManager::parse_entry_from_line(std::string_view line,
|
|
616
|
+
size_t /* line_number */) const {
|
|
617
|
+
MemoryEntry entry;
|
|
618
|
+
entry.content =
|
|
619
|
+
std::string(line); // Convert string_view to string only when needed
|
|
620
|
+
entry.timestamp = get_timestamp(); // Default timestamp
|
|
621
|
+
entry.type = "interaction"; // Default type
|
|
622
|
+
|
|
623
|
+
// Use string_view for efficient parsing - avoid substr() copies
|
|
624
|
+
if (line.starts_with("Fact [")) {
|
|
625
|
+
entry.type = "fact";
|
|
626
|
+
size_t bracket_end = line.find("]: ");
|
|
627
|
+
if (bracket_end != std::string_view::npos) {
|
|
628
|
+
entry.content = std::string(line.substr(bracket_end + 3));
|
|
629
|
+
// Extract timestamp using string_view
|
|
630
|
+
size_t ts_start = 5; // After "Fact ["
|
|
631
|
+
size_t ts_end = line.find(']');
|
|
632
|
+
if (ts_end != std::string_view::npos && ts_end > ts_start) {
|
|
633
|
+
entry.timestamp = std::string(line.substr(ts_start, ts_end - ts_start));
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
} else if (line.starts_with("Preference [")) {
|
|
637
|
+
entry.type = "preference";
|
|
638
|
+
size_t bracket_end = line.find("]: ");
|
|
639
|
+
if (bracket_end != std::string_view::npos) {
|
|
640
|
+
entry.content = std::string(line.substr(bracket_end + 3));
|
|
641
|
+
// Extract timestamp using string_view
|
|
642
|
+
size_t ts_start = 11; // After "Preference ["
|
|
643
|
+
size_t ts_end = line.find(']');
|
|
644
|
+
if (ts_end != std::string_view::npos && ts_end > ts_start) {
|
|
645
|
+
entry.timestamp = std::string(line.substr(ts_start, ts_end - ts_start));
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
return entry;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
std::vector<MemoryEntry> MemoryManager::load_recent_entries_only() const {
|
|
654
|
+
std::vector<MemoryEntry> entries;
|
|
655
|
+
|
|
656
|
+
if (!Services::FileService::file_exists(memory_file_)) {
|
|
657
|
+
return entries;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// Check cache validity first
|
|
661
|
+
if (!is_cache_valid()) {
|
|
662
|
+
entry_cache_.clear();
|
|
663
|
+
lru_order_.clear();
|
|
664
|
+
index_dirty_ = true;
|
|
665
|
+
current_memory_usage_ = 0;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
std::ifstream file(memory_file_, std::ios::in);
|
|
669
|
+
if (!file.is_open()) {
|
|
670
|
+
return entries;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
std::string line;
|
|
674
|
+
size_t line_number = 0;
|
|
675
|
+
size_t total_lines = 0;
|
|
676
|
+
|
|
677
|
+
// First pass: count total lines (for recent entries calculation)
|
|
678
|
+
while (std::getline(file, line)) {
|
|
679
|
+
if (!line.empty()) {
|
|
680
|
+
total_lines++;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Reset file stream
|
|
685
|
+
file.clear();
|
|
686
|
+
file.seekg(0, std::ios::beg);
|
|
687
|
+
|
|
688
|
+
// Calculate start position for recent entries
|
|
689
|
+
size_t start_line = total_lines > recent_entries_limit
|
|
690
|
+
? total_lines - recent_entries_limit
|
|
691
|
+
: 0;
|
|
692
|
+
|
|
693
|
+
entries.reserve(std::min(total_lines, recent_entries_limit));
|
|
694
|
+
|
|
695
|
+
// Second pass: load only recent entries
|
|
696
|
+
line_number = 0;
|
|
697
|
+
while (std::getline(file, line)) {
|
|
698
|
+
if (line.empty())
|
|
699
|
+
continue;
|
|
700
|
+
|
|
701
|
+
if (line_number >= start_line) {
|
|
702
|
+
// Check cache first
|
|
703
|
+
auto cache_it = entry_cache_.find(line_number);
|
|
704
|
+
if (cache_it != entry_cache_.end()) {
|
|
705
|
+
entries.push_back(cache_it->second);
|
|
706
|
+
update_lru_access(line_number);
|
|
707
|
+
} else {
|
|
708
|
+
// Parse new entry using string_view for efficiency
|
|
709
|
+
MemoryEntry entry = parse_entry_from_line(line, line_number);
|
|
710
|
+
entries.push_back(entry);
|
|
711
|
+
|
|
712
|
+
// Add to cache if within limits
|
|
713
|
+
if (entry_cache_.size() < lru_cache_size) {
|
|
714
|
+
entry_cache_[line_number] = entry;
|
|
715
|
+
update_lru_access(line_number);
|
|
716
|
+
current_memory_usage_ += calculate_entry_size(entry);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
line_number++;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Evict old entries if cache is full
|
|
724
|
+
evict_lru_entries();
|
|
725
|
+
|
|
726
|
+
return entries;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
void MemoryManager::rebuild_index() const {
|
|
730
|
+
if (!index_dirty_)
|
|
731
|
+
return;
|
|
732
|
+
|
|
733
|
+
content_index_.clear();
|
|
734
|
+
|
|
735
|
+
// Load recent entries and build index
|
|
736
|
+
std::vector<MemoryEntry> entries = load_recent_entries_only();
|
|
737
|
+
|
|
738
|
+
for (size_t i = 0; i < entries.size(); ++i) {
|
|
739
|
+
const auto &entry = entries[i];
|
|
740
|
+
|
|
741
|
+
// Tokenize content for indexing (simple word-based)
|
|
742
|
+
std::istringstream iss(entry.content);
|
|
743
|
+
std::string word;
|
|
744
|
+
while (iss >> word) {
|
|
745
|
+
// Convert to lowercase for case-insensitive search
|
|
746
|
+
std::transform(word.begin(), word.end(), word.begin(), ::tolower);
|
|
747
|
+
// Remove punctuation
|
|
748
|
+
word.erase(std::remove_if(word.begin(), word.end(), ::ispunct),
|
|
749
|
+
word.end());
|
|
750
|
+
|
|
751
|
+
if (word.length() > 2) { // Index words longer than 2 characters
|
|
752
|
+
content_index_[word].push_back(i);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Also index by type
|
|
757
|
+
content_index_[entry.type].push_back(i);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
index_dirty_ = false;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
std::vector<MemoryEntry>
|
|
764
|
+
MemoryManager::search_memory_by_type(const std::string &type) const {
|
|
765
|
+
rebuild_index();
|
|
766
|
+
|
|
767
|
+
std::vector<MemoryEntry> results;
|
|
768
|
+
auto it = content_index_.find(type);
|
|
769
|
+
if (it != content_index_.end()) {
|
|
770
|
+
std::vector<MemoryEntry> recent_entries = load_recent_entries_only();
|
|
771
|
+
results.reserve(it->second.size());
|
|
772
|
+
|
|
773
|
+
for (size_t idx : it->second) {
|
|
774
|
+
if (idx < recent_entries.size()) {
|
|
775
|
+
results.push_back(recent_entries[idx]);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
return results;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
size_t MemoryManager::get_cache_hit_ratio() const {
|
|
784
|
+
if (lru_order_.empty())
|
|
785
|
+
return 0;
|
|
786
|
+
|
|
787
|
+
// Simple approximation: cache hits vs total accesses
|
|
788
|
+
return (entry_cache_.size() * 100) / std::max(size_t(1), lru_order_.size());
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
void MemoryManager::print_memory_stats() const {
|
|
792
|
+
std::cout << "=== Memory Manager Statistics ===" << std::endl;
|
|
793
|
+
std::cout << "Cache entries: " << entry_cache_.size() << "/" << lru_cache_size
|
|
794
|
+
<< std::endl;
|
|
795
|
+
std::cout << "Memory usage: " << (current_memory_usage_ / 1024) << " KB"
|
|
796
|
+
<< std::endl;
|
|
797
|
+
std::cout << "Cache hit ratio: " << get_cache_hit_ratio() << "%" << std::endl;
|
|
798
|
+
std::cout << "Index entries: " << content_index_.size() << std::endl;
|
|
799
|
+
std::cout << "File size: " << (cached_file_size_ / 1024) << " KB"
|
|
800
|
+
<< std::endl;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Helper methods implementation
|
|
804
|
+
void MemoryManager::evict_old_entries() const {
|
|
805
|
+
// This method can be used for automatic memory management
|
|
806
|
+
// Currently handled by evict_lru_entries()
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
size_t MemoryManager::calculate_entry_size(const MemoryEntry &entry) const {
|
|
810
|
+
return entry.content.size() + entry.timestamp.size() + entry.type.size() +
|
|
811
|
+
sizeof(MemoryEntry); // Approximate size including object overhead
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
} // namespace Data
|