cntx-ui 2.0.15 → 3.0.1

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.
Files changed (32) hide show
  1. package/README.md +40 -344
  2. package/bin/cntx-ui-mcp.sh +3 -0
  3. package/bin/cntx-ui.js +2 -1
  4. package/lib/agent-runtime.js +161 -1340
  5. package/lib/agent-tools.js +9 -7
  6. package/lib/api-router.js +262 -79
  7. package/lib/bundle-manager.js +172 -407
  8. package/lib/configuration-manager.js +94 -59
  9. package/lib/database-manager.js +397 -0
  10. package/lib/file-system-manager.js +17 -0
  11. package/lib/heuristics-manager.js +119 -17
  12. package/lib/mcp-server.js +125 -55
  13. package/lib/semantic-splitter.js +222 -481
  14. package/lib/simple-vector-store.js +69 -300
  15. package/package.json +18 -31
  16. package/server.js +151 -73
  17. package/templates/TOOLS.md +41 -0
  18. package/templates/activities/activities/create-project-bundles/README.md +4 -3
  19. package/templates/activities/activities/create-project-bundles/notes.md +15 -19
  20. package/templates/activities/activities/create-project-bundles/tasks.md +4 -4
  21. package/templates/activities/activities.json +1 -1
  22. package/templates/agent-config.yaml +0 -13
  23. package/templates/agent-instructions.md +22 -6
  24. package/templates/agent-rules/capabilities/bundle-system.md +1 -1
  25. package/templates/agent-rules/project-specific/architecture.md +1 -1
  26. package/web/dist/assets/index-B2OdTzzI.css +1 -0
  27. package/web/dist/assets/index-D0tBsKiR.js +2016 -0
  28. package/web/dist/index.html +2 -2
  29. package/mcp-config-example.json +0 -9
  30. package/web/dist/assets/heuristics-manager-browser-DfonOP5I.js +0 -1
  31. package/web/dist/assets/index-dF3qg-y_.js +0 -2486
  32. package/web/dist/assets/index-h5FGSg_P.css +0 -1
@@ -5,6 +5,7 @@
5
5
 
6
6
  import { readFileSync, writeFileSync, existsSync, unlinkSync } from 'fs';
7
7
  import { join, relative } from 'path';
8
+ import DatabaseManager from './database-manager.js';
8
9
 
9
10
  export default class ConfigurationManager {
10
11
  constructor(cwd = process.cwd(), options = {}) {
@@ -32,45 +33,34 @@ export default class ConfigurationManager {
32
33
  };
33
34
  this.ignorePatterns = [];
34
35
 
36
+ // Initialize database manager
37
+ this.dbManager = new DatabaseManager(this.CNTX_DIR, { verbose: this.verbose });
38
+
35
39
  // Load initial configuration
36
40
  this.loadConfig();
41
+
42
+ // Load bundles (try SQLite first, fallback to JSON)
43
+ this.loadBundleStates();
37
44
  }
38
45
 
39
46
  // === Bundle Configuration ===
40
47
 
41
48
  loadConfig() {
42
- // Clear existing bundles to ensure deleted ones are removed
43
- this.bundleStates.clear();
44
-
45
49
  if (existsSync(this.CONFIG_FILE)) {
46
50
  const config = JSON.parse(readFileSync(this.CONFIG_FILE, 'utf8'));
47
51
 
48
- // Load editor setting
52
+ // Load non-bundle settings only
49
53
  this.editor = config.editor || 'code';
50
-
51
- Object.entries(config.bundles || {}).forEach(([name, patterns]) => {
52
- this.bundleStates.set(name, {
53
- patterns: Array.isArray(patterns) ? patterns : [patterns],
54
- files: [],
55
- content: '',
56
- changed: false,
57
- size: 0,
58
- generated: null
59
- });
60
- });
54
+
55
+ // Store full config for other non-bundle settings
56
+ this.config = config;
57
+ } else {
58
+ // Set defaults
59
+ this.editor = 'code';
60
+ this.config = {};
61
61
  }
62
62
 
63
- // Ensure 'master' bundle exists
64
- if (!this.bundleStates.has('master')) {
65
- this.bundleStates.set('master', {
66
- patterns: ['**/*'],
67
- files: [],
68
- content: '',
69
- changed: false,
70
- size: 0,
71
- generated: null
72
- });
73
- }
63
+ // Bundle loading is now handled by loadBundleStates()
74
64
  }
75
65
 
76
66
  saveConfig(config) {
@@ -80,56 +70,88 @@ export default class ConfigurationManager {
80
70
  // === Bundle States Persistence ===
81
71
 
82
72
  loadBundleStates() {
73
+ // Try loading from SQLite first
74
+ try {
75
+ this.bundleStates = this.dbManager.loadBundles();
76
+
77
+ if (this.bundleStates.size > 0) {
78
+ if (this.verbose) {
79
+ console.log('✅ Loaded bundles from SQLite database');
80
+ }
81
+ return;
82
+ }
83
+ } catch (error) {
84
+ if (this.verbose) {
85
+ console.log('⚠️ SQLite load failed, trying JSON fallback:', error.message);
86
+ }
87
+ }
88
+
89
+ // Fallback to JSON file
90
+ this.bundleStates.clear();
91
+
83
92
  if (existsSync(this.BUNDLE_STATES_FILE)) {
84
93
  try {
85
94
  const bundleStates = JSON.parse(readFileSync(this.BUNDLE_STATES_FILE, 'utf8'));
86
95
 
87
96
  bundleStates.forEach(state => {
88
- if (this.bundleStates.has(state.name)) {
89
- const bundle = this.bundleStates.get(state.name);
90
- bundle.content = state.content || '';
91
- bundle.size = state.size || 0;
92
- bundle.generated = state.generated || null;
93
- bundle.changed = false;
94
- if (state.files) {
95
- // Migrate absolute paths to relative paths for portability
96
- bundle.files = state.files.map(file => {
97
- if (file.startsWith('/')) {
98
- // Convert absolute to relative
99
- const relativePath = require('path').relative(this.CWD, file);
100
- return relativePath;
101
- } else {
102
- // Already relative
103
- return file;
104
- }
105
- });
106
- }
107
- }
97
+ this.bundleStates.set(state.name, {
98
+ patterns: state.patterns || [],
99
+ files: (state.files || []).map(file => {
100
+ if (file.startsWith('/')) {
101
+ const relativePath = relative(this.CWD, file);
102
+ return relativePath;
103
+ } else {
104
+ return file;
105
+ }
106
+ }),
107
+ content: state.content || '',
108
+ size: state.size || 0,
109
+ generated: state.generated || null,
110
+ changed: false
111
+ });
108
112
  });
113
+
114
+ // Migrate JSON data to SQLite
115
+ this.dbManager.saveBundles(this.bundleStates);
116
+ if (this.verbose) {
117
+ console.log('📦 Migrated bundle data from JSON to SQLite');
118
+ }
109
119
  } catch (error) {
110
- console.error('Failed to load bundle states:', error.message);
120
+ console.error('Failed to load bundle states from JSON:', error.message);
111
121
  }
112
122
  }
123
+
124
+ // Ensure 'master' bundle exists
125
+ if (!this.bundleStates.has('master')) {
126
+ this.bundleStates.set('master', {
127
+ patterns: ['**/*'],
128
+ files: [],
129
+ content: '',
130
+ changed: false,
131
+ size: 0,
132
+ generated: null
133
+ });
134
+ }
113
135
  }
114
136
 
115
137
  saveBundleStates() {
116
138
  try {
139
+ // Save to SQLite database (primary)
140
+ this.dbManager.saveBundles(this.bundleStates);
141
+
142
+ // Also save to JSON for compatibility/backup
117
143
  const bundleStates = Array.from(this.bundleStates.entries()).map(([name, bundle]) => ({
118
144
  name,
119
- // Don't save the full content - it's too large and can cause RangeError
120
145
  contentPreview: bundle.content ? bundle.content.substring(0, 200) + '...' : '',
121
146
  size: bundle.size,
122
147
  fileCount: bundle.files ? bundle.files.length : 0,
123
148
  generated: bundle.generated,
124
149
  changed: bundle.changed,
125
150
  patterns: bundle.patterns,
126
- // Always save relative paths for portability
127
151
  files: (bundle.files || []).map(file => {
128
152
  if (file.startsWith('/')) {
129
- // Convert absolute to relative
130
153
  return relative(this.CWD, file);
131
154
  } else {
132
- // Already relative
133
155
  return file;
134
156
  }
135
157
  })
@@ -137,7 +159,7 @@ export default class ConfigurationManager {
137
159
 
138
160
  writeFileSync(this.BUNDLE_STATES_FILE, JSON.stringify(bundleStates, null, 2));
139
161
  if (this.verbose) {
140
- console.log(`💾 Saved bundle states for ${bundleStates.length} bundles`);
162
+ console.log(`💾 Saved ${bundleStates.length} bundles to SQLite + JSON`);
141
163
  }
142
164
  } catch (error) {
143
165
  if (this.verbose) {
@@ -694,19 +716,32 @@ When working on this project, please:
694
716
  generated: null
695
717
  });
696
718
 
697
- // Save to config file
698
- const config = { bundles: {} };
699
- this.bundleStates.forEach((bundle, name) => {
700
- config.bundles[name] = bundle.patterns;
701
- });
702
-
703
- this.saveConfig(config);
719
+ // Save to bundle-states.json (single source of truth)
720
+ this.saveBundleStates();
704
721
 
705
722
  return bundleName;
706
723
  }
707
724
 
708
725
  // === Getters ===
709
726
 
727
+ // === Ignore File Management ===
728
+
729
+ saveCntxignore(content) {
730
+ try {
731
+ writeFileSync(this.IGNORE_FILE, content, 'utf8');
732
+
733
+ // Update ignore patterns in memory immediately
734
+ this.ignorePatterns = content.split('\n')
735
+ .map(line => line.trim())
736
+ .filter(line => line && !line.startsWith('#'));
737
+
738
+ return true;
739
+ } catch (error) {
740
+ console.error('Failed to save .cntxignore:', error.message);
741
+ return false;
742
+ }
743
+ }
744
+
710
745
  getBundles() {
711
746
  return this.bundleStates;
712
747
  }
@@ -0,0 +1,397 @@
1
+ /**
2
+ * Database Manager for cntx-ui
3
+ * Handles SQLite operations for bundle and file data
4
+ */
5
+
6
+ import Database from 'better-sqlite3';
7
+ import { join } from 'path';
8
+ import { statSync } from 'fs';
9
+
10
+ export default class DatabaseManager {
11
+ constructor(cntxDir, options = {}) {
12
+ this.cntxDir = cntxDir;
13
+ this.verbose = options.verbose || false;
14
+ this.dbPath = join(cntxDir, 'bundles.db');
15
+
16
+ try {
17
+ this.db = new Database(this.dbPath);
18
+ this.initSchema();
19
+ if (this.verbose) {
20
+ console.log(`📊 SQLite database initialized: ${this.dbPath}`);
21
+ }
22
+ } catch (error) {
23
+ console.error('Failed to initialize SQLite database:', error.message);
24
+ throw error;
25
+ }
26
+ }
27
+
28
+ initSchema() {
29
+ this.db.exec(`
30
+ -- Existing bundles table
31
+ CREATE TABLE IF NOT EXISTS bundles (
32
+ name TEXT PRIMARY KEY,
33
+ patterns TEXT NOT NULL,
34
+ files TEXT NOT NULL,
35
+ size INTEGER DEFAULT 0,
36
+ file_count INTEGER DEFAULT 0,
37
+ generated_at TEXT,
38
+ changed BOOLEAN DEFAULT FALSE
39
+ );
40
+
41
+ -- Persistent Semantic Chunks
42
+ CREATE TABLE IF NOT EXISTS semantic_chunks (
43
+ id TEXT PRIMARY KEY,
44
+ name TEXT NOT NULL,
45
+ file_path TEXT NOT NULL,
46
+ type TEXT,
47
+ subtype TEXT,
48
+ content TEXT NOT NULL,
49
+ start_line INTEGER,
50
+ complexity_score INTEGER,
51
+ purpose TEXT,
52
+ metadata TEXT, -- JSON string for tags, imports, types, etc.
53
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
54
+ );
55
+
56
+ -- Vector Embeddings (Persistence for RAG)
57
+ CREATE TABLE IF NOT EXISTS vector_embeddings (
58
+ chunk_id TEXT PRIMARY KEY,
59
+ embedding BLOB NOT NULL, -- Stored as Float32Array blob
60
+ model_name TEXT NOT NULL,
61
+ FOREIGN KEY(chunk_id) REFERENCES semantic_chunks(id) ON DELETE CASCADE
62
+ );
63
+
64
+ -- Agent Working Memory & Sessions
65
+ CREATE TABLE IF NOT EXISTS agent_sessions (
66
+ id TEXT PRIMARY KEY,
67
+ title TEXT,
68
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
69
+ last_active_at DATETIME DEFAULT CURRENT_TIMESTAMP,
70
+ context_summary TEXT -- High-level summary of what was being worked on
71
+ );
72
+
73
+ CREATE TABLE IF NOT EXISTS agent_memory (
74
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
75
+ session_id TEXT NOT NULL,
76
+ role TEXT NOT NULL, -- 'user' or 'agent'
77
+ content TEXT NOT NULL,
78
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
79
+ metadata TEXT, -- JSON for tools used, files referenced, etc.
80
+ FOREIGN KEY(session_id) REFERENCES agent_sessions(id) ON DELETE CASCADE
81
+ );
82
+
83
+ CREATE INDEX IF NOT EXISTS idx_bundles_changed ON bundles(changed);
84
+ CREATE INDEX IF NOT EXISTS idx_chunks_file ON semantic_chunks(file_path);
85
+ CREATE INDEX IF NOT EXISTS idx_chunks_purpose ON semantic_chunks(purpose);
86
+ CREATE INDEX IF NOT EXISTS idx_memory_session ON agent_memory(session_id);
87
+ `);
88
+ }
89
+
90
+ // Save all bundles from bundleStates Map
91
+ saveBundles(bundleStates) {
92
+ const stmt = this.db.prepare(`
93
+ INSERT OR REPLACE INTO bundles (name, patterns, files, size, file_count, generated_at, changed)
94
+ VALUES (?, ?, ?, ?, ?, ?, ?)
95
+ `);
96
+
97
+ const transaction = this.db.transaction(() => {
98
+ for (const [name, bundle] of bundleStates) {
99
+ stmt.run(
100
+ name,
101
+ JSON.stringify(bundle.patterns || []),
102
+ JSON.stringify(bundle.files || []),
103
+ bundle.size || 0,
104
+ bundle.files ? bundle.files.length : 0,
105
+ bundle.generated || null,
106
+ bundle.changed ? 1 : 0
107
+ );
108
+ }
109
+ });
110
+
111
+ try {
112
+ transaction();
113
+ if (this.verbose) {
114
+ console.log(`💾 Saved ${bundleStates.size} bundles to SQLite`);
115
+ }
116
+ } catch (error) {
117
+ console.error('Failed to save bundles to SQLite:', error.message);
118
+ throw error;
119
+ }
120
+ }
121
+
122
+ // Load all bundles and return as Map
123
+ loadBundles() {
124
+ try {
125
+ const rows = this.db.prepare('SELECT * FROM bundles').all();
126
+ const bundleStates = new Map();
127
+
128
+ rows.forEach(row => {
129
+ bundleStates.set(row.name, {
130
+ patterns: JSON.parse(row.patterns),
131
+ files: JSON.parse(row.files),
132
+ size: row.size,
133
+ generated: row.generated_at,
134
+ changed: Boolean(row.changed)
135
+ });
136
+ });
137
+
138
+ if (this.verbose) {
139
+ console.log(`📊 Loaded ${bundleStates.size} bundles from SQLite`);
140
+ }
141
+
142
+ return bundleStates;
143
+ } catch (error) {
144
+ console.error('Failed to load bundles from SQLite:', error.message);
145
+ return new Map();
146
+ }
147
+ }
148
+
149
+ // Get single bundle by name
150
+ getBundle(name) {
151
+ try {
152
+ const row = this.db.prepare('SELECT * FROM bundles WHERE name = ?').get(name);
153
+ if (!row) return null;
154
+
155
+ return {
156
+ patterns: JSON.parse(row.patterns),
157
+ files: JSON.parse(row.files),
158
+ size: row.size,
159
+ generated: row.generated_at,
160
+ changed: Boolean(row.changed)
161
+ };
162
+ } catch (error) {
163
+ console.error(`Failed to get bundle ${name}:`, error.message);
164
+ return null;
165
+ }
166
+ }
167
+
168
+ // Delete bundle
169
+ deleteBundle(name) {
170
+ try {
171
+ const stmt = this.db.prepare('DELETE FROM bundles WHERE name = ?');
172
+ const result = stmt.run(name);
173
+
174
+ if (this.verbose && result.changes > 0) {
175
+ console.log(`🗑️ Deleted bundle: ${name}`);
176
+ }
177
+
178
+ return result.changes > 0;
179
+ } catch (error) {
180
+ console.error(`Failed to delete bundle ${name}:`, error.message);
181
+ return false;
182
+ }
183
+ }
184
+
185
+ // Get all bundle names
186
+ getBundleNames() {
187
+ try {
188
+ const rows = this.db.prepare('SELECT name FROM bundles ORDER BY name').all();
189
+ return rows.map(row => row.name);
190
+ } catch (error) {
191
+ console.error('Failed to get bundle names:', error.message);
192
+ return [];
193
+ }
194
+ }
195
+
196
+ // Query interface for database tab
197
+ query(sql) {
198
+ try {
199
+ // Safety: only allow SELECT statements for now
200
+ if (!sql.trim().toLowerCase().startsWith('select')) {
201
+ throw new Error('Only SELECT queries are allowed');
202
+ }
203
+
204
+ const stmt = this.db.prepare(sql);
205
+ return stmt.all();
206
+ } catch (error) {
207
+ console.error('Query failed:', error.message);
208
+ throw error;
209
+ }
210
+ }
211
+
212
+ // Get database info for debugging
213
+ getInfo() {
214
+ try {
215
+ const bundleCount = this.db.prepare('SELECT COUNT(*) as count FROM bundles').get().count;
216
+ const chunkCount = this.db.prepare('SELECT COUNT(*) as count FROM semantic_chunks').get().count;
217
+ const embeddingCount = this.db.prepare('SELECT COUNT(*) as count FROM vector_embeddings').get().count;
218
+ const sessionCount = this.db.prepare('SELECT COUNT(*) as count FROM agent_sessions').get().count;
219
+ const dbSize = statSync(this.dbPath).size;
220
+
221
+ return {
222
+ path: this.dbPath,
223
+ bundleCount,
224
+ chunkCount,
225
+ embeddingCount,
226
+ sessionCount,
227
+ sizeBytes: dbSize,
228
+ sizeFormatted: (dbSize / 1024).toFixed(1) + ' KB',
229
+ tables: ['bundles', 'semantic_chunks', 'vector_embeddings', 'agent_sessions', 'agent_memory']
230
+ };
231
+ } catch (error) {
232
+ return { error: error.message };
233
+ }
234
+ }
235
+
236
+ // Save semantic chunks
237
+ saveChunks(chunks) {
238
+ const stmt = this.db.prepare(`
239
+ INSERT OR REPLACE INTO semantic_chunks (id, name, file_path, type, subtype, content, start_line, complexity_score, purpose, metadata)
240
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
241
+ `);
242
+
243
+ const transaction = this.db.transaction(() => {
244
+ for (const chunk of chunks) {
245
+ // Generate a stable ID if not provided (org/repo:path:name)
246
+ const id = chunk.id || `${chunk.filePath}:${chunk.name}:${chunk.startLine}`;
247
+ stmt.run(
248
+ id,
249
+ chunk.name,
250
+ chunk.filePath,
251
+ chunk.type || 'unknown',
252
+ chunk.subtype || 'unknown',
253
+ chunk.code || chunk.content || '',
254
+ chunk.startLine || 0,
255
+ chunk.complexity?.score || 0,
256
+ chunk.purpose || 'Utility function',
257
+ JSON.stringify({
258
+ tags: chunk.tags || [],
259
+ businessDomain: chunk.businessDomain || [],
260
+ technicalPatterns: chunk.technicalPatterns || [],
261
+ imports: chunk.includes?.imports || [],
262
+ types: chunk.includes?.types || [],
263
+ bundles: chunk.bundles || []
264
+ })
265
+ );
266
+ }
267
+ });
268
+
269
+ try {
270
+ transaction();
271
+ return true;
272
+ } catch (error) {
273
+ console.error('Failed to save chunks to SQLite:', error.message);
274
+ return false;
275
+ }
276
+ }
277
+
278
+ // Get chunks for a specific file
279
+ getChunksByFile(filePath) {
280
+ try {
281
+ const rows = this.db.prepare('SELECT * FROM semantic_chunks WHERE file_path = ?').all(filePath);
282
+ return rows.map(row => this.mapChunkRow(row));
283
+ } catch (error) {
284
+ return [];
285
+ }
286
+ }
287
+
288
+ // Search chunks by name or purpose
289
+ searchChunks(query) {
290
+ try {
291
+ const rows = this.db.prepare(`
292
+ SELECT * FROM semantic_chunks
293
+ WHERE name LIKE ? OR purpose LIKE ?
294
+ LIMIT 50
295
+ `).all(`%${query}%`, `%${query}%`);
296
+ return rows.map(row => this.mapChunkRow(row));
297
+ } catch (error) {
298
+ return [];
299
+ }
300
+ }
301
+
302
+ mapChunkRow(row) {
303
+ const metadata = JSON.parse(row.metadata || '{}');
304
+ return {
305
+ id: row.id,
306
+ name: row.name,
307
+ filePath: row.file_path,
308
+ type: row.type,
309
+ subtype: row.subtype,
310
+ code: row.content,
311
+ startLine: row.start_line,
312
+ complexity: { score: row.complexity_score },
313
+ purpose: row.purpose,
314
+ tags: metadata.tags || [],
315
+ businessDomain: metadata.businessDomain || [],
316
+ technicalPatterns: metadata.technicalPatterns || [],
317
+ includes: {
318
+ imports: metadata.imports || [],
319
+ types: metadata.types || []
320
+ },
321
+ bundles: metadata.bundles || []
322
+ };
323
+ }
324
+
325
+ // Vector Embedding Persistence
326
+ saveEmbedding(chunkId, embedding, modelName) {
327
+ try {
328
+ const stmt = this.db.prepare(`
329
+ INSERT OR REPLACE INTO vector_embeddings (chunk_id, embedding, model_name)
330
+ VALUES (?, ?, ?)
331
+ `);
332
+ // Convert Float32Array to Buffer for SQLite BLOB
333
+ const buffer = Buffer.from(embedding.buffer);
334
+ stmt.run(chunkId, buffer, modelName);
335
+ return true;
336
+ } catch (error) {
337
+ console.error(`Failed to save embedding for ${chunkId}:`, error.message);
338
+ return false;
339
+ }
340
+ }
341
+
342
+ getEmbedding(chunkId) {
343
+ try {
344
+ const row = this.db.prepare('SELECT embedding FROM vector_embeddings WHERE chunk_id = ?').get(chunkId);
345
+ if (!row) return null;
346
+ // Convert Buffer back to Float32Array
347
+ return new Float32Array(row.embedding.buffer, row.embedding.byteOffset, row.embedding.byteLength / 4);
348
+ } catch (error) {
349
+ return null;
350
+ }
351
+ }
352
+
353
+ // Agent Memory Methods
354
+ createSession(id, title) {
355
+ try {
356
+ const stmt = this.db.prepare('INSERT OR REPLACE INTO agent_sessions (id, title) VALUES (?, ?)');
357
+ stmt.run(id, title);
358
+ return true;
359
+ } catch (error) {
360
+ return false;
361
+ }
362
+ }
363
+
364
+ addMessage(sessionId, role, content, metadata = {}) {
365
+ try {
366
+ const stmt = this.db.prepare(`
367
+ INSERT INTO agent_memory (session_id, role, content, metadata)
368
+ VALUES (?, ?, ?, ?)
369
+ `);
370
+ stmt.run(sessionId, role, content, JSON.stringify(metadata));
371
+
372
+ // Update session last_active_at
373
+ this.db.prepare('UPDATE agent_sessions SET last_active_at = CURRENT_TIMESTAMP WHERE id = ?').run(sessionId);
374
+ return true;
375
+ } catch (error) {
376
+ return false;
377
+ }
378
+ }
379
+
380
+ getSessionHistory(sessionId) {
381
+ try {
382
+ return this.db.prepare('SELECT * FROM agent_memory WHERE session_id = ? ORDER BY timestamp ASC').all(sessionId);
383
+ } catch (error) {
384
+ return [];
385
+ }
386
+ }
387
+
388
+ // Close database connection
389
+ close() {
390
+ if (this.db) {
391
+ this.db.close();
392
+ if (this.verbose) {
393
+ console.log('📊 SQLite database connection closed');
394
+ }
395
+ }
396
+ }
397
+ }
@@ -12,6 +12,23 @@ export default class FileSystemManager {
12
12
  this.verbose = options.verbose || false;
13
13
  this.watchers = [];
14
14
  this.ignorePatterns = [];
15
+ this.loadIgnorePatterns();
16
+ }
17
+
18
+ loadIgnorePatterns() {
19
+ const ignorePath = join(this.CWD, '.cntxignore');
20
+ if (existsSync(ignorePath)) {
21
+ try {
22
+ const content = readFileSync(ignorePath, 'utf8');
23
+ const patterns = content.split('\n')
24
+ .map(line => line.trim())
25
+ .filter(line => line && !line.startsWith('#'));
26
+ this.ignorePatterns = patterns;
27
+ if (this.verbose) console.log(`📋 Loaded ${patterns.length} patterns from .cntxignore`);
28
+ } catch (e) {
29
+ console.error('Failed to load .cntxignore:', e.message);
30
+ }
31
+ }
15
32
  }
16
33
 
17
34
  // === File Traversal ===