cntx-ui 3.0.7 → 3.0.9
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/dist/bin/cntx-ui.js +70 -0
- package/dist/lib/agent-runtime.js +269 -0
- package/dist/lib/agent-tools.js +162 -0
- package/dist/lib/api-router.js +387 -0
- package/dist/lib/bundle-manager.js +236 -0
- package/dist/lib/configuration-manager.js +230 -0
- package/dist/lib/database-manager.js +277 -0
- package/dist/lib/file-system-manager.js +305 -0
- package/dist/lib/function-level-chunker.js +144 -0
- package/dist/lib/heuristics-manager.js +491 -0
- package/dist/lib/mcp-server.js +159 -0
- package/dist/lib/mcp-transport.js +10 -0
- package/dist/lib/semantic-splitter.js +335 -0
- package/dist/lib/simple-vector-store.js +98 -0
- package/dist/lib/treesitter-semantic-chunker.js +277 -0
- package/dist/lib/websocket-manager.js +268 -0
- package/dist/server.js +225 -0
- package/package.json +18 -8
- package/bin/cntx-ui-mcp.sh +0 -3
- package/bin/cntx-ui.js +0 -123
- package/lib/agent-runtime.js +0 -371
- package/lib/agent-tools.js +0 -370
- package/lib/api-router.js +0 -1026
- package/lib/bundle-manager.js +0 -326
- package/lib/configuration-manager.js +0 -760
- package/lib/database-manager.js +0 -397
- package/lib/file-system-manager.js +0 -489
- package/lib/function-level-chunker.js +0 -406
- package/lib/heuristics-manager.js +0 -529
- package/lib/mcp-server.js +0 -1380
- package/lib/mcp-transport.js +0 -97
- package/lib/semantic-splitter.js +0 -304
- package/lib/simple-vector-store.js +0 -108
- package/lib/treesitter-semantic-chunker.js +0 -1485
- package/lib/websocket-manager.js +0 -470
- package/server.js +0 -687
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Manager for cntx-ui
|
|
3
|
+
* Handles all configuration files, settings, and persistence
|
|
4
|
+
*/
|
|
5
|
+
import { readFileSync, writeFileSync, existsSync, unlinkSync } from 'fs';
|
|
6
|
+
import { join, relative } from 'path';
|
|
7
|
+
import DatabaseManager from './database-manager.js';
|
|
8
|
+
export default class ConfigurationManager {
|
|
9
|
+
CWD;
|
|
10
|
+
CNTX_DIR;
|
|
11
|
+
CONFIG_FILE;
|
|
12
|
+
IGNORE_FILE;
|
|
13
|
+
BUNDLE_STATES_FILE;
|
|
14
|
+
HIDDEN_FILES_FILE;
|
|
15
|
+
SEMANTIC_CACHE_FILE;
|
|
16
|
+
bundleStates;
|
|
17
|
+
ignorePatterns;
|
|
18
|
+
hiddenFilesConfig;
|
|
19
|
+
editor;
|
|
20
|
+
verbose;
|
|
21
|
+
dbManager;
|
|
22
|
+
constructor(cwd, options = {}) {
|
|
23
|
+
this.CWD = cwd;
|
|
24
|
+
this.CNTX_DIR = join(cwd, '.cntx');
|
|
25
|
+
this.CONFIG_FILE = join(this.CNTX_DIR, 'config.json');
|
|
26
|
+
this.IGNORE_FILE = join(cwd, '.cntxignore');
|
|
27
|
+
this.BUNDLE_STATES_FILE = join(this.CNTX_DIR, 'bundle-states.json');
|
|
28
|
+
this.HIDDEN_FILES_FILE = join(this.CNTX_DIR, 'hidden-files.json');
|
|
29
|
+
this.SEMANTIC_CACHE_FILE = join(this.CNTX_DIR, 'semantic-cache.json');
|
|
30
|
+
this.bundleStates = new Map();
|
|
31
|
+
this.ignorePatterns = [];
|
|
32
|
+
this.hiddenFilesConfig = { hiddenFiles: [] };
|
|
33
|
+
this.editor = process.env.EDITOR || 'code';
|
|
34
|
+
this.verbose = options.verbose || false;
|
|
35
|
+
this.dbManager = new DatabaseManager(this.CNTX_DIR, { verbose: this.verbose });
|
|
36
|
+
// Load bundles (try SQLite first, fallback to JSON)
|
|
37
|
+
this.loadBundleStates();
|
|
38
|
+
}
|
|
39
|
+
// === Bundle Configuration ===
|
|
40
|
+
loadConfig() {
|
|
41
|
+
if (existsSync(this.CONFIG_FILE)) {
|
|
42
|
+
const config = JSON.parse(readFileSync(this.CONFIG_FILE, 'utf8'));
|
|
43
|
+
// Update in-memory bundle states from config patterns
|
|
44
|
+
if (config.bundles) {
|
|
45
|
+
Object.entries(config.bundles).forEach(([name, patterns]) => {
|
|
46
|
+
const existing = this.bundleStates.get(name) || { files: [], patterns: [] };
|
|
47
|
+
this.bundleStates.set(name, {
|
|
48
|
+
...existing,
|
|
49
|
+
patterns: patterns
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return config;
|
|
54
|
+
}
|
|
55
|
+
return { bundles: { master: ['**/*'] } };
|
|
56
|
+
}
|
|
57
|
+
saveConfig(config) {
|
|
58
|
+
writeFileSync(this.CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8');
|
|
59
|
+
}
|
|
60
|
+
// === Ignore File Management ===
|
|
61
|
+
saveCntxignore(content) {
|
|
62
|
+
try {
|
|
63
|
+
writeFileSync(this.IGNORE_FILE, content, 'utf8');
|
|
64
|
+
// Update ignore patterns in memory immediately
|
|
65
|
+
this.ignorePatterns = content.split('\n')
|
|
66
|
+
.map(line => line.trim())
|
|
67
|
+
.filter(line => line && !line.startsWith('#'));
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.error('Failed to save .cntxignore:', error.message);
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
getBundles() {
|
|
76
|
+
return this.bundleStates;
|
|
77
|
+
}
|
|
78
|
+
getIgnorePatterns() {
|
|
79
|
+
return this.ignorePatterns;
|
|
80
|
+
}
|
|
81
|
+
getHiddenFilesConfig() {
|
|
82
|
+
return this.hiddenFilesConfig;
|
|
83
|
+
}
|
|
84
|
+
getEditor() {
|
|
85
|
+
return this.editor;
|
|
86
|
+
}
|
|
87
|
+
// === Persistence ===
|
|
88
|
+
loadBundleStates() {
|
|
89
|
+
// 1. Try to load from SQLite first
|
|
90
|
+
try {
|
|
91
|
+
const rows = this.dbManager.db.prepare('SELECT * FROM bundles').all();
|
|
92
|
+
if (rows.length > 0) {
|
|
93
|
+
rows.forEach(row => {
|
|
94
|
+
this.bundleStates.set(row.name, {
|
|
95
|
+
patterns: JSON.parse(row.patterns),
|
|
96
|
+
files: JSON.parse(row.files),
|
|
97
|
+
size: row.size,
|
|
98
|
+
fileCount: row.file_count,
|
|
99
|
+
generated: row.generated_at,
|
|
100
|
+
changed: row.changed === 1
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
if (this.verbose)
|
|
104
|
+
console.log('✅ Loaded bundles from SQLite database');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
// Fallback to JSON
|
|
110
|
+
}
|
|
111
|
+
// 2. Fallback to legacy JSON file
|
|
112
|
+
if (existsSync(this.BUNDLE_STATES_FILE)) {
|
|
113
|
+
try {
|
|
114
|
+
const data = readFileSync(this.BUNDLE_STATES_FILE, 'utf8');
|
|
115
|
+
const bundleStates = JSON.parse(data);
|
|
116
|
+
bundleStates.forEach(state => {
|
|
117
|
+
this.bundleStates.set(state.name, {
|
|
118
|
+
patterns: state.patterns || [],
|
|
119
|
+
files: (state.files || []).map((file) => {
|
|
120
|
+
if (file.startsWith('/')) {
|
|
121
|
+
const relativePath = relative(this.CWD, file);
|
|
122
|
+
return relativePath;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
return file;
|
|
126
|
+
}
|
|
127
|
+
}),
|
|
128
|
+
size: state.size || 0,
|
|
129
|
+
fileCount: (state.files || []).length,
|
|
130
|
+
generated: state.generated,
|
|
131
|
+
changed: state.changed || false
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
console.error('Error loading bundle states:', error);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
saveBundleStates() {
|
|
141
|
+
// 1. Save to SQLite
|
|
142
|
+
const stmt = this.dbManager.db.prepare(`
|
|
143
|
+
INSERT OR REPLACE INTO bundles (name, patterns, files, size, file_count, generated_at, changed)
|
|
144
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
145
|
+
`);
|
|
146
|
+
const transaction = this.dbManager.db.transaction(() => {
|
|
147
|
+
for (const [name, bundle] of this.bundleStates.entries()) {
|
|
148
|
+
stmt.run(name, JSON.stringify(bundle.patterns), JSON.stringify(bundle.files), bundle.size || 0, bundle.files.length, bundle.generated || '', bundle.changed ? 1 : 0);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
try {
|
|
152
|
+
transaction();
|
|
153
|
+
if (this.verbose)
|
|
154
|
+
console.log('💾 Saved bundles to SQLite');
|
|
155
|
+
}
|
|
156
|
+
catch (e) {
|
|
157
|
+
console.error('Failed to save bundles to SQLite:', e.message);
|
|
158
|
+
}
|
|
159
|
+
// 2. Also save to JSON for backup/legacy compatibility
|
|
160
|
+
try {
|
|
161
|
+
const data = Array.from(this.bundleStates.entries()).map(([name, bundle]) => ({
|
|
162
|
+
name,
|
|
163
|
+
...bundle
|
|
164
|
+
}));
|
|
165
|
+
writeFileSync(this.BUNDLE_STATES_FILE, JSON.stringify(data, null, 2), 'utf8');
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
console.error('Error saving bundle states:', error);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
loadHiddenFilesConfig() {
|
|
172
|
+
if (existsSync(this.HIDDEN_FILES_FILE)) {
|
|
173
|
+
try {
|
|
174
|
+
const data = readFileSync(this.HIDDEN_FILES_FILE, 'utf8');
|
|
175
|
+
this.hiddenFilesConfig = JSON.parse(data);
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
console.error('Error loading hidden files config:', error);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
isFileHidden(filePath, bundleName) {
|
|
183
|
+
const bundleHidden = this.hiddenFilesConfig[bundleName] || [];
|
|
184
|
+
return bundleHidden.includes(filePath);
|
|
185
|
+
}
|
|
186
|
+
loadIgnorePatterns() {
|
|
187
|
+
const ignorePath = join(this.CWD, '.cntxignore');
|
|
188
|
+
if (existsSync(ignorePath)) {
|
|
189
|
+
try {
|
|
190
|
+
const content = readFileSync(ignorePath, 'utf8');
|
|
191
|
+
this.ignorePatterns = content.split('\n')
|
|
192
|
+
.map(line => line.trim())
|
|
193
|
+
.filter(line => line && !line.startsWith('#'));
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
console.error('Error loading ignore patterns:', error);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// === Semantic Cache ===
|
|
201
|
+
saveSemanticCache(analysis) {
|
|
202
|
+
try {
|
|
203
|
+
const data = {
|
|
204
|
+
timestamp: Date.now(),
|
|
205
|
+
analysis
|
|
206
|
+
};
|
|
207
|
+
writeFileSync(this.SEMANTIC_CACHE_FILE, JSON.stringify(data), 'utf8');
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
console.error('Error saving semantic cache:', error);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
loadSemanticCache() {
|
|
214
|
+
if (existsSync(this.SEMANTIC_CACHE_FILE)) {
|
|
215
|
+
try {
|
|
216
|
+
const data = readFileSync(this.SEMANTIC_CACHE_FILE, 'utf8');
|
|
217
|
+
return JSON.parse(data);
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
invalidateSemanticCache() {
|
|
226
|
+
if (existsSync(this.SEMANTIC_CACHE_FILE)) {
|
|
227
|
+
unlinkSync(this.SEMANTIC_CACHE_FILE);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Manager for cntx-ui
|
|
3
|
+
* Handles SQLite operations for bundle and file data
|
|
4
|
+
*/
|
|
5
|
+
import Database from 'better-sqlite3';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { statSync } from 'fs';
|
|
8
|
+
export default class DatabaseManager {
|
|
9
|
+
dbPath;
|
|
10
|
+
db;
|
|
11
|
+
verbose;
|
|
12
|
+
constructor(dbDir, options = {}) {
|
|
13
|
+
this.dbPath = join(dbDir, 'bundles.db');
|
|
14
|
+
this.verbose = options.verbose || false;
|
|
15
|
+
try {
|
|
16
|
+
this.db = new Database(this.dbPath);
|
|
17
|
+
if (this.verbose) {
|
|
18
|
+
console.log(`📊 SQLite database initialized: ${this.dbPath}`);
|
|
19
|
+
}
|
|
20
|
+
this.initSchema();
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
console.error('Failed to initialize SQLite database:', error.message);
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
initSchema() {
|
|
28
|
+
this.db.exec(`
|
|
29
|
+
-- Existing bundles table
|
|
30
|
+
CREATE TABLE IF NOT EXISTS bundles (
|
|
31
|
+
name TEXT PRIMARY KEY,
|
|
32
|
+
patterns TEXT NOT NULL,
|
|
33
|
+
files TEXT NOT NULL,
|
|
34
|
+
size INTEGER DEFAULT 0,
|
|
35
|
+
file_count INTEGER DEFAULT 0,
|
|
36
|
+
generated_at TEXT,
|
|
37
|
+
changed BOOLEAN DEFAULT FALSE
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
-- Persistent Semantic Chunks
|
|
41
|
+
CREATE TABLE IF NOT EXISTS semantic_chunks (
|
|
42
|
+
id TEXT PRIMARY KEY,
|
|
43
|
+
name TEXT NOT NULL,
|
|
44
|
+
file_path TEXT NOT NULL,
|
|
45
|
+
type TEXT,
|
|
46
|
+
subtype TEXT,
|
|
47
|
+
content TEXT NOT NULL,
|
|
48
|
+
start_line INTEGER,
|
|
49
|
+
complexity_score INTEGER,
|
|
50
|
+
purpose TEXT,
|
|
51
|
+
metadata TEXT, -- JSON string for tags, imports, types, etc.
|
|
52
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
-- Vector Embeddings (Persistence for RAG)
|
|
56
|
+
CREATE TABLE IF NOT EXISTS vector_embeddings (
|
|
57
|
+
chunk_id TEXT PRIMARY KEY,
|
|
58
|
+
embedding BLOB NOT NULL, -- Stored as Float32Array blob
|
|
59
|
+
model_name TEXT NOT NULL,
|
|
60
|
+
FOREIGN KEY(chunk_id) REFERENCES semantic_chunks(id) ON DELETE CASCADE
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
-- Agent Working Memory & Sessions
|
|
64
|
+
CREATE TABLE IF NOT EXISTS agent_sessions (
|
|
65
|
+
id TEXT PRIMARY KEY,
|
|
66
|
+
title TEXT,
|
|
67
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
68
|
+
last_active_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
69
|
+
context_summary TEXT -- High-level summary of what was being worked on
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
CREATE TABLE IF NOT EXISTS agent_memory (
|
|
73
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
74
|
+
session_id TEXT NOT NULL,
|
|
75
|
+
role TEXT NOT NULL, -- 'user' or 'agent'
|
|
76
|
+
content TEXT NOT NULL,
|
|
77
|
+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
78
|
+
metadata TEXT, -- JSON for tools used, files referenced, etc.
|
|
79
|
+
FOREIGN KEY(session_id) REFERENCES agent_sessions(id) ON DELETE CASCADE
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
CREATE INDEX IF NOT EXISTS idx_bundles_changed ON bundles(changed);
|
|
83
|
+
CREATE INDEX IF NOT EXISTS idx_chunks_file ON semantic_chunks(file_path);
|
|
84
|
+
CREATE INDEX IF NOT EXISTS idx_chunks_purpose ON semantic_chunks(purpose);
|
|
85
|
+
CREATE INDEX IF NOT EXISTS idx_memory_session ON agent_memory(session_id);
|
|
86
|
+
`);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Run a raw SELECT query against the database
|
|
90
|
+
*/
|
|
91
|
+
query(sql) {
|
|
92
|
+
try {
|
|
93
|
+
if (!sql.trim().toUpperCase().startsWith('SELECT')) {
|
|
94
|
+
throw new Error('Only SELECT queries are allowed');
|
|
95
|
+
}
|
|
96
|
+
const stmt = this.db.prepare(sql);
|
|
97
|
+
return stmt.all();
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
console.error('Query failed:', error.message);
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Get database info for debugging
|
|
105
|
+
getInfo() {
|
|
106
|
+
try {
|
|
107
|
+
const bundleCountRow = this.db.prepare('SELECT COUNT(*) as count FROM bundles').get();
|
|
108
|
+
const chunkCountRow = this.db.prepare('SELECT COUNT(*) as count FROM semantic_chunks').get();
|
|
109
|
+
const embeddingCountRow = this.db.prepare('SELECT COUNT(*) as count FROM vector_embeddings').get();
|
|
110
|
+
const sessionCountRow = this.db.prepare('SELECT COUNT(*) as count FROM agent_sessions').get();
|
|
111
|
+
const dbSize = statSync(this.dbPath).size;
|
|
112
|
+
return {
|
|
113
|
+
path: this.dbPath,
|
|
114
|
+
bundleCount: bundleCountRow.count,
|
|
115
|
+
chunkCount: chunkCountRow.count,
|
|
116
|
+
embeddingCount: embeddingCountRow.count,
|
|
117
|
+
sessionCount: sessionCountRow.count,
|
|
118
|
+
sizeBytes: dbSize,
|
|
119
|
+
sizeFormatted: (dbSize / 1024).toFixed(1) + ' KB',
|
|
120
|
+
tables: ['bundles', 'semantic_chunks', 'vector_embeddings', 'agent_sessions', 'agent_memory']
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
return { error: error.message };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Save semantic chunks
|
|
128
|
+
saveChunks(chunks) {
|
|
129
|
+
const stmt = this.db.prepare(`
|
|
130
|
+
INSERT OR REPLACE INTO semantic_chunks (id, name, file_path, type, subtype, content, start_line, complexity_score, purpose, metadata)
|
|
131
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
132
|
+
`);
|
|
133
|
+
const transaction = this.db.transaction(() => {
|
|
134
|
+
for (const chunk of chunks) {
|
|
135
|
+
// Generate a stable ID if not provided (org/repo:path:name)
|
|
136
|
+
const id = chunk.id || `${chunk.filePath}:${chunk.name}:${chunk.startLine}`;
|
|
137
|
+
stmt.run(id, chunk.name, chunk.filePath, chunk.type || 'unknown', chunk.subtype || 'unknown', chunk.code || chunk.content || '', chunk.startLine || 0, chunk.complexity?.score || 0, chunk.purpose || 'Utility function', JSON.stringify({
|
|
138
|
+
tags: chunk.tags || [],
|
|
139
|
+
businessDomain: chunk.businessDomain || [],
|
|
140
|
+
technicalPatterns: chunk.technicalPatterns || [],
|
|
141
|
+
imports: chunk.includes?.imports || [],
|
|
142
|
+
types: chunk.includes?.types || [],
|
|
143
|
+
bundles: chunk.bundles || []
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
try {
|
|
148
|
+
transaction();
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
console.error('Failed to save chunks to SQLite:', error.message);
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Get chunks for a specific file
|
|
157
|
+
getChunksByFile(filePath) {
|
|
158
|
+
try {
|
|
159
|
+
const rows = this.db.prepare('SELECT * FROM semantic_chunks WHERE file_path = ?').all(filePath);
|
|
160
|
+
return rows.map(row => this.mapChunkRow(row));
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Search chunks by name or purpose
|
|
167
|
+
searchChunks(query) {
|
|
168
|
+
try {
|
|
169
|
+
const rows = this.db.prepare(`
|
|
170
|
+
SELECT * FROM semantic_chunks
|
|
171
|
+
WHERE name LIKE ? OR purpose LIKE ?
|
|
172
|
+
LIMIT 50
|
|
173
|
+
`).all(`%${query}%`, `%${query}%`);
|
|
174
|
+
return rows.map(row => this.mapChunkRow(row));
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
return [];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
mapChunkRow(row) {
|
|
181
|
+
const metadata = JSON.parse(row.metadata || '{}');
|
|
182
|
+
return {
|
|
183
|
+
id: row.id,
|
|
184
|
+
name: row.name,
|
|
185
|
+
filePath: row.file_path,
|
|
186
|
+
type: row.type,
|
|
187
|
+
subtype: row.subtype,
|
|
188
|
+
code: row.content,
|
|
189
|
+
startLine: row.start_line,
|
|
190
|
+
complexity: {
|
|
191
|
+
score: row.complexity_score,
|
|
192
|
+
level: row.complexity_score < 5 ? 'low' : row.complexity_score < 15 ? 'medium' : 'high'
|
|
193
|
+
},
|
|
194
|
+
purpose: row.purpose,
|
|
195
|
+
tags: metadata.tags || [],
|
|
196
|
+
businessDomain: metadata.businessDomain || [],
|
|
197
|
+
technicalPatterns: metadata.technicalPatterns || [],
|
|
198
|
+
includes: {
|
|
199
|
+
imports: metadata.imports || [],
|
|
200
|
+
types: metadata.types || []
|
|
201
|
+
},
|
|
202
|
+
bundles: metadata.bundles || []
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
// Vector Embedding Persistence
|
|
206
|
+
saveEmbedding(chunkId, embedding, modelName) {
|
|
207
|
+
try {
|
|
208
|
+
const stmt = this.db.prepare(`
|
|
209
|
+
INSERT OR REPLACE INTO vector_embeddings (chunk_id, embedding, model_name)
|
|
210
|
+
VALUES (?, ?, ?)
|
|
211
|
+
`);
|
|
212
|
+
// Convert Float32Array to Buffer for SQLite BLOB
|
|
213
|
+
const buffer = Buffer.from(embedding.buffer);
|
|
214
|
+
stmt.run(chunkId, buffer, modelName);
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
console.error(`Failed to save embedding for ${chunkId}:`, error.message);
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
getEmbedding(chunkId) {
|
|
223
|
+
try {
|
|
224
|
+
const row = this.db.prepare('SELECT embedding FROM vector_embeddings WHERE chunk_id = ?').get(chunkId);
|
|
225
|
+
if (!row)
|
|
226
|
+
return null;
|
|
227
|
+
// Convert Buffer back to Float32Array
|
|
228
|
+
return new Float32Array(row.embedding.buffer, row.embedding.byteOffset, row.embedding.byteLength / 4);
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// Agent Memory Methods
|
|
235
|
+
createSession(id, title) {
|
|
236
|
+
try {
|
|
237
|
+
const stmt = this.db.prepare('INSERT OR REPLACE INTO agent_sessions (id, title) VALUES (?, ?)');
|
|
238
|
+
stmt.run(id, title);
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
addMessage(sessionId, role, content, metadata = {}) {
|
|
246
|
+
try {
|
|
247
|
+
const stmt = this.db.prepare(`
|
|
248
|
+
INSERT INTO agent_memory (session_id, role, content, metadata)
|
|
249
|
+
VALUES (?, ?, ?, ?)
|
|
250
|
+
`);
|
|
251
|
+
stmt.run(sessionId, role, content, JSON.stringify(metadata));
|
|
252
|
+
// Update session last_active_at
|
|
253
|
+
this.db.prepare('UPDATE agent_sessions SET last_active_at = CURRENT_TIMESTAMP WHERE id = ?').run(sessionId);
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
getSessionHistory(sessionId) {
|
|
261
|
+
try {
|
|
262
|
+
return this.db.prepare('SELECT * FROM agent_memory WHERE session_id = ? ORDER BY timestamp ASC').all(sessionId);
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
return [];
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
// Close database connection
|
|
269
|
+
close() {
|
|
270
|
+
if (this.db) {
|
|
271
|
+
this.db.close();
|
|
272
|
+
if (this.verbose) {
|
|
273
|
+
console.log('📊 SQLite database connection closed');
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|