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.
- package/README.md +40 -344
- package/bin/cntx-ui-mcp.sh +3 -0
- package/bin/cntx-ui.js +2 -1
- package/lib/agent-runtime.js +161 -1340
- package/lib/agent-tools.js +9 -7
- package/lib/api-router.js +262 -79
- package/lib/bundle-manager.js +172 -407
- package/lib/configuration-manager.js +94 -59
- package/lib/database-manager.js +397 -0
- package/lib/file-system-manager.js +17 -0
- package/lib/heuristics-manager.js +119 -17
- package/lib/mcp-server.js +125 -55
- package/lib/semantic-splitter.js +222 -481
- package/lib/simple-vector-store.js +69 -300
- package/package.json +18 -31
- package/server.js +151 -73
- package/templates/TOOLS.md +41 -0
- package/templates/activities/activities/create-project-bundles/README.md +4 -3
- package/templates/activities/activities/create-project-bundles/notes.md +15 -19
- package/templates/activities/activities/create-project-bundles/tasks.md +4 -4
- package/templates/activities/activities.json +1 -1
- package/templates/agent-config.yaml +0 -13
- package/templates/agent-instructions.md +22 -6
- package/templates/agent-rules/capabilities/bundle-system.md +1 -1
- package/templates/agent-rules/project-specific/architecture.md +1 -1
- package/web/dist/assets/index-B2OdTzzI.css +1 -0
- package/web/dist/assets/index-D0tBsKiR.js +2016 -0
- package/web/dist/index.html +2 -2
- package/mcp-config-example.json +0 -9
- package/web/dist/assets/heuristics-manager-browser-DfonOP5I.js +0 -1
- package/web/dist/assets/index-dF3qg-y_.js +0 -2486
- package/web/dist/assets/index-h5FGSg_P.css +0 -1
|
@@ -1,329 +1,98 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Simple
|
|
3
|
-
*
|
|
2
|
+
* Simple Vector Store with SQLite Persistence
|
|
3
|
+
* Powered by Transformers.js for local embeddings
|
|
4
|
+
* Persists vectors to SQLite for instant startup
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
import { pipeline } from '@xenova/transformers'
|
|
7
|
+
import { pipeline } from '@xenova/transformers';
|
|
7
8
|
|
|
8
|
-
class SimpleVectorStore {
|
|
9
|
-
constructor(options = {}) {
|
|
10
|
-
this.
|
|
11
|
-
this.
|
|
12
|
-
this.
|
|
13
|
-
this.
|
|
9
|
+
export default class SimpleVectorStore {
|
|
10
|
+
constructor(databaseManager, options = {}) {
|
|
11
|
+
this.db = databaseManager;
|
|
12
|
+
this.modelName = options.modelName || 'Xenova/all-MiniLM-L6-v2';
|
|
13
|
+
this.pipe = null;
|
|
14
|
+
this.initialized = false;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
/**
|
|
17
|
-
* Initialize the embedding model
|
|
18
|
-
*/
|
|
19
|
-
async initialize() {
|
|
20
|
-
console.log('🔧 Initializing simple vector store...')
|
|
21
|
-
|
|
22
|
-
// Load the embedding model (downloads on first run)
|
|
23
|
-
this.embedder = await pipeline('feature-extraction', this.modelName)
|
|
24
|
-
console.log('✅ Embedding model loaded')
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Alias for initialize() to match server expectations
|
|
29
|
-
*/
|
|
30
17
|
async init() {
|
|
31
|
-
|
|
18
|
+
if (this.initialized) return;
|
|
19
|
+
console.log(`🤖 Initializing local RAG engine (${this.modelName})...`);
|
|
20
|
+
this.pipe = await pipeline('feature-extraction', this.modelName);
|
|
21
|
+
this.initialized = true;
|
|
22
|
+
console.log('✅ Local RAG engine ready');
|
|
32
23
|
}
|
|
33
24
|
|
|
34
|
-
/**
|
|
35
|
-
* Generate embedding for a single text
|
|
36
|
-
*/
|
|
37
25
|
async generateEmbedding(text) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
const result = await this.embedder(text, {
|
|
44
|
-
pooling: 'mean',
|
|
45
|
-
normalize: true
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
return Array.from(result.data)
|
|
49
|
-
} catch (error) {
|
|
50
|
-
console.error('❌ Failed to generate embedding:', error.message)
|
|
51
|
-
throw error
|
|
52
|
-
}
|
|
26
|
+
await this.init();
|
|
27
|
+
const output = await this.pipe(text, { pooling: 'mean', normalize: true });
|
|
28
|
+
return new Float32Array(output.data);
|
|
53
29
|
}
|
|
54
30
|
|
|
55
31
|
/**
|
|
56
|
-
*
|
|
32
|
+
* Upsert a chunk's embedding to persistence
|
|
57
33
|
*/
|
|
58
|
-
async
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
console.log(`🔍 Creating embeddings for ${chunks.length} chunks...`)
|
|
64
|
-
|
|
65
|
-
const embeddings = []
|
|
66
|
-
const batchSize = 10 // Process in batches to avoid memory issues
|
|
67
|
-
|
|
68
|
-
for (let i = 0; i < chunks.length; i += batchSize) {
|
|
69
|
-
const batch = chunks.slice(i, i + batchSize)
|
|
70
|
-
console.log(`📦 Processing batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(chunks.length / batchSize)}`)
|
|
71
|
-
|
|
72
|
-
for (const chunk of batch) {
|
|
73
|
-
try {
|
|
74
|
-
// Create searchable text from chunk data
|
|
75
|
-
const searchableText = this.createSearchableText(chunk)
|
|
76
|
-
|
|
77
|
-
// Generate embedding
|
|
78
|
-
const result = await this.embedder(searchableText, {
|
|
79
|
-
pooling: 'mean',
|
|
80
|
-
normalize: true
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
// Convert to array format
|
|
84
|
-
const embedding = Array.from(result.data)
|
|
85
|
-
|
|
86
|
-
embeddings.push({
|
|
87
|
-
id: chunk.name && chunk.filePath
|
|
88
|
-
? `${chunk.name}:${chunk.filePath}:${chunk.startLine || ''}`
|
|
89
|
-
: `chunk-${i}`,
|
|
90
|
-
embedding: embedding,
|
|
91
|
-
metadata: {
|
|
92
|
-
content: chunk.code || '',
|
|
93
|
-
semanticType: chunk.semanticType || '',
|
|
94
|
-
businessDomain: chunk.businessDomain || [],
|
|
95
|
-
technicalPatterns: chunk.technicalPatterns || [],
|
|
96
|
-
purpose: chunk.purpose || '',
|
|
97
|
-
files: chunk.files || [],
|
|
98
|
-
size: chunk.size || 0,
|
|
99
|
-
complexity: chunk.complexity || 0
|
|
100
|
-
}
|
|
101
|
-
})
|
|
102
|
-
} catch (error) {
|
|
103
|
-
console.warn(`⚠️ Failed to embed chunk ${chunk.name}:`, error.message)
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
console.log(`✅ Created ${embeddings.length} embeddings`)
|
|
109
|
-
return embeddings
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Create searchable text from semantic chunk
|
|
114
|
-
*/
|
|
115
|
-
createSearchableText(chunk) {
|
|
116
|
-
const parts = []
|
|
117
|
-
|
|
118
|
-
// Add code content if available
|
|
119
|
-
if (chunk.code) {
|
|
120
|
-
parts.push(chunk.code)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Add semantic type
|
|
124
|
-
if (chunk.semanticType) {
|
|
125
|
-
parts.push(`Type: ${chunk.semanticType}`)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Add business domains
|
|
129
|
-
if (chunk.businessDomain && chunk.businessDomain.length > 0) {
|
|
130
|
-
parts.push(`Domain: ${chunk.businessDomain.join(', ')}`)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Add technical patterns
|
|
134
|
-
if (chunk.technicalPatterns && chunk.technicalPatterns.length > 0) {
|
|
135
|
-
parts.push(`Patterns: ${chunk.technicalPatterns.join(', ')}`)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Add purpose
|
|
139
|
-
if (chunk.purpose) {
|
|
140
|
-
parts.push(`Purpose: ${chunk.purpose}`)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Add file names for context
|
|
144
|
-
if (chunk.files && chunk.files.length > 0) {
|
|
145
|
-
const fileNames = chunk.files.map(f => f.split('/').pop()).join(', ')
|
|
146
|
-
parts.push(`Files: ${fileNames}`)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return parts.join(' | ')
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Store chunks in vector database
|
|
154
|
-
*/
|
|
155
|
-
async storeChunks(chunks) {
|
|
156
|
-
const embeddings = await this.createEmbeddings(chunks)
|
|
157
|
-
|
|
158
|
-
if (embeddings.length === 0) {
|
|
159
|
-
console.warn('⚠️ No embeddings created, skipping storage')
|
|
160
|
-
return
|
|
161
|
-
}
|
|
34
|
+
async upsertChunk(chunk) {
|
|
35
|
+
const chunkId = chunk.id;
|
|
36
|
+
// Check if we already have it in DB
|
|
37
|
+
const existing = this.db.getEmbedding(chunkId);
|
|
38
|
+
if (existing) return existing;
|
|
162
39
|
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
console.log(`✅ Stored ${embeddings.length} chunks in memory vector store`)
|
|
40
|
+
// Generate new embedding
|
|
41
|
+
const textToEmbed = `${chunk.name} ${chunk.purpose} ${chunk.code}`;
|
|
42
|
+
const embedding = await this.generateEmbedding(textToEmbed);
|
|
43
|
+
|
|
44
|
+
// Save to SQLite
|
|
45
|
+
this.db.saveEmbedding(chunkId, embedding, this.modelName);
|
|
46
|
+
return embedding;
|
|
172
47
|
}
|
|
173
48
|
|
|
174
49
|
/**
|
|
175
|
-
*
|
|
50
|
+
* Semantic Search across persistent embeddings
|
|
176
51
|
*/
|
|
177
|
-
async
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
complexity: chunk.complexity || 0
|
|
197
|
-
}
|
|
198
|
-
})
|
|
199
|
-
storedCount++
|
|
52
|
+
async search(query, options = {}) {
|
|
53
|
+
const { limit = 10, threshold = 0.5 } = options;
|
|
54
|
+
const queryEmbedding = await this.generateEmbedding(query);
|
|
55
|
+
|
|
56
|
+
// Load all embeddings from DB
|
|
57
|
+
// Optimization: In a huge codebase, we'd use a real vector DB or FAISS
|
|
58
|
+
// For now, SQLite + Manual Cosine Similarity is fine for local repos
|
|
59
|
+
const rows = this.db.db.prepare('SELECT chunk_id, embedding FROM vector_embeddings WHERE model_name = ?').all(this.modelName);
|
|
60
|
+
|
|
61
|
+
const results = [];
|
|
62
|
+
for (const row of rows) {
|
|
63
|
+
const embedding = new Float32Array(row.embedding.buffer, row.embedding.byteOffset, row.embedding.byteLength / 4);
|
|
64
|
+
const similarity = this.cosineSimilarity(queryEmbedding, embedding);
|
|
65
|
+
|
|
66
|
+
if (similarity >= threshold) {
|
|
67
|
+
results.push({
|
|
68
|
+
chunkId: row.chunk_id,
|
|
69
|
+
similarity
|
|
70
|
+
});
|
|
200
71
|
}
|
|
201
72
|
}
|
|
202
73
|
|
|
203
|
-
|
|
204
|
-
return
|
|
74
|
+
// Sort by similarity and get chunk details
|
|
75
|
+
return results
|
|
76
|
+
.sort((a, b) => b.similarity - a.similarity)
|
|
77
|
+
.slice(0, limit)
|
|
78
|
+
.map(res => {
|
|
79
|
+
const chunk = this.db.db.prepare('SELECT * FROM semantic_chunks WHERE id = ?').get(res.chunkId);
|
|
80
|
+
return {
|
|
81
|
+
...this.db.mapChunkRow(chunk),
|
|
82
|
+
similarity: res.similarity
|
|
83
|
+
};
|
|
84
|
+
});
|
|
205
85
|
}
|
|
206
86
|
|
|
207
|
-
/**
|
|
208
|
-
* Calculate cosine similarity between two vectors
|
|
209
|
-
*/
|
|
210
87
|
cosineSimilarity(vecA, vecB) {
|
|
211
|
-
let dotProduct = 0
|
|
212
|
-
let normA = 0
|
|
213
|
-
let normB = 0
|
|
214
|
-
|
|
88
|
+
let dotProduct = 0;
|
|
89
|
+
let normA = 0;
|
|
90
|
+
let normB = 0;
|
|
215
91
|
for (let i = 0; i < vecA.length; i++) {
|
|
216
|
-
dotProduct += vecA[i] * vecB[i]
|
|
217
|
-
normA += vecA[i] * vecA[i]
|
|
218
|
-
normB += vecB[i] * vecB[i]
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB))
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Find similar code chunks
|
|
226
|
-
*/
|
|
227
|
-
async findSimilar(query, options = {}) {
|
|
228
|
-
if (!this.embedder) {
|
|
229
|
-
await this.initialize()
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const {
|
|
233
|
-
limit = 10,
|
|
234
|
-
minSimilarity = 0.5
|
|
235
|
-
} = options
|
|
236
|
-
|
|
237
|
-
try {
|
|
238
|
-
// Create embedding for the query
|
|
239
|
-
const queryEmbedding = await this.embedder(query, {
|
|
240
|
-
pooling: 'mean',
|
|
241
|
-
normalize: true
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
const queryVector = Array.from(queryEmbedding.data)
|
|
245
|
-
|
|
246
|
-
// Calculate similarities with all stored vectors
|
|
247
|
-
const similarities = []
|
|
248
|
-
for (const [id, vector] of this.vectors) {
|
|
249
|
-
const similarity = this.cosineSimilarity(queryVector, vector.embedding)
|
|
250
|
-
if (similarity >= minSimilarity) {
|
|
251
|
-
similarities.push({
|
|
252
|
-
id: id,
|
|
253
|
-
similarity: similarity,
|
|
254
|
-
metadata: vector.metadata
|
|
255
|
-
})
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Sort by similarity and return top results
|
|
260
|
-
return similarities
|
|
261
|
-
.sort((a, b) => b.similarity - a.similarity)
|
|
262
|
-
.slice(0, limit)
|
|
263
|
-
} catch (error) {
|
|
264
|
-
console.error('❌ Search failed:', error)
|
|
265
|
-
return []
|
|
92
|
+
dotProduct += vecA[i] * vecB[i];
|
|
93
|
+
normA += vecA[i] * vecA[i];
|
|
94
|
+
normB += vecB[i] * vecB[i];
|
|
266
95
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Find chunks by semantic type
|
|
271
|
-
*/
|
|
272
|
-
async findByType(semanticType, limit = 10) {
|
|
273
|
-
return this.findSimilar(`Type: ${semanticType}`, {
|
|
274
|
-
limit,
|
|
275
|
-
minSimilarity: 0.7
|
|
276
|
-
})
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Find chunks by business domain
|
|
281
|
-
*/
|
|
282
|
-
async findByDomain(domain, limit = 10) {
|
|
283
|
-
return this.findSimilar(`Domain: ${domain}`, {
|
|
284
|
-
limit,
|
|
285
|
-
minSimilarity: 0.6
|
|
286
|
-
})
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Find chunks by technical pattern
|
|
291
|
-
*/
|
|
292
|
-
async findByPattern(pattern, limit = 10) {
|
|
293
|
-
return this.findSimilar(`Patterns: ${pattern}`, {
|
|
294
|
-
limit,
|
|
295
|
-
minSimilarity: 0.6
|
|
296
|
-
})
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Get collection statistics
|
|
301
|
-
*/
|
|
302
|
-
async getStats() {
|
|
303
|
-
return {
|
|
304
|
-
totalChunks: this.vectors.size,
|
|
305
|
-
collectionName: this.collectionName,
|
|
306
|
-
modelName: this.modelName
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Clear all stored chunks
|
|
312
|
-
*/
|
|
313
|
-
async clear() {
|
|
314
|
-
this.vectors.clear()
|
|
315
|
-
console.log('✅ Cleared in-memory vector store')
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Add/update a single chunk (legacy compatibility)
|
|
320
|
-
*/
|
|
321
|
-
async upsert(id, embedding, metadata) {
|
|
322
|
-
this.vectors.set(id, {
|
|
323
|
-
embedding: embedding,
|
|
324
|
-
metadata: metadata
|
|
325
|
-
})
|
|
96
|
+
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
326
97
|
}
|
|
327
98
|
}
|
|
328
|
-
|
|
329
|
-
export default SimpleVectorStore
|
package/package.json
CHANGED
|
@@ -1,24 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cntx-ui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "
|
|
5
|
-
"description": "
|
|
4
|
+
"version": "3.0.1",
|
|
5
|
+
"description": "Autonomous Repository Intelligence engine with web UI and MCP server. Unified semantic code understanding, local RAG, and agent working memory.",
|
|
6
6
|
"keywords": [
|
|
7
|
-
"
|
|
7
|
+
"repository-intelligence",
|
|
8
|
+
"semantic-code-search",
|
|
9
|
+
"ai-agent-context",
|
|
8
10
|
"mcp-server",
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"file-aggregation",
|
|
15
|
-
"project-context",
|
|
16
|
-
"ai-workflow",
|
|
17
|
-
"codebase-bundling",
|
|
18
|
-
"development-tools",
|
|
19
|
-
"websocket",
|
|
20
|
-
"react",
|
|
21
|
-
"cli-tool"
|
|
11
|
+
"local-rag",
|
|
12
|
+
"tree-sitter",
|
|
13
|
+
"codebase-indexing",
|
|
14
|
+
"ai-development",
|
|
15
|
+
"context-management"
|
|
22
16
|
],
|
|
23
17
|
"repository": {
|
|
24
18
|
"type": "git",
|
|
@@ -30,14 +24,13 @@
|
|
|
30
24
|
"cntx-ui": "./bin/cntx-ui.js"
|
|
31
25
|
},
|
|
32
26
|
"files": [
|
|
33
|
-
"bin/
|
|
27
|
+
"bin/",
|
|
34
28
|
"server.js",
|
|
35
29
|
"lib/",
|
|
36
|
-
"
|
|
30
|
+
"templates/",
|
|
37
31
|
"web/dist/",
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"templates/"
|
|
32
|
+
"README.md",
|
|
33
|
+
"VISION.md"
|
|
41
34
|
],
|
|
42
35
|
"engines": {
|
|
43
36
|
"node": ">=18.0.0"
|
|
@@ -45,19 +38,13 @@
|
|
|
45
38
|
"scripts": {
|
|
46
39
|
"dev": "node server.js",
|
|
47
40
|
"build": "cd web && npm install && npm run build",
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"prebuild": "npm run build:web",
|
|
51
|
-
"prepublishOnly": "npm run build:web",
|
|
52
|
-
"prepack": "npm run build:web",
|
|
53
|
-
"test:local": "npm pack && npm install -g ./cntx-ui-2.0.0.tgz"
|
|
41
|
+
"prepublishOnly": "npm run build",
|
|
42
|
+
"test:local": "npm pack && npm install -g ./cntx-ui-3.0.0.tgz"
|
|
54
43
|
},
|
|
55
44
|
"dependencies": {
|
|
56
|
-
"@radix-ui/react-tooltip": "^1.2.7",
|
|
57
45
|
"@xenova/transformers": "^2.17.2",
|
|
58
|
-
"
|
|
59
|
-
"glob": "^
|
|
60
|
-
"recharts": "^3.0.2",
|
|
46
|
+
"better-sqlite3": "^12.2.0",
|
|
47
|
+
"glob": "^9.0.0",
|
|
61
48
|
"tree-sitter": "^0.21.1",
|
|
62
49
|
"tree-sitter-javascript": "^0.23.1",
|
|
63
50
|
"tree-sitter-typescript": "^0.23.2",
|