cntx-ui 2.0.13 → 3.0.0

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 (49) hide show
  1. package/README.md +51 -339
  2. package/VISION.md +110 -0
  3. package/bin/cntx-ui-mcp.sh +3 -0
  4. package/bin/cntx-ui.js +138 -55
  5. package/lib/agent-runtime.js +301 -0
  6. package/lib/agent-tools.js +370 -0
  7. package/lib/api-router.js +1161 -0
  8. package/lib/bundle-manager.js +236 -0
  9. package/lib/configuration-manager.js +760 -0
  10. package/lib/database-manager.js +397 -0
  11. package/lib/file-system-manager.js +489 -0
  12. package/lib/heuristics-manager.js +527 -0
  13. package/lib/mcp-server.js +1125 -2
  14. package/lib/semantic-splitter.js +225 -491
  15. package/lib/simple-vector-store.js +98 -0
  16. package/lib/websocket-manager.js +470 -0
  17. package/package.json +19 -25
  18. package/server.js +742 -1935
  19. package/templates/TOOLS.md +41 -0
  20. package/templates/activities/README.md +67 -0
  21. package/templates/activities/activities/create-project-bundles/README.md +84 -0
  22. package/templates/activities/activities/create-project-bundles/notes.md +98 -0
  23. package/templates/activities/activities/create-project-bundles/progress.md +63 -0
  24. package/templates/activities/activities/create-project-bundles/tasks.md +39 -0
  25. package/templates/activities/activities.json +219 -0
  26. package/templates/activities/lib/.markdownlint.jsonc +18 -0
  27. package/templates/activities/lib/create-activity.mdc +63 -0
  28. package/templates/activities/lib/generate-tasks.mdc +64 -0
  29. package/templates/activities/lib/process-task-list.mdc +52 -0
  30. package/templates/agent-config.yaml +65 -0
  31. package/templates/agent-instructions.md +234 -0
  32. package/templates/agent-rules/capabilities/activities-system.md +147 -0
  33. package/templates/agent-rules/capabilities/bundle-system.md +131 -0
  34. package/templates/agent-rules/capabilities/vector-search.md +135 -0
  35. package/templates/agent-rules/core/codebase-navigation.md +91 -0
  36. package/templates/agent-rules/core/performance-hierarchy.md +48 -0
  37. package/templates/agent-rules/core/response-formatting.md +120 -0
  38. package/templates/agent-rules/project-specific/architecture.md +145 -0
  39. package/templates/config.json +76 -0
  40. package/templates/hidden-files.json +14 -0
  41. package/web/dist/assets/index-B2OdTzzI.css +1 -0
  42. package/web/dist/assets/index-D0tBsKiR.js +2016 -0
  43. package/web/dist/cntx-ui.svg +18 -0
  44. package/web/dist/index.html +25 -8
  45. package/lib/semantic-integration.js +0 -441
  46. package/mcp-config-example.json +0 -9
  47. package/web/dist/assets/index-Ci1Q-YrQ.js +0 -611
  48. package/web/dist/assets/index-IUp4q_fr.css +0 -1
  49. package/web/dist/vite.svg +0 -21
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Simple Vector Store with SQLite Persistence
3
+ * Powered by Transformers.js for local embeddings
4
+ * Persists vectors to SQLite for instant startup
5
+ */
6
+
7
+ import { pipeline } from '@xenova/transformers';
8
+
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;
15
+ }
16
+
17
+ async init() {
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');
23
+ }
24
+
25
+ async generateEmbedding(text) {
26
+ await this.init();
27
+ const output = await this.pipe(text, { pooling: 'mean', normalize: true });
28
+ return new Float32Array(output.data);
29
+ }
30
+
31
+ /**
32
+ * Upsert a chunk's embedding to persistence
33
+ */
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;
39
+
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;
47
+ }
48
+
49
+ /**
50
+ * Semantic Search across persistent embeddings
51
+ */
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
+ });
71
+ }
72
+ }
73
+
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
+ });
85
+ }
86
+
87
+ cosineSimilarity(vecA, vecB) {
88
+ let dotProduct = 0;
89
+ let normA = 0;
90
+ let normB = 0;
91
+ for (let i = 0; i < vecA.length; i++) {
92
+ dotProduct += vecA[i] * vecB[i];
93
+ normA += vecA[i] * vecA[i];
94
+ normB += vecB[i] * vecB[i];
95
+ }
96
+ return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
97
+ }
98
+ }
@@ -0,0 +1,470 @@
1
+ /**
2
+ * WebSocket Manager for cntx-ui
3
+ * Handles real-time client communication and updates
4
+ */
5
+
6
+ import { WebSocketServer } from 'ws';
7
+
8
+ export default class WebSocketManager {
9
+ constructor(bundleManager, configManager, options = {}) {
10
+ this.bundleManager = bundleManager;
11
+ this.configManager = configManager;
12
+ this.verbose = options.verbose || false;
13
+ this.clients = new Set();
14
+ this.wss = null;
15
+ }
16
+
17
+ // === WebSocket Server Setup ===
18
+
19
+ initialize(httpServer) {
20
+ this.wss = new WebSocketServer({ server: httpServer });
21
+
22
+ this.wss.on('connection', (ws) => {
23
+ this.handleConnection(ws);
24
+ });
25
+
26
+ if (this.verbose) {
27
+ console.log('🔌 WebSocket server initialized');
28
+ }
29
+ }
30
+
31
+ handleConnection(ws) {
32
+ // Add client to our set
33
+ this.clients.add(ws);
34
+ if (this.verbose) {
35
+ console.log(`📱 WebSocket client connected (${this.clients.size} total clients)`);
36
+ }
37
+
38
+ // Send initial update to the new client
39
+ this.sendUpdate(ws);
40
+
41
+ // Handle client disconnect
42
+ ws.on('close', () => {
43
+ this.clients.delete(ws);
44
+ if (this.verbose) {
45
+ console.log(`📱 WebSocket client disconnected (${this.clients.size} total clients)`);
46
+ }
47
+ });
48
+
49
+ // Handle client errors
50
+ ws.on('error', (error) => {
51
+ if (this.verbose) {
52
+ console.error('WebSocket client error:', error.message);
53
+ }
54
+ this.clients.delete(ws);
55
+ });
56
+
57
+ // Optional: Handle incoming messages from clients
58
+ ws.on('message', (message) => {
59
+ try {
60
+ const data = JSON.parse(message.toString());
61
+ this.handleClientMessage(ws, data);
62
+ } catch (error) {
63
+ if (this.verbose) {
64
+ console.error('Invalid WebSocket message:', error.message);
65
+ }
66
+ }
67
+ });
68
+ }
69
+
70
+ handleClientMessage(ws, data) {
71
+ // Handle different types of client messages
72
+ switch (data.type) {
73
+ case 'ping':
74
+ ws.send(JSON.stringify({ type: 'pong', timestamp: Date.now() }));
75
+ break;
76
+
77
+ case 'request-update':
78
+ this.sendUpdate(ws);
79
+ break;
80
+
81
+ case 'subscribe':
82
+ // Future: Handle subscription to specific bundle updates
83
+ ws.subscriptions = data.bundles || [];
84
+ break;
85
+
86
+ default:
87
+ if (this.verbose) {
88
+ console.warn('Unknown WebSocket message type:', data.type);
89
+ }
90
+ }
91
+ }
92
+
93
+ // === Client Management ===
94
+
95
+ getClientCount() {
96
+ return this.clients.size;
97
+ }
98
+
99
+ getActiveClients() {
100
+ // Filter out clients that might be in a closed state
101
+ const activeClients = new Set();
102
+
103
+ this.clients.forEach(client => {
104
+ if (client.readyState === 1) { // WebSocket.OPEN
105
+ activeClients.add(client);
106
+ } else {
107
+ // Remove dead connections
108
+ this.clients.delete(client);
109
+ }
110
+ });
111
+
112
+ return activeClients;
113
+ }
114
+
115
+ // === Broadcasting Updates ===
116
+
117
+ broadcastUpdate() {
118
+ const activeClients = this.getActiveClients();
119
+
120
+ if (activeClients.size === 0) {
121
+ return; // No clients to update
122
+ }
123
+
124
+ if (this.verbose) {
125
+ console.log(`📡 Broadcasting update to ${activeClients.size} client(s)`);
126
+ }
127
+
128
+ activeClients.forEach(client => {
129
+ this.sendUpdate(client);
130
+ });
131
+ }
132
+
133
+ sendUpdate(client) {
134
+ if (client.readyState !== 1) { // Not WebSocket.OPEN
135
+ return;
136
+ }
137
+
138
+ try {
139
+ const updateData = this.prepareUpdateData();
140
+ client.send(JSON.stringify(updateData));
141
+ } catch (error) {
142
+ if (this.verbose) {
143
+ console.error('Failed to send WebSocket update:', error.message);
144
+ }
145
+ this.clients.delete(client);
146
+ }
147
+ }
148
+
149
+ prepareUpdateData() {
150
+ const bundles = this.configManager.getBundles();
151
+
152
+ const bundleData = Array.from(bundles.entries()).map(([name, bundle]) => ({
153
+ name,
154
+ changed: bundle.changed,
155
+ fileCount: bundle.files.length,
156
+ content: bundle.content.substring(0, 2000) + (bundle.content.length > 2000 ? '...' : ''),
157
+ files: bundle.files,
158
+ lastGenerated: bundle.generated,
159
+ size: bundle.size,
160
+ patterns: bundle.patterns
161
+ }));
162
+
163
+ return {
164
+ type: 'bundle-update',
165
+ timestamp: new Date().toISOString(),
166
+ bundles: bundleData,
167
+ serverStatus: {
168
+ uptime: process.uptime(),
169
+ scanning: this.bundleManager._isScanning || false,
170
+ totalFiles: this.bundleManager.fileSystemManager?.getAllFiles()?.length || 0
171
+ }
172
+ };
173
+ }
174
+
175
+ // === Targeted Updates ===
176
+
177
+ broadcastBundleUpdate(bundleName) {
178
+ const activeClients = this.getActiveClients();
179
+
180
+ if (activeClients.size === 0) {
181
+ return;
182
+ }
183
+
184
+ if (this.verbose) {
185
+ console.log(`📡 Broadcasting ${bundleName} bundle update to ${activeClients.size} client(s)`);
186
+ }
187
+
188
+ const bundle = this.configManager.getBundles().get(bundleName);
189
+ if (!bundle) {
190
+ return;
191
+ }
192
+
193
+ const updateData = {
194
+ type: 'bundle-specific-update',
195
+ timestamp: new Date().toISOString(),
196
+ bundleName,
197
+ bundle: {
198
+ name: bundleName,
199
+ changed: bundle.changed,
200
+ fileCount: bundle.files.length,
201
+ content: bundle.content.substring(0, 2000) + (bundle.content.length > 2000 ? '...' : ''),
202
+ files: bundle.files,
203
+ lastGenerated: bundle.generated,
204
+ size: bundle.size,
205
+ patterns: bundle.patterns
206
+ }
207
+ };
208
+
209
+ activeClients.forEach(client => {
210
+ try {
211
+ if (client.readyState === 1) {
212
+ client.send(JSON.stringify(updateData));
213
+ }
214
+ } catch (error) {
215
+ if (this.verbose) {
216
+ console.error('Failed to send bundle update:', error.message);
217
+ }
218
+ this.clients.delete(client);
219
+ }
220
+ });
221
+ }
222
+
223
+ broadcastFileChange(filename, eventType) {
224
+ const activeClients = this.getActiveClients();
225
+
226
+ if (activeClients.size === 0) {
227
+ return;
228
+ }
229
+
230
+ const updateData = {
231
+ type: 'file-change',
232
+ timestamp: new Date().toISOString(),
233
+ filename,
234
+ eventType,
235
+ message: `File ${eventType}: ${filename}`
236
+ };
237
+
238
+ activeClients.forEach(client => {
239
+ try {
240
+ if (client.readyState === 1) {
241
+ client.send(JSON.stringify(updateData));
242
+ }
243
+ } catch (error) {
244
+ if (this.verbose) {
245
+ console.error('Failed to send file change update:', error.message);
246
+ }
247
+ this.clients.delete(client);
248
+ }
249
+ });
250
+ }
251
+
252
+ broadcastStatusUpdate(status) {
253
+ const activeClients = this.getActiveClients();
254
+
255
+ if (activeClients.size === 0) {
256
+ return;
257
+ }
258
+
259
+ const updateData = {
260
+ type: 'status-update',
261
+ timestamp: new Date().toISOString(),
262
+ status
263
+ };
264
+
265
+ activeClients.forEach(client => {
266
+ try {
267
+ if (client.readyState === 1) {
268
+ client.send(JSON.stringify(updateData));
269
+ }
270
+ } catch (error) {
271
+ if (this.verbose) {
272
+ console.error('Failed to send status update:', error.message);
273
+ }
274
+ this.clients.delete(client);
275
+ }
276
+ });
277
+ }
278
+
279
+ // === Utility Methods ===
280
+
281
+ ping() {
282
+ const activeClients = this.getActiveClients();
283
+
284
+ const pingData = {
285
+ type: 'ping',
286
+ timestamp: new Date().toISOString(),
287
+ serverTime: Date.now()
288
+ };
289
+
290
+ activeClients.forEach(client => {
291
+ try {
292
+ if (client.readyState === 1) {
293
+ client.send(JSON.stringify(pingData));
294
+ }
295
+ } catch (error) {
296
+ this.clients.delete(client);
297
+ }
298
+ });
299
+ }
300
+
301
+ // === Cleanup ===
302
+
303
+ close() {
304
+ if (this.wss) {
305
+ if (this.verbose) {
306
+ console.log('🔌 Closing WebSocket server...');
307
+ }
308
+
309
+ // Close all client connections
310
+ this.clients.forEach(client => {
311
+ try {
312
+ if (client.readyState === 1) {
313
+ client.close(1000, 'Server shutting down');
314
+ }
315
+ } catch (error) {
316
+ if (this.verbose) {
317
+ console.error('Error closing WebSocket client:', error.message);
318
+ }
319
+ }
320
+ });
321
+
322
+ // Close the WebSocket server
323
+ this.wss.close(() => {
324
+ if (this.verbose) {
325
+ console.log('🔌 WebSocket server closed');
326
+ }
327
+ });
328
+
329
+ this.clients.clear();
330
+ }
331
+ }
332
+
333
+ // === Health Check ===
334
+
335
+ getHealthStatus() {
336
+ return {
337
+ connected: this.clients.size,
338
+ active: this.getActiveClients().size,
339
+ server: this.wss ? 'running' : 'stopped'
340
+ };
341
+ }
342
+
343
+ // === Event Handlers for Integration ===
344
+
345
+ onBundleGenerated(bundleName) {
346
+ this.broadcastBundleUpdate(bundleName);
347
+ this.broadcastBundleSyncCompleted(bundleName);
348
+ }
349
+
350
+ onBundlesGenerated() {
351
+ this.broadcastUpdate();
352
+ }
353
+
354
+ onConfigChanged() {
355
+ this.broadcastUpdate();
356
+ }
357
+
358
+ onFileChanged(filename, eventType) {
359
+ // Notify which bundles are affected by this file change
360
+ const affectedBundles = this.getAffectedBundles(filename);
361
+
362
+ affectedBundles.forEach(bundleName => {
363
+ this.broadcastBundleFileChanged(bundleName, filename);
364
+ });
365
+
366
+ this.broadcastFileChange(filename, eventType);
367
+
368
+ // Also broadcast bundle updates after a short delay to allow bundle regeneration
369
+ setTimeout(() => {
370
+ this.broadcastUpdate();
371
+ }, 500);
372
+ }
373
+
374
+ // === New Bundle Sync Event Handlers ===
375
+
376
+ onBundleSyncStarted(bundleName) {
377
+ const updateData = {
378
+ type: 'bundle-sync-started',
379
+ bundleName,
380
+ timestamp: new Date().toISOString()
381
+ };
382
+ this.broadcastToActiveClients(updateData);
383
+ }
384
+
385
+ onBundleSyncCompleted(bundleName) {
386
+ const updateData = {
387
+ type: 'bundle-sync-completed',
388
+ bundleName,
389
+ timestamp: new Date().toISOString()
390
+ };
391
+ this.broadcastToActiveClients(updateData);
392
+ }
393
+
394
+ onBundleSyncFailed(bundleName, error) {
395
+ const updateData = {
396
+ type: 'bundle-sync-failed',
397
+ bundleName,
398
+ error: error.message || error,
399
+ timestamp: new Date().toISOString()
400
+ };
401
+ this.broadcastToActiveClients(updateData);
402
+ }
403
+
404
+ broadcastBundleFileChanged(bundleName, filename) {
405
+ const updateData = {
406
+ type: 'bundle-file-changed',
407
+ bundleName,
408
+ filename,
409
+ timestamp: new Date().toISOString()
410
+ };
411
+ this.broadcastToActiveClients(updateData);
412
+ }
413
+
414
+ broadcastBundleSyncCompleted(bundleName) {
415
+ const updateData = {
416
+ type: 'bundle-sync-completed',
417
+ bundleName,
418
+ timestamp: new Date().toISOString()
419
+ };
420
+ this.broadcastToActiveClients(updateData);
421
+ }
422
+
423
+ // Helper method to broadcast to all active clients
424
+ broadcastToActiveClients(data) {
425
+ const activeClients = this.getActiveClients();
426
+
427
+ if (activeClients.size === 0) {
428
+ return;
429
+ }
430
+
431
+ activeClients.forEach(client => {
432
+ try {
433
+ if (client.readyState === 1) {
434
+ client.send(JSON.stringify(data));
435
+ }
436
+ } catch (error) {
437
+ if (this.verbose) {
438
+ console.error('Failed to send WebSocket update:', error.message);
439
+ }
440
+ this.clients.delete(client);
441
+ }
442
+ });
443
+ }
444
+
445
+ // Helper to find which bundles are affected by a file change
446
+ getAffectedBundles(filename) {
447
+ const bundles = this.configManager.getBundles();
448
+ const affectedBundles = [];
449
+
450
+ bundles.forEach((bundle, name) => {
451
+ const matchesBundle = bundle.patterns.some(pattern =>
452
+ this.bundleManager.fileSystemManager.matchesPattern(filename, pattern)
453
+ );
454
+
455
+ if (matchesBundle) {
456
+ affectedBundles.push(name);
457
+ }
458
+ });
459
+
460
+ return affectedBundles;
461
+ }
462
+
463
+ onHiddenFilesChanged() {
464
+ this.broadcastUpdate();
465
+ }
466
+
467
+ onIgnorePatternsChanged() {
468
+ this.broadcastUpdate();
469
+ }
470
+ }
package/package.json CHANGED
@@ -1,24 +1,18 @@
1
1
  {
2
2
  "name": "cntx-ui",
3
3
  "type": "module",
4
- "version": "2.0.13",
5
- "description": "File context management tool with web UI and MCP server for AI development workflows - bundle project files for LLM consumption",
4
+ "version": "3.0.0",
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
- "ai-development",
7
+ "repository-intelligence",
8
+ "semantic-code-search",
9
+ "ai-agent-context",
8
10
  "mcp-server",
9
- "file-bundling",
10
- "context-management",
11
- "llm-tools",
12
- "claude-desktop",
13
- "model-context-protocol",
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,12 +24,13 @@
30
24
  "cntx-ui": "./bin/cntx-ui.js"
31
25
  },
32
26
  "files": [
33
- "bin/cntx-ui.js",
27
+ "bin/",
34
28
  "server.js",
35
29
  "lib/",
30
+ "templates/",
31
+ "web/dist/",
36
32
  "README.md",
37
- "web/dist",
38
- "mcp-config-example.json"
33
+ "VISION.md"
39
34
  ],
40
35
  "engines": {
41
36
  "node": ">=18.0.0"
@@ -43,14 +38,13 @@
43
38
  "scripts": {
44
39
  "dev": "node server.js",
45
40
  "build": "cd web && npm install && npm run build",
46
- "build:web": "cd web && npm install && npm run build",
47
- "dev:web": "cd web && npm run dev",
48
- "prebuild": "npm run build:web",
49
- "prepublishOnly": "npm run build:web",
50
- "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"
51
43
  },
52
44
  "dependencies": {
53
- "glob": "^8.1.0",
45
+ "@xenova/transformers": "^2.17.2",
46
+ "better-sqlite3": "^12.2.0",
47
+ "glob": "^9.0.0",
54
48
  "tree-sitter": "^0.21.1",
55
49
  "tree-sitter-javascript": "^0.23.1",
56
50
  "tree-sitter-typescript": "^0.23.2",