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.
Files changed (36) hide show
  1. package/dist/bin/cntx-ui.js +70 -0
  2. package/dist/lib/agent-runtime.js +269 -0
  3. package/dist/lib/agent-tools.js +162 -0
  4. package/dist/lib/api-router.js +387 -0
  5. package/dist/lib/bundle-manager.js +236 -0
  6. package/dist/lib/configuration-manager.js +230 -0
  7. package/dist/lib/database-manager.js +277 -0
  8. package/dist/lib/file-system-manager.js +305 -0
  9. package/dist/lib/function-level-chunker.js +144 -0
  10. package/dist/lib/heuristics-manager.js +491 -0
  11. package/dist/lib/mcp-server.js +159 -0
  12. package/dist/lib/mcp-transport.js +10 -0
  13. package/dist/lib/semantic-splitter.js +335 -0
  14. package/dist/lib/simple-vector-store.js +98 -0
  15. package/dist/lib/treesitter-semantic-chunker.js +277 -0
  16. package/dist/lib/websocket-manager.js +268 -0
  17. package/dist/server.js +225 -0
  18. package/package.json +18 -8
  19. package/bin/cntx-ui-mcp.sh +0 -3
  20. package/bin/cntx-ui.js +0 -123
  21. package/lib/agent-runtime.js +0 -371
  22. package/lib/agent-tools.js +0 -370
  23. package/lib/api-router.js +0 -1026
  24. package/lib/bundle-manager.js +0 -326
  25. package/lib/configuration-manager.js +0 -760
  26. package/lib/database-manager.js +0 -397
  27. package/lib/file-system-manager.js +0 -489
  28. package/lib/function-level-chunker.js +0 -406
  29. package/lib/heuristics-manager.js +0 -529
  30. package/lib/mcp-server.js +0 -1380
  31. package/lib/mcp-transport.js +0 -97
  32. package/lib/semantic-splitter.js +0 -304
  33. package/lib/simple-vector-store.js +0 -108
  34. package/lib/treesitter-semantic-chunker.js +0 -1485
  35. package/lib/websocket-manager.js +0 -470
  36. package/server.js +0 -687
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Treesitter-based Semantic Chunker for JavaScript/TypeScript and Rust Files
3
+ * Uses tree-sitter for true AST-based code analysis and semantic chunking
4
+ * Supports JS/TS/JSX/TSX and Rust with equal treatment
5
+ */
6
+ import { readFileSync, existsSync } from 'fs';
7
+ import { extname, basename, dirname, join } from 'path';
8
+ import { glob } from 'glob';
9
+ import Parser from 'tree-sitter';
10
+ import JavaScript from 'tree-sitter-javascript';
11
+ import TypeScript from 'tree-sitter-typescript';
12
+ import Rust from 'tree-sitter-rust';
13
+ class TreesitterSemanticChunker {
14
+ options;
15
+ parsers;
16
+ semanticPatterns;
17
+ constructor(options = {}) {
18
+ this.options = {
19
+ includeImports: true,
20
+ includeExports: true,
21
+ detectComponentTypes: true,
22
+ groupRelatedFiles: true,
23
+ minChunkSize: 100,
24
+ maxChunkSize: 50000,
25
+ namingStrategy: 'domain-based',
26
+ ...options
27
+ };
28
+ this.parsers = {};
29
+ this.initializeParsers();
30
+ this.semanticPatterns = {
31
+ reactComponent: this.isReactComponent.bind(this),
32
+ reactHook: this.isReactHook.bind(this),
33
+ expressRoute: this.isExpressRoute.bind(this),
34
+ expressMiddleware: this.isExpressMiddleware.bind(this),
35
+ cliCommand: this.isCliCommand.bind(this),
36
+ utilityFunction: this.isUtilityFunction.bind(this),
37
+ apiHandler: this.isApiHandler.bind(this),
38
+ typeDefinition: this.isTypeDefinition.bind(this),
39
+ configModule: this.isConfigModule.bind(this)
40
+ };
41
+ }
42
+ initializeParsers() {
43
+ this.parsers.javascript = new Parser();
44
+ this.parsers.javascript.setLanguage(JavaScript);
45
+ this.parsers.typescript = new Parser();
46
+ this.parsers.typescript.setLanguage(TypeScript.typescript);
47
+ this.parsers.tsx = new Parser();
48
+ this.parsers.tsx.setLanguage(TypeScript.tsx);
49
+ this.parsers.rust = new Parser();
50
+ this.parsers.rust.setLanguage(Rust);
51
+ }
52
+ getParser(filePath) {
53
+ const ext = extname(filePath);
54
+ switch (ext) {
55
+ case '.ts': return this.parsers.typescript;
56
+ case '.tsx': return this.parsers.tsx;
57
+ case '.rs': return this.parsers.rust;
58
+ case '.js':
59
+ case '.jsx':
60
+ default: return this.parsers.javascript;
61
+ }
62
+ }
63
+ async analyzeProject(projectPath, patterns = ['**/*.{js,jsx,ts,tsx,rs}']) {
64
+ console.log('🔍 Starting treesitter-based semantic analysis...');
65
+ const files = await this.findFiles(projectPath, patterns);
66
+ console.log(`📁 Found ${files.length} files to analyze`);
67
+ const analysis = await this.analyzeFiles(files, projectPath);
68
+ const successfulFiles = Object.keys(analysis).filter(f => !analysis[f].error);
69
+ const relationshipGraph = this.buildRelationshipGraph(analysis);
70
+ const chunks = await this.createSmartChunks(analysis, relationshipGraph);
71
+ return {
72
+ summary: this.generateSummary(analysis, chunks),
73
+ files: analysis,
74
+ chunks: chunks,
75
+ relationshipGraph,
76
+ recommendations: this.generateRecommendations(analysis, chunks)
77
+ };
78
+ }
79
+ async findFiles(projectPath, patterns) {
80
+ const files = [];
81
+ for (const pattern of patterns) {
82
+ const matches = await glob(pattern, {
83
+ cwd: projectPath,
84
+ ignore: ['node_modules/**', 'dist/**', 'build/**', '.git/**']
85
+ });
86
+ files.push(...matches);
87
+ }
88
+ return [...new Set(files)];
89
+ }
90
+ async analyzeFiles(filePaths, projectPath) {
91
+ const analysis = {};
92
+ for (const relativePath of filePaths) {
93
+ const fullPath = join(projectPath, relativePath);
94
+ if (!existsSync(fullPath))
95
+ continue;
96
+ try {
97
+ const content = readFileSync(fullPath, 'utf8');
98
+ const fileAnalysis = await this.analyzeFile(fullPath, content);
99
+ fileAnalysis.path = relativePath;
100
+ analysis[relativePath] = fileAnalysis;
101
+ }
102
+ catch (error) {
103
+ analysis[relativePath] = { error: error.message, path: relativePath };
104
+ }
105
+ }
106
+ return analysis;
107
+ }
108
+ async analyzeFile(filePath, content) {
109
+ const parser = this.getParser(filePath);
110
+ let tree, rootNode;
111
+ try {
112
+ tree = parser.parse(content);
113
+ rootNode = tree.rootNode;
114
+ }
115
+ catch (error) {
116
+ throw new Error(`Tree-sitter parse failed: ${error.message}`);
117
+ }
118
+ const analysis = {
119
+ path: filePath,
120
+ fileName: basename(filePath),
121
+ dirName: basename(dirname(filePath)),
122
+ extension: extname(filePath),
123
+ size: content.length,
124
+ lines: content.split('\n').length,
125
+ ast: {
126
+ functions: this.extractFunctions(rootNode, content),
127
+ classes: this.extractClasses(rootNode, content),
128
+ imports: this.extractImports(rootNode, content),
129
+ exports: this.extractExports(rootNode, content),
130
+ variables: this.extractVariables(rootNode, content),
131
+ jsxElements: this.extractJsxElements(rootNode, content),
132
+ typeDefinitions: this.extractTypeDefinitions(rootNode, content)
133
+ },
134
+ semanticType: this.classifyFileSemantics(rootNode, content, filePath),
135
+ businessDomain: this.extractBusinessDomain(rootNode, content, filePath),
136
+ technicalPatterns: this.identifyTechnicalPatterns(rootNode, content),
137
+ dependencies: this.analyzeDependencies(rootNode, content),
138
+ complexity: this.calculateAstComplexity(rootNode),
139
+ codeSignature: this.generateCodeSignature(rootNode, content),
140
+ semanticTags: []
141
+ };
142
+ analysis.semanticTags = this.generateSemanticTags(analysis);
143
+ return analysis;
144
+ }
145
+ extractFunctions(rootNode, content) {
146
+ const functions = [];
147
+ const functionDeclarations = this.queryNode(rootNode, ['function_declaration']);
148
+ functions.push(...functionDeclarations.map(capture => ({
149
+ name: this.getNodeText(capture.node, content),
150
+ type: 'function_declaration',
151
+ startPosition: capture.node.startPosition,
152
+ isExported: this.isNodeExported(capture.node)
153
+ })));
154
+ const rustFunctions = this.queryNode(rootNode, ['function_item']);
155
+ functions.push(...rustFunctions.map(capture => ({
156
+ name: this.getNodeText(capture.node, content),
157
+ type: 'function_item',
158
+ startPosition: capture.node.startPosition,
159
+ isExported: this.isNodeExported(capture.node)
160
+ })));
161
+ return functions;
162
+ }
163
+ extractClasses(rootNode, content) {
164
+ return [];
165
+ }
166
+ extractImports(rootNode, content) {
167
+ return [];
168
+ }
169
+ extractExports(rootNode, content) {
170
+ return [];
171
+ }
172
+ extractVariables(rootNode, content) {
173
+ return [];
174
+ }
175
+ extractJsxElements(rootNode, content) {
176
+ return [];
177
+ }
178
+ extractTypeDefinitions(rootNode, content) {
179
+ return [];
180
+ }
181
+ classifyFileSemantics(rootNode, content, filePath) {
182
+ return 'module';
183
+ }
184
+ isReactComponent(rootNode, content, filePath) {
185
+ return false;
186
+ }
187
+ isReactHook(rootNode, content, filePath) {
188
+ return false;
189
+ }
190
+ isExpressRoute(rootNode, content, filePath) {
191
+ return false;
192
+ }
193
+ isExpressMiddleware(rootNode, content, filePath) {
194
+ return false;
195
+ }
196
+ isCliCommand(rootNode, content, filePath) {
197
+ return false;
198
+ }
199
+ isUtilityFunction(rootNode, content, filePath) {
200
+ return false;
201
+ }
202
+ isApiHandler(rootNode, content, filePath) {
203
+ return false;
204
+ }
205
+ isTypeDefinition(rootNode, content, filePath) {
206
+ return false;
207
+ }
208
+ isConfigModule(rootNode, content, filePath) {
209
+ return false;
210
+ }
211
+ extractBusinessDomain(rootNode, content, filePath) {
212
+ return [];
213
+ }
214
+ identifyTechnicalPatterns(rootNode, content) {
215
+ return [];
216
+ }
217
+ buildRelationshipGraph(analysis) {
218
+ return {};
219
+ }
220
+ async createSmartChunks(analysis, relationshipGraph) {
221
+ return [];
222
+ }
223
+ generateSummary(analysis, chunks) {
224
+ return {};
225
+ }
226
+ generateRecommendations(analysis, chunks) {
227
+ return [];
228
+ }
229
+ queryNode(node, types) {
230
+ const results = [];
231
+ const traverse = (currentNode) => {
232
+ if (types.includes(currentNode.type)) {
233
+ results.push({ node: currentNode });
234
+ }
235
+ for (let i = 0; i < currentNode.namedChildCount; i++) {
236
+ const child = currentNode.namedChild(i);
237
+ if (child)
238
+ traverse(child);
239
+ }
240
+ };
241
+ traverse(node);
242
+ return results;
243
+ }
244
+ getNodeText(node, content) {
245
+ return content.slice(node.startIndex, node.endIndex);
246
+ }
247
+ isNodeExported(node) {
248
+ let parent = node.parent;
249
+ while (parent) {
250
+ if (parent.type === 'export_statement' || parent.type === 'export_declaration' || parent.type === 'visibility_modifier') {
251
+ return true;
252
+ }
253
+ parent = parent.parent;
254
+ }
255
+ return false;
256
+ }
257
+ calculateOverlap(arrayA, arrayB) {
258
+ const setA = new Set(arrayA);
259
+ const setB = new Set(arrayB);
260
+ const intersection = new Set([...setA].filter(x => setB.has(x)));
261
+ const union = new Set([...setA, ...setB]);
262
+ return union.size === 0 ? 0 : intersection.size / union.size;
263
+ }
264
+ generateSemanticTags(analysis) {
265
+ return [];
266
+ }
267
+ calculateAstComplexity(rootNode) {
268
+ return { score: 1, level: 'low' };
269
+ }
270
+ analyzeDependencies(rootNode, content) {
271
+ return { internal: [], external: [], relative: [] };
272
+ }
273
+ generateCodeSignature(rootNode, content) {
274
+ return {};
275
+ }
276
+ }
277
+ export default TreesitterSemanticChunker;
@@ -0,0 +1,268 @@
1
+ /**
2
+ * WebSocket Manager for cntx-ui
3
+ * Handles real-time communication between server and dashboard
4
+ */
5
+ import { WebSocketServer, WebSocket } from 'ws';
6
+ export default class WebSocketManager {
7
+ bundleManager;
8
+ configManager;
9
+ verbose;
10
+ clients;
11
+ wss;
12
+ constructor(bundleManager, configManager, options = {}) {
13
+ this.bundleManager = bundleManager;
14
+ this.configManager = configManager;
15
+ this.verbose = options.verbose || false;
16
+ this.clients = new Set();
17
+ this.wss = null;
18
+ }
19
+ initialize(httpServer) {
20
+ this.wss = new WebSocketServer({ server: httpServer });
21
+ this.wss.on('connection', (ws) => {
22
+ this.handleConnection(ws);
23
+ });
24
+ if (this.verbose) {
25
+ console.log('🔌 WebSocket server initialized');
26
+ }
27
+ }
28
+ handleConnection(ws) {
29
+ this.clients.add(ws);
30
+ if (this.verbose) {
31
+ console.log(`📱 WebSocket client connected (${this.clients.size} total clients)`);
32
+ }
33
+ // Send initial status
34
+ this.sendUpdate(ws);
35
+ ws.on('close', () => {
36
+ this.clients.delete(ws);
37
+ if (this.verbose) {
38
+ console.log(`📱 WebSocket client disconnected (${this.clients.size} total clients)`);
39
+ }
40
+ });
41
+ ws.on('error', (error) => {
42
+ if (this.verbose) {
43
+ console.error('WebSocket client error:', error.message);
44
+ }
45
+ this.clients.delete(ws);
46
+ });
47
+ ws.on('message', (message) => {
48
+ try {
49
+ const data = JSON.parse(message.toString());
50
+ this.handleClientMessage(ws, data);
51
+ }
52
+ catch (error) {
53
+ if (this.verbose) {
54
+ console.error('Invalid WebSocket message:', error.message);
55
+ }
56
+ }
57
+ });
58
+ }
59
+ handleClientMessage(ws, data) {
60
+ if (this.verbose) {
61
+ console.log('📩 Received client message:', data.type);
62
+ }
63
+ switch (data.type) {
64
+ case 'ping':
65
+ ws.send(JSON.stringify({ type: 'pong', timestamp: Date.now() }));
66
+ break;
67
+ case 'get_status':
68
+ this.sendUpdate(ws);
69
+ break;
70
+ default:
71
+ if (this.verbose) {
72
+ console.warn('Unknown message type:', data.type);
73
+ }
74
+ }
75
+ }
76
+ get connectedClientsCount() {
77
+ return this.clients.size;
78
+ }
79
+ broadcast(type, payload) {
80
+ const data = JSON.stringify({ type, payload, timestamp: Date.now() });
81
+ this.clients.forEach(client => {
82
+ if (client.readyState === WebSocket.OPEN) {
83
+ try {
84
+ client.send(data);
85
+ }
86
+ catch (error) {
87
+ this.clients.delete(client);
88
+ }
89
+ }
90
+ });
91
+ }
92
+ /**
93
+ * Broadcast a general status update
94
+ */
95
+ broadcastUpdate() {
96
+ if (this.verbose) {
97
+ console.log('📢 Broadcasting status update to all clients');
98
+ }
99
+ this.clients.forEach(client => {
100
+ this.sendUpdate(client);
101
+ });
102
+ }
103
+ sendUpdate(client) {
104
+ if (client.readyState !== WebSocket.OPEN)
105
+ return;
106
+ try {
107
+ const bundles = this.configManager.getBundles();
108
+ const bundleData = Array.from(bundles.entries()).map(([name, bundle]) => ({
109
+ name,
110
+ changed: bundle.changed,
111
+ fileCount: bundle.files.length,
112
+ content: bundle.content ? bundle.content.substring(0, 200) + '...' : '',
113
+ files: bundle.files,
114
+ lastGenerated: bundle.generated,
115
+ size: bundle.size,
116
+ patterns: bundle.patterns
117
+ }));
118
+ const updateData = {
119
+ type: 'status_update',
120
+ payload: {
121
+ bundles: bundleData,
122
+ scanning: this.bundleManager.isScanning || false,
123
+ totalFiles: this.bundleManager.fileSystemManager?.getAllFiles()?.length || 0
124
+ },
125
+ timestamp: Date.now()
126
+ };
127
+ client.send(JSON.stringify(updateData));
128
+ }
129
+ catch (error) {
130
+ if (this.verbose) {
131
+ console.error('Failed to send WebSocket update:', error.message);
132
+ }
133
+ this.clients.delete(client);
134
+ }
135
+ }
136
+ broadcastBundleUpdate(bundleName) {
137
+ if (this.verbose) {
138
+ console.log(`📢 Broadcasting update for bundle: ${bundleName}`);
139
+ }
140
+ const bundle = this.configManager.getBundles().get(bundleName);
141
+ if (!bundle)
142
+ return;
143
+ const updateData = {
144
+ type: 'bundle_update',
145
+ payload: {
146
+ name: bundleName,
147
+ changed: bundle.changed,
148
+ fileCount: bundle.files.length,
149
+ size: bundle.size,
150
+ generated: bundle.generated
151
+ },
152
+ timestamp: Date.now()
153
+ };
154
+ this.broadcastToActiveClients(updateData);
155
+ }
156
+ broadcastFileChange(filename, eventType) {
157
+ const updateData = {
158
+ type: 'file_change',
159
+ payload: {
160
+ filename,
161
+ eventType
162
+ },
163
+ timestamp: Date.now()
164
+ };
165
+ this.broadcastToActiveClients(updateData);
166
+ }
167
+ broadcastStatusUpdate(status) {
168
+ const updateData = {
169
+ type: 'status',
170
+ payload: status,
171
+ timestamp: Date.now()
172
+ };
173
+ this.broadcastToActiveClients(updateData);
174
+ }
175
+ ping() {
176
+ const pingData = { type: 'ping', timestamp: Date.now() };
177
+ this.clients.forEach(client => {
178
+ if (client.readyState === WebSocket.OPEN) {
179
+ client.send(JSON.stringify(pingData));
180
+ }
181
+ else {
182
+ this.clients.delete(client);
183
+ }
184
+ });
185
+ }
186
+ close() {
187
+ if (this.wss) {
188
+ if (this.verbose) {
189
+ console.log('🔌 Closing WebSocket server');
190
+ }
191
+ this.clients.forEach(client => {
192
+ try {
193
+ client.terminate();
194
+ }
195
+ catch (error) {
196
+ if (this.verbose) {
197
+ console.error('Error closing WebSocket client:', error.message);
198
+ }
199
+ }
200
+ });
201
+ this.wss.close(() => {
202
+ if (this.verbose) {
203
+ console.log('🔌 WebSocket server closed');
204
+ }
205
+ });
206
+ this.clients.clear();
207
+ }
208
+ }
209
+ getStatus() {
210
+ return {
211
+ connected: this.clients.size,
212
+ server: this.wss ? 'running' : 'stopped'
213
+ };
214
+ }
215
+ onBundleGenerated(bundleName) {
216
+ this.broadcastBundleUpdate(bundleName);
217
+ }
218
+ onFileChanged(filename, eventType) {
219
+ this.broadcastFileChange(filename, eventType);
220
+ // Also broadcast bundle changes if needed
221
+ const affectedBundles = this.getAffectedBundles(filename);
222
+ affectedBundles.forEach(bundleName => {
223
+ this.broadcastBundleUpdate(bundleName);
224
+ });
225
+ }
226
+ onBundleSyncStarted(bundleName) {
227
+ this.broadcast('bundle_sync_started', { name: bundleName });
228
+ }
229
+ onBundleSyncCompleted(bundleName) {
230
+ this.broadcast('bundle_sync_completed', { name: bundleName });
231
+ }
232
+ onBundleSyncFailed(bundleName, error) {
233
+ this.broadcast('bundle_sync_failed', { name: bundleName, error: error.message });
234
+ }
235
+ broadcastBundleFileChanged(bundleName, filename) {
236
+ this.broadcast('bundle_file_changed', { bundleName, filename });
237
+ }
238
+ broadcastBundleSyncCompleted(bundleName) {
239
+ this.broadcast('bundle_sync_completed', { bundleName });
240
+ }
241
+ broadcastToActiveClients(data) {
242
+ const message = JSON.stringify(data);
243
+ this.clients.forEach(client => {
244
+ if (client.readyState === WebSocket.OPEN) {
245
+ try {
246
+ client.send(message);
247
+ }
248
+ catch (error) {
249
+ if (this.verbose) {
250
+ console.error('Failed to send WebSocket update:', error.message);
251
+ }
252
+ this.clients.delete(client);
253
+ }
254
+ }
255
+ });
256
+ }
257
+ getAffectedBundles(filename) {
258
+ const bundles = this.configManager.getBundles();
259
+ const affectedBundles = [];
260
+ bundles.forEach((bundle, name) => {
261
+ const matchesBundle = bundle.patterns.some(pattern => this.bundleManager.fileSystemManager.matchesPattern(filename, pattern));
262
+ if (matchesBundle) {
263
+ affectedBundles.push(name);
264
+ }
265
+ });
266
+ return affectedBundles;
267
+ }
268
+ }