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
package/server.js
DELETED
|
@@ -1,687 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Refactored cntx-ui Server
|
|
3
|
-
* Lean orchestration layer using modular architecture
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { createServer } from 'http';
|
|
7
|
-
import { join, dirname, relative, extname } from 'path';
|
|
8
|
-
import { fileURLToPath } from 'url';
|
|
9
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
10
|
-
|
|
11
|
-
// Import our modular components
|
|
12
|
-
import ConfigurationManager from './lib/configuration-manager.js';
|
|
13
|
-
import DatabaseManager from './lib/database-manager.js';
|
|
14
|
-
import FileSystemManager from './lib/file-system-manager.js';
|
|
15
|
-
import BundleManager from './lib/bundle-manager.js';
|
|
16
|
-
import APIRouter from './lib/api-router.js';
|
|
17
|
-
import WebSocketManager from './lib/websocket-manager.js';
|
|
18
|
-
|
|
19
|
-
// Import existing lib modules
|
|
20
|
-
import { startMCPTransport } from './lib/mcp-transport.js';
|
|
21
|
-
import SemanticSplitter from './lib/semantic-splitter.js';
|
|
22
|
-
import SimpleVectorStore from './lib/simple-vector-store.js';
|
|
23
|
-
import { MCPServer } from './lib/mcp-server.js';
|
|
24
|
-
|
|
25
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
26
|
-
|
|
27
|
-
// Utility function for content types
|
|
28
|
-
function getContentType(filePath) {
|
|
29
|
-
const ext = filePath.substring(filePath.lastIndexOf('.')).toLowerCase();
|
|
30
|
-
const contentTypes = {
|
|
31
|
-
'.html': 'text/html',
|
|
32
|
-
'.js': 'application/javascript',
|
|
33
|
-
'.css': 'text/css',
|
|
34
|
-
'.json': 'application/json',
|
|
35
|
-
'.png': 'image/png',
|
|
36
|
-
'.jpg': 'image/jpeg',
|
|
37
|
-
'.gif': 'image/gif',
|
|
38
|
-
'.svg': 'image/svg+xml',
|
|
39
|
-
'.ico': 'image/x-icon'
|
|
40
|
-
};
|
|
41
|
-
return contentTypes[ext] || 'text/plain';
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export class CntxServer {
|
|
45
|
-
constructor(cwd = process.cwd(), options = {}) {
|
|
46
|
-
this.CWD = cwd;
|
|
47
|
-
this.CNTX_DIR = join(cwd, '.cntx');
|
|
48
|
-
this.verbose = options.verbose || false;
|
|
49
|
-
this.mcpServerStarted = false;
|
|
50
|
-
this.mcpServer = null;
|
|
51
|
-
|
|
52
|
-
// Ensure directory exists early
|
|
53
|
-
if (!existsSync(this.CNTX_DIR)) mkdirSync(this.CNTX_DIR, { recursive: true });
|
|
54
|
-
|
|
55
|
-
// Initialize modular components
|
|
56
|
-
this.configManager = new ConfigurationManager(cwd, { verbose: this.verbose });
|
|
57
|
-
this.databaseManager = new DatabaseManager(this.CNTX_DIR, { verbose: this.verbose });
|
|
58
|
-
this.fileSystemManager = new FileSystemManager(cwd, { verbose: this.verbose });
|
|
59
|
-
this.bundleManager = new BundleManager(this.configManager, this.fileSystemManager, this.verbose);
|
|
60
|
-
this.webSocketManager = new WebSocketManager(this.bundleManager, this.configManager, { verbose: this.verbose });
|
|
61
|
-
|
|
62
|
-
// Initialize semantic analysis components
|
|
63
|
-
this.semanticSplitter = new SemanticSplitter({
|
|
64
|
-
maxChunkSize: 2000,
|
|
65
|
-
includeContext: true,
|
|
66
|
-
minFunctionSize: 50
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
this.vectorStore = new SimpleVectorStore(this.databaseManager, {
|
|
70
|
-
modelName: 'Xenova/all-MiniLM-L6-v2'
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
this.semanticCache = null;
|
|
74
|
-
this.lastSemanticAnalysis = null;
|
|
75
|
-
this.vectorStoreInitialized = false;
|
|
76
|
-
|
|
77
|
-
// Create semantic analysis manager object for API router
|
|
78
|
-
this.semanticAnalysisManager = {
|
|
79
|
-
getSemanticAnalysis: () => this.getSemanticAnalysis(),
|
|
80
|
-
refreshSemanticAnalysis: () => this.refreshSemanticAnalysis(),
|
|
81
|
-
exportSemanticChunk: (chunkName) => this.exportSemanticChunk(chunkName),
|
|
82
|
-
lastSemanticAnalysis: this.lastSemanticAnalysis
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
// Initialize API router with all managers
|
|
86
|
-
this.apiRouter = new APIRouter(
|
|
87
|
-
this,
|
|
88
|
-
this.configManager,
|
|
89
|
-
this.bundleManager,
|
|
90
|
-
this.fileSystemManager,
|
|
91
|
-
this.semanticAnalysisManager,
|
|
92
|
-
this.vectorStore
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
// Add references for cross-module communication
|
|
96
|
-
this.bundleManager.fileSystemManager = this.fileSystemManager;
|
|
97
|
-
this.bundleManager.webSocketManager = this.webSocketManager;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// === Proxy methods for MCP compatibility ===
|
|
101
|
-
|
|
102
|
-
get bundles() {
|
|
103
|
-
return this.configManager.getBundles();
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
getAllFiles() {
|
|
107
|
-
return this.fileSystemManager.getAllFiles();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
getFileTree() {
|
|
111
|
-
return this.fileSystemManager.getFileTree();
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
generateBundle(name) {
|
|
115
|
-
return this.bundleManager.regenerateBundle(name);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
generateAllBundles() {
|
|
119
|
-
return this.bundleManager.generateAllBundles();
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
saveBundleStates() {
|
|
123
|
-
return this.configManager.saveBundleStates();
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
loadIgnorePatterns() {
|
|
127
|
-
this.configManager.loadIgnorePatterns();
|
|
128
|
-
this.fileSystemManager.setIgnorePatterns(this.configManager.getIgnorePatterns());
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// === Initialization ===
|
|
132
|
-
|
|
133
|
-
async init(options = {}) {
|
|
134
|
-
if (!existsSync(this.CNTX_DIR)) mkdirSync(this.CNTX_DIR, { recursive: true });
|
|
135
|
-
|
|
136
|
-
const { skipFileWatcher = false, skipBundleGeneration = false } = options;
|
|
137
|
-
|
|
138
|
-
// Step 1: Load configuration
|
|
139
|
-
this.configManager.loadConfig();
|
|
140
|
-
this.configManager.loadHiddenFilesConfig();
|
|
141
|
-
this.configManager.loadIgnorePatterns();
|
|
142
|
-
this.configManager.loadBundleStates();
|
|
143
|
-
console.log(' Configuration loaded');
|
|
144
|
-
|
|
145
|
-
if (!skipFileWatcher) {
|
|
146
|
-
// Step 2: Set up file watcher
|
|
147
|
-
this.fileSystemManager.setIgnorePatterns(this.configManager.getIgnorePatterns());
|
|
148
|
-
console.log(' File watcher configured');
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Step 3: Load semantic cache
|
|
152
|
-
const cacheData = this.configManager.loadSemanticCache();
|
|
153
|
-
if (cacheData) {
|
|
154
|
-
this.semanticCache = cacheData.analysis;
|
|
155
|
-
this.lastSemanticAnalysis = cacheData.timestamp;
|
|
156
|
-
console.log(` Semantic cache loaded (${this.semanticCache.chunks.length} chunks)`);
|
|
157
|
-
} else {
|
|
158
|
-
console.log(' No semantic cache found (will analyze on first request)');
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (!skipFileWatcher) {
|
|
162
|
-
// Step 4: Start file watcher
|
|
163
|
-
this.startWatching();
|
|
164
|
-
console.log(' File watcher started');
|
|
165
|
-
|
|
166
|
-
// Trigger initial semantic analysis in background if no cache
|
|
167
|
-
if (!this.semanticCache) {
|
|
168
|
-
this.getSemanticAnalysis().catch(err => console.error('Initial semantic analysis failed:', err.message));
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Step 5: Generate bundles (awaited to prevent race conditions)
|
|
172
|
-
if (!skipBundleGeneration) {
|
|
173
|
-
await this.bundleManager.generateAllBundles();
|
|
174
|
-
console.log(' Bundles generated');
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Display initialization summary
|
|
180
|
-
displayInitSummary() {
|
|
181
|
-
const summary = [];
|
|
182
|
-
|
|
183
|
-
// Add semantic cache info
|
|
184
|
-
if (this.semanticCache) {
|
|
185
|
-
summary.push(`Loaded semantic cache (${this.semanticCache.chunks.length} chunks with embeddings)`);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Add ignore patterns info
|
|
189
|
-
const ignorePatterns = this.configManager.getIgnorePatterns();
|
|
190
|
-
if (ignorePatterns.length > 0) {
|
|
191
|
-
summary.push(`Loaded ${ignorePatterns.length} ignore patterns`);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Add bundle info
|
|
195
|
-
const bundles = this.bundleManager.getAllBundleInfo();
|
|
196
|
-
if (bundles.length > 0) {
|
|
197
|
-
summary.push(`Generated ${bundles.length} bundles`);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Add file watcher info
|
|
201
|
-
summary.push('File watcher started');
|
|
202
|
-
summary.push('WebSocket server initialized');
|
|
203
|
-
|
|
204
|
-
// Display summary
|
|
205
|
-
if (summary.length > 0) {
|
|
206
|
-
console.log('Initialization complete:');
|
|
207
|
-
summary.forEach(msg => console.log(` - ${msg}`));
|
|
208
|
-
console.log('');
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// === File Watching ===
|
|
213
|
-
|
|
214
|
-
startWatching() {
|
|
215
|
-
this.fileSystemManager.startWatching(async (eventType, filename) => {
|
|
216
|
-
if (this.verbose) {
|
|
217
|
-
console.log(`File ${eventType}: ${filename}`);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Skip processing files in .cntx directory to prevent infinite loops
|
|
221
|
-
if (filename.startsWith('.cntx/')) {
|
|
222
|
-
if (this.verbose) {
|
|
223
|
-
console.log(`Skipping .cntx file: ${filename}`);
|
|
224
|
-
}
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Mark affected bundles as changed
|
|
229
|
-
this.bundleManager.markBundlesChanged(filename);
|
|
230
|
-
|
|
231
|
-
// Invalidate semantic cache if needed
|
|
232
|
-
this.invalidateSemanticCache();
|
|
233
|
-
|
|
234
|
-
// Notify WebSocket clients
|
|
235
|
-
this.webSocketManager.onFileChanged(filename, eventType);
|
|
236
|
-
|
|
237
|
-
// Automatically regenerate affected bundles after a short delay
|
|
238
|
-
setTimeout(async () => {
|
|
239
|
-
await this.regenerateChangedBundles(filename);
|
|
240
|
-
}, 1000); // 1 second delay to batch multiple rapid changes
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
async regenerateChangedBundles(filename) {
|
|
245
|
-
try {
|
|
246
|
-
const bundles = this.configManager.getBundles();
|
|
247
|
-
const affectedBundles = [];
|
|
248
|
-
|
|
249
|
-
// Find which bundles are affected by this file
|
|
250
|
-
bundles.forEach((bundle, name) => {
|
|
251
|
-
const matchesBundle = bundle.patterns.some(pattern =>
|
|
252
|
-
this.fileSystemManager.matchesPattern(filename, pattern)
|
|
253
|
-
);
|
|
254
|
-
|
|
255
|
-
if (matchesBundle && bundle.changed) {
|
|
256
|
-
affectedBundles.push(name);
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
// Regenerate each affected bundle
|
|
261
|
-
for (const bundleName of affectedBundles) {
|
|
262
|
-
if (this.verbose) {
|
|
263
|
-
console.log(`Auto-regenerating bundle: ${bundleName}`);
|
|
264
|
-
}
|
|
265
|
-
await this.bundleManager.regenerateBundle(bundleName);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
} catch (error) {
|
|
269
|
-
console.error('Failed to auto-regenerate bundles:', error.message);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// === HTTP Server ===
|
|
274
|
-
|
|
275
|
-
async handleRequest(req, res) {
|
|
276
|
-
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
277
|
-
|
|
278
|
-
// Add CORS headers for all requests
|
|
279
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
280
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
281
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
282
|
-
|
|
283
|
-
// Handle preflight requests
|
|
284
|
-
if (req.method === 'OPTIONS') {
|
|
285
|
-
res.writeHead(200);
|
|
286
|
-
res.end();
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
try {
|
|
291
|
-
// Handle API routes
|
|
292
|
-
if (url.pathname.startsWith('/api/')) {
|
|
293
|
-
return await this.apiRouter.handleRequest(req, res, url);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Handle static files
|
|
297
|
-
return this.handleStaticFile(req, res, url);
|
|
298
|
-
|
|
299
|
-
} catch (error) {
|
|
300
|
-
console.error('Request handling error:', error);
|
|
301
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
302
|
-
res.end(JSON.stringify({ error: 'Internal server error' }));
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
handleStaticFile(req, res, url) {
|
|
307
|
-
const webDir = join(__dirname, 'web', 'dist');
|
|
308
|
-
let filePath = join(webDir, url.pathname === '/' ? 'index.html' : url.pathname);
|
|
309
|
-
|
|
310
|
-
// Security check - ensure path is within web directory
|
|
311
|
-
if (!filePath.startsWith(webDir)) {
|
|
312
|
-
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
313
|
-
res.end('Forbidden');
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (!existsSync(filePath)) {
|
|
318
|
-
// For SPA routing, serve index.html for non-API routes
|
|
319
|
-
if (!url.pathname.startsWith('/api/')) {
|
|
320
|
-
filePath = join(webDir, 'index.html');
|
|
321
|
-
} else {
|
|
322
|
-
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
323
|
-
res.end('Not Found');
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
try {
|
|
329
|
-
const content = readFileSync(filePath);
|
|
330
|
-
const contentType = getContentType(filePath);
|
|
331
|
-
|
|
332
|
-
res.writeHead(200, { 'Content-Type': contentType });
|
|
333
|
-
res.end(content);
|
|
334
|
-
} catch (error) {
|
|
335
|
-
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
336
|
-
res.end('Error reading file');
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// === Semantic Analysis (Legacy methods for compatibility) ===
|
|
341
|
-
|
|
342
|
-
async getSemanticAnalysis() {
|
|
343
|
-
// Return cached result if available
|
|
344
|
-
if (this.semanticCache) {
|
|
345
|
-
return this.semanticCache;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// 1. Try to load from SQLite first
|
|
349
|
-
try {
|
|
350
|
-
const dbChunks = this.databaseManager.db.prepare('SELECT * FROM semantic_chunks').all();
|
|
351
|
-
if (dbChunks.length > 0) {
|
|
352
|
-
this.semanticCache = {
|
|
353
|
-
chunks: dbChunks.map(row => this.databaseManager.mapChunkRow(row)),
|
|
354
|
-
summary: { totalChunks: dbChunks.length }
|
|
355
|
-
};
|
|
356
|
-
return this.semanticCache;
|
|
357
|
-
}
|
|
358
|
-
} catch (e) {
|
|
359
|
-
console.warn('Failed to load chunks from SQLite, performing fresh analysis...');
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// 2. Perform fresh analysis if DB is empty
|
|
363
|
-
try {
|
|
364
|
-
const supportedExtensions = ['.js', '.jsx', '.ts', '.tsx', '.mjs'];
|
|
365
|
-
const files = this.fileSystemManager.getAllFiles()
|
|
366
|
-
.filter(f => supportedExtensions.includes(extname(f).toLowerCase()))
|
|
367
|
-
.map(f => relative(this.CWD, f));
|
|
368
|
-
|
|
369
|
-
let bundleConfig = null;
|
|
370
|
-
if (existsSync(this.configManager.CONFIG_FILE)) {
|
|
371
|
-
bundleConfig = JSON.parse(readFileSync(this.configManager.CONFIG_FILE, 'utf8'));
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
this.semanticCache = await this.semanticSplitter.extractSemanticChunks(this.CWD, files, bundleConfig);
|
|
375
|
-
this.lastSemanticAnalysis = Date.now();
|
|
376
|
-
|
|
377
|
-
// 3. Persist chunks to SQLite immediately
|
|
378
|
-
if (this.semanticCache.chunks.length > 0) {
|
|
379
|
-
this.databaseManager.saveChunks(this.semanticCache.chunks);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// 4. Trigger background embedding enhancement
|
|
383
|
-
this.enhanceSemanticChunksIfNeeded(this.semanticCache).catch(err => {
|
|
384
|
-
console.error('Background embedding enhancement failed:', err.message);
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
return this.semanticCache;
|
|
388
|
-
} catch (error) {
|
|
389
|
-
console.error('Semantic analysis failed:', error.message);
|
|
390
|
-
throw new Error(`Semantic analysis failed: ${error.message}`);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
async refreshSemanticAnalysis() {
|
|
395
|
-
console.log('Refreshing semantic analysis and database...');
|
|
396
|
-
|
|
397
|
-
// Clear the database table but keep other data
|
|
398
|
-
this.databaseManager.db.prepare('DELETE FROM semantic_chunks').run();
|
|
399
|
-
this.databaseManager.db.prepare('DELETE FROM vector_embeddings').run();
|
|
400
|
-
|
|
401
|
-
this.semanticCache = null;
|
|
402
|
-
this.lastSemanticAnalysis = null;
|
|
403
|
-
|
|
404
|
-
return this.getSemanticAnalysis();
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
async enhanceSemanticChunksIfNeeded(analysis) {
|
|
408
|
-
if (!analysis || !analysis.chunks) return;
|
|
409
|
-
|
|
410
|
-
// Check DB for existing embeddings to find only what's missing
|
|
411
|
-
const chunksNeedingEmbeddings = [];
|
|
412
|
-
for (const chunk of analysis.chunks) {
|
|
413
|
-
if (!this.databaseManager.getEmbedding(chunk.id)) {
|
|
414
|
-
chunksNeedingEmbeddings.push(chunk);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
if (chunksNeedingEmbeddings.length === 0) {
|
|
419
|
-
console.log('All chunks already have persistent embeddings');
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
console.log(`Enhancing ${chunksNeedingEmbeddings.length} chunks with persistent embeddings...`);
|
|
424
|
-
|
|
425
|
-
// Initialize vector store if needed
|
|
426
|
-
if (!this.vectorStoreInitialized) {
|
|
427
|
-
await this.vectorStore.init();
|
|
428
|
-
this.vectorStoreInitialized = true;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// Add embeddings to chunks that need them and persist
|
|
432
|
-
for (const chunk of chunksNeedingEmbeddings) {
|
|
433
|
-
try {
|
|
434
|
-
await this.vectorStore.upsertChunk(chunk);
|
|
435
|
-
} catch (error) {
|
|
436
|
-
console.error(`Failed to generate/persist embedding for chunk ${chunk.id}:`, error.message);
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
console.log('Background embedding enhancement complete');
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
invalidateSemanticCache() {
|
|
443
|
-
this.semanticCache = null;
|
|
444
|
-
this.lastSemanticAnalysis = null;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
async exportSemanticChunk(chunkName) {
|
|
448
|
-
const analysis = await this.getSemanticAnalysis();
|
|
449
|
-
const chunk = analysis.chunks.find(c => c.name === chunkName || c.id === chunkName);
|
|
450
|
-
|
|
451
|
-
if (!chunk) {
|
|
452
|
-
throw new Error(`Chunk "${chunkName}" not found`);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
return this.bundleManager.generateFileXML(chunk.filePath);
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// === MCP Server Integration ===
|
|
459
|
-
|
|
460
|
-
startMCPServer() {
|
|
461
|
-
if (!this.mcpServer) {
|
|
462
|
-
this.mcpServer = new MCPServer(this);
|
|
463
|
-
this.mcpServerStarted = true;
|
|
464
|
-
|
|
465
|
-
if (this.verbose) {
|
|
466
|
-
console.log('MCP server started');
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// === Server Lifecycle ===
|
|
472
|
-
|
|
473
|
-
async listen(port = 3333, host = 'localhost') {
|
|
474
|
-
const server = createServer((req, res) => {
|
|
475
|
-
this.handleRequest(req, res);
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
// Initialize WebSocket server
|
|
479
|
-
this.webSocketManager.initialize(server);
|
|
480
|
-
|
|
481
|
-
// Start server and show progress
|
|
482
|
-
server.listen(port, host, () => {
|
|
483
|
-
console.log('');
|
|
484
|
-
console.log(`Server running at http://${host}:${port}`);
|
|
485
|
-
console.log(`Serving ${this.bundleManager.getAllBundleInfo().length} bundles from your project`);
|
|
486
|
-
console.log('');
|
|
487
|
-
|
|
488
|
-
// Display initialization summary
|
|
489
|
-
this.displayInitSummary();
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
// Handle graceful shutdown
|
|
493
|
-
process.on('SIGINT', () => {
|
|
494
|
-
console.log('\nShutting down server...');
|
|
495
|
-
this.webSocketManager.close();
|
|
496
|
-
this.fileSystemManager.destroy();
|
|
497
|
-
server.close(() => {
|
|
498
|
-
console.log('Server stopped');
|
|
499
|
-
process.exit(0);
|
|
500
|
-
});
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
return server;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
// Auto-init and start: checks for .cntx/, runs initConfig() if missing, then starts server
|
|
508
|
-
export async function autoInitAndStart(options = {}) {
|
|
509
|
-
const cwd = options.cwd || process.cwd();
|
|
510
|
-
const cntxDir = join(cwd, '.cntx');
|
|
511
|
-
|
|
512
|
-
if (!existsSync(cntxDir)) {
|
|
513
|
-
console.log('No .cntx directory found, initializing...');
|
|
514
|
-
console.log('');
|
|
515
|
-
await initConfig(cwd);
|
|
516
|
-
console.log('');
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
return startServer(options);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// Export function for CLI compatibility
|
|
523
|
-
export async function startServer(options = {}) {
|
|
524
|
-
const server = new CntxServer(options.cwd, { verbose: options.verbose });
|
|
525
|
-
|
|
526
|
-
const asciiArt = `
|
|
527
|
-
██████ ███ ██ ████████ ██ ██ ██ ██ ██
|
|
528
|
-
██ ████ ██ ██ ██ ██ ██ ██ ██
|
|
529
|
-
██ ██ ██ ██ ██ ███ █████ ██ ██ ██
|
|
530
|
-
██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
531
|
-
██████ ██ ████ ██ ██ ██ ██████ ██
|
|
532
|
-
`;
|
|
533
|
-
console.log(asciiArt);
|
|
534
|
-
|
|
535
|
-
// Now start initialization
|
|
536
|
-
await server.init();
|
|
537
|
-
|
|
538
|
-
// Enable MCP status tracking by default
|
|
539
|
-
const withMcp = options.withMcp !== false;
|
|
540
|
-
if (withMcp) {
|
|
541
|
-
server.startMCPServer();
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
return await server.listen(options.port, options.host);
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
// CLI Functions for backward compatibility
|
|
548
|
-
export async function startMCPServer(options = {}) {
|
|
549
|
-
const server = new CntxServer(options.cwd, { verbose: true });
|
|
550
|
-
await server.init();
|
|
551
|
-
server.startMCPServer();
|
|
552
|
-
|
|
553
|
-
// For MCP mode, we don't start the web server, just keep the process alive
|
|
554
|
-
console.log('MCP server running on stdio...');
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
export async function generateBundle(bundleName = 'master') {
|
|
558
|
-
const server = new CntxServer(process.cwd(), { verbose: true });
|
|
559
|
-
await server.init({ skipFileWatcher: true });
|
|
560
|
-
|
|
561
|
-
await server.bundleManager.regenerateBundle(bundleName);
|
|
562
|
-
const bundleInfo = server.bundleManager.getBundleInfo(bundleName);
|
|
563
|
-
|
|
564
|
-
if (!bundleInfo) {
|
|
565
|
-
throw new Error(`Bundle '${bundleName}' not found`);
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
return bundleInfo;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
// Initialize project configuration
|
|
572
|
-
export async function initConfig(cwd = process.cwd()) {
|
|
573
|
-
const server = new CntxServer(cwd);
|
|
574
|
-
|
|
575
|
-
// 1. Initialize directory structure
|
|
576
|
-
if (!existsSync(server.CNTX_DIR)) {
|
|
577
|
-
mkdirSync(server.CNTX_DIR, { recursive: true });
|
|
578
|
-
console.log('Created .cntx directory');
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// 2. Create .mcp.json for Claude Code discovery
|
|
582
|
-
const mcpConfigPath = join(cwd, '.mcp.json');
|
|
583
|
-
const mcpConfig = {
|
|
584
|
-
mcpServers: {
|
|
585
|
-
"cntx-ui": {
|
|
586
|
-
command: "cntx-ui",
|
|
587
|
-
args: ["mcp"],
|
|
588
|
-
cwd: "."
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
};
|
|
592
|
-
writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2), 'utf8');
|
|
593
|
-
console.log('Created .mcp.json for MCP auto-discovery');
|
|
594
|
-
|
|
595
|
-
// 3. Initialize basic configuration with better defaults and auto-suggestions
|
|
596
|
-
server.configManager.loadConfig();
|
|
597
|
-
|
|
598
|
-
const suggestedBundles = {
|
|
599
|
-
master: ['**/*']
|
|
600
|
-
};
|
|
601
|
-
|
|
602
|
-
// Directory-based auto-suggestions
|
|
603
|
-
const commonDirs = [
|
|
604
|
-
{ dir: 'src/components', name: 'ui-components' },
|
|
605
|
-
{ dir: 'src/services', name: 'services' },
|
|
606
|
-
{ dir: 'src/lib', name: 'libraries' },
|
|
607
|
-
{ dir: 'src/hooks', name: 'react-hooks' },
|
|
608
|
-
{ dir: 'server', name: 'backend-api' },
|
|
609
|
-
{ dir: 'tests', name: 'test-suite' }
|
|
610
|
-
];
|
|
611
|
-
|
|
612
|
-
commonDirs.forEach(d => {
|
|
613
|
-
if (existsSync(join(cwd, d.dir))) {
|
|
614
|
-
suggestedBundles[d.name] = [`${d.dir}/**`];
|
|
615
|
-
console.log(` Suggested bundle: ${d.name} (${d.dir}/**)`);
|
|
616
|
-
}
|
|
617
|
-
});
|
|
618
|
-
|
|
619
|
-
server.configManager.saveConfig({
|
|
620
|
-
bundles: suggestedBundles
|
|
621
|
-
});
|
|
622
|
-
|
|
623
|
-
// 4. Create robust default .cntxignore
|
|
624
|
-
const ignorePath = join(cwd, '.cntxignore');
|
|
625
|
-
if (!existsSync(ignorePath)) {
|
|
626
|
-
const defaultIgnore = `# Binary files
|
|
627
|
-
*.db
|
|
628
|
-
*.db-journal
|
|
629
|
-
*.png
|
|
630
|
-
*.jpg
|
|
631
|
-
*.jpeg
|
|
632
|
-
*.ico
|
|
633
|
-
*.icns
|
|
634
|
-
*.gif
|
|
635
|
-
*.zip
|
|
636
|
-
*.tar.gz
|
|
637
|
-
|
|
638
|
-
# Generated files
|
|
639
|
-
**/gen/**
|
|
640
|
-
**/dist/**
|
|
641
|
-
**/build/**
|
|
642
|
-
**/node_modules/**
|
|
643
|
-
**/.next/**
|
|
644
|
-
**/.cache/**
|
|
645
|
-
|
|
646
|
-
# cntx-ui internals
|
|
647
|
-
.cntx/**
|
|
648
|
-
.mcp.json
|
|
649
|
-
`;
|
|
650
|
-
writeFileSync(ignorePath, defaultIgnore, 'utf8');
|
|
651
|
-
console.log('Created .cntxignore with smart defaults');
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
console.log('Configuration initialized');
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
export async function getStatus() {
|
|
658
|
-
const server = new CntxServer(process.cwd(), { verbose: true });
|
|
659
|
-
await server.init({ skipFileWatcher: true });
|
|
660
|
-
|
|
661
|
-
const bundles = server.bundleManager.getAllBundleInfo();
|
|
662
|
-
const totalFiles = server.fileSystemManager.getAllFiles().length;
|
|
663
|
-
|
|
664
|
-
console.log('cntx-ui Status');
|
|
665
|
-
console.log('================');
|
|
666
|
-
console.log(`Total files: ${totalFiles}`);
|
|
667
|
-
console.log(`Bundles: ${bundles.length}`);
|
|
668
|
-
|
|
669
|
-
bundles.forEach(bundle => {
|
|
670
|
-
console.log(` - ${bundle.name}: ${bundle.fileCount} files (${Math.round(bundle.size / 1024)}KB)`);
|
|
671
|
-
});
|
|
672
|
-
|
|
673
|
-
return {
|
|
674
|
-
totalFiles,
|
|
675
|
-
bundles: bundles.length,
|
|
676
|
-
bundleDetails: bundles
|
|
677
|
-
};
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
// Auto-start server when run directly
|
|
681
|
-
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
|
|
682
|
-
if (isMainModule) {
|
|
683
|
-
console.log('Starting cntx-ui server...');
|
|
684
|
-
const server = new CntxServer();
|
|
685
|
-
server.init();
|
|
686
|
-
server.listen(3333, 'localhost');
|
|
687
|
-
}
|