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/lib/agent-tools.js
DELETED
|
@@ -1,370 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent Tools for Codebase Exploration
|
|
3
|
-
* Built on top of existing cntx-ui infrastructure
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { readFileSync, existsSync, statSync } from 'fs';
|
|
7
|
-
import { join, relative, extname } from 'path';
|
|
8
|
-
import fs from 'fs';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import { exec } from 'child_process';
|
|
11
|
-
import { promisify } from 'util';
|
|
12
|
-
|
|
13
|
-
const execAsync = promisify(exec);
|
|
14
|
-
|
|
15
|
-
export class AgentTools {
|
|
16
|
-
constructor(cntxServer) {
|
|
17
|
-
this.cntxServer = cntxServer;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Read file contents with bundle context
|
|
22
|
-
*/
|
|
23
|
-
async readFile(filePath, options = {}) {
|
|
24
|
-
try {
|
|
25
|
-
const fullPath = join(this.cntxServer.CWD, filePath);
|
|
26
|
-
|
|
27
|
-
if (!existsSync(fullPath)) {
|
|
28
|
-
throw new Error(`File not found: ${filePath}`);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const content = readFileSync(fullPath, 'utf8');
|
|
32
|
-
const bundles = this.getFileBundles(filePath);
|
|
33
|
-
|
|
34
|
-
return {
|
|
35
|
-
path: filePath,
|
|
36
|
-
content: options.truncate ? this.truncateContent(content, options.maxLength) : content,
|
|
37
|
-
size: content.length,
|
|
38
|
-
lines: content.split('\n').length,
|
|
39
|
-
bundles,
|
|
40
|
-
mimeType: this.getMimeType(filePath)
|
|
41
|
-
};
|
|
42
|
-
} catch (error) {
|
|
43
|
-
throw new Error(`Failed to read file ${filePath}: ${error.message}`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* List files with bundle awareness and filtering
|
|
49
|
-
*/
|
|
50
|
-
async listFiles(options = {}) {
|
|
51
|
-
const { bundle, pattern, type, limit = 100 } = options;
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
let files = [];
|
|
55
|
-
|
|
56
|
-
if (bundle) {
|
|
57
|
-
const bundleObj = this.cntxServer.bundles.get(bundle);
|
|
58
|
-
if (!bundleObj) {
|
|
59
|
-
throw new Error(`Bundle '${bundle}' not found`);
|
|
60
|
-
}
|
|
61
|
-
files = bundleObj.files.map(f => ({
|
|
62
|
-
path: f,
|
|
63
|
-
bundle,
|
|
64
|
-
size: this.getFileSize(f),
|
|
65
|
-
type: this.getFileType(f)
|
|
66
|
-
}));
|
|
67
|
-
} else {
|
|
68
|
-
// Get all files across bundles
|
|
69
|
-
const allFiles = this.cntxServer.getAllFiles();
|
|
70
|
-
files = allFiles.map(f => ({
|
|
71
|
-
path: f,
|
|
72
|
-
bundles: this.getFileBundles(f),
|
|
73
|
-
size: this.getFileSize(f),
|
|
74
|
-
type: this.getFileType(f)
|
|
75
|
-
}));
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Apply filters
|
|
79
|
-
if (pattern) {
|
|
80
|
-
const regex = new RegExp(pattern, 'i');
|
|
81
|
-
files = files.filter(f => regex.test(f.path));
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (type) {
|
|
85
|
-
files = files.filter(f => f.type === type);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Limit results
|
|
89
|
-
return files.slice(0, limit);
|
|
90
|
-
} catch (error) {
|
|
91
|
-
throw new Error(`Failed to list files: ${error.message}`);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Search semantic chunks using existing vector search
|
|
97
|
-
*/
|
|
98
|
-
async searchSemanticChunks(query, options = {}) {
|
|
99
|
-
try {
|
|
100
|
-
const analysis = await this.cntxServer.getSemanticAnalysis();
|
|
101
|
-
if (!analysis || !analysis.chunks) {
|
|
102
|
-
return { chunks: [], message: 'No semantic analysis available' };
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
let chunks = analysis.chunks;
|
|
106
|
-
const { bundle, type, complexity, maxResults = 10 } = options;
|
|
107
|
-
|
|
108
|
-
// Apply semantic search if vector store is available
|
|
109
|
-
if (this.cntxServer.vectorStoreInitialized) {
|
|
110
|
-
try {
|
|
111
|
-
const searchResults = await this.cntxServer.vectorStore.search(query, { limit: maxResults * 2 });
|
|
112
|
-
const chunkIds = searchResults.map(r => r.id || r.chunkId).filter(Boolean);
|
|
113
|
-
chunks = chunks.filter(c => chunkIds.includes(c.id));
|
|
114
|
-
} catch (error) {
|
|
115
|
-
// Fall back to text-based search
|
|
116
|
-
chunks = chunks.filter(c =>
|
|
117
|
-
c.purpose?.toLowerCase().includes(query.toLowerCase()) ||
|
|
118
|
-
c.name?.toLowerCase().includes(query.toLowerCase()) ||
|
|
119
|
-
c.code?.toLowerCase().includes(query.toLowerCase())
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
} else {
|
|
123
|
-
// Text-based semantic search
|
|
124
|
-
chunks = chunks.filter(c =>
|
|
125
|
-
c.purpose?.toLowerCase().includes(query.toLowerCase()) ||
|
|
126
|
-
c.name?.toLowerCase().includes(query.toLowerCase()) ||
|
|
127
|
-
c.code?.toLowerCase().includes(query.toLowerCase())
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Apply filters
|
|
132
|
-
if (bundle) {
|
|
133
|
-
chunks = chunks.filter(c => c.bundles && c.bundles.includes(bundle));
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (type) {
|
|
137
|
-
chunks = chunks.filter(c => c.subtype === type);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (complexity) {
|
|
141
|
-
chunks = chunks.filter(c => c.complexity?.level === complexity);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Clean and limit results
|
|
145
|
-
const cleanChunks = chunks.slice(0, maxResults).map(chunk => ({
|
|
146
|
-
...chunk,
|
|
147
|
-
code: this.truncateContent(chunk.code, 500),
|
|
148
|
-
bundles: chunk.bundles || [],
|
|
149
|
-
relevanceScore: chunk.score || 0
|
|
150
|
-
}));
|
|
151
|
-
|
|
152
|
-
return {
|
|
153
|
-
query,
|
|
154
|
-
chunks: cleanChunks,
|
|
155
|
-
totalResults: chunks.length,
|
|
156
|
-
hasMore: chunks.length > maxResults
|
|
157
|
-
};
|
|
158
|
-
} catch (error) {
|
|
159
|
-
throw new Error(`Semantic search failed: ${error.message}`);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Get bundle information
|
|
165
|
-
*/
|
|
166
|
-
async getBundle(bundleName) {
|
|
167
|
-
try {
|
|
168
|
-
const bundle = this.cntxServer.bundles.get(bundleName);
|
|
169
|
-
if (!bundle) {
|
|
170
|
-
throw new Error(`Bundle '${bundleName}' not found`);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return {
|
|
174
|
-
name: bundleName,
|
|
175
|
-
patterns: bundle.patterns,
|
|
176
|
-
files: bundle.files,
|
|
177
|
-
fileCount: bundle.files.length,
|
|
178
|
-
size: bundle.size,
|
|
179
|
-
lastGenerated: bundle.lastGenerated,
|
|
180
|
-
changed: bundle.changed,
|
|
181
|
-
content: bundle.content ? this.truncateContent(bundle.content, 2000) : null
|
|
182
|
-
};
|
|
183
|
-
} catch (error) {
|
|
184
|
-
throw new Error(`Failed to get bundle: ${error.message}`);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Parse AST using existing tree-sitter infrastructure
|
|
190
|
-
*/
|
|
191
|
-
async parseAST(filePath, options = {}) {
|
|
192
|
-
try {
|
|
193
|
-
const fullPath = join(this.cntxServer.CWD, filePath);
|
|
194
|
-
|
|
195
|
-
if (!existsSync(fullPath)) {
|
|
196
|
-
throw new Error(`File not found: ${filePath}`);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Use existing semantic analysis if available
|
|
200
|
-
const analysis = await this.cntxServer.getSemanticAnalysis();
|
|
201
|
-
const fileChunks = analysis?.chunks?.filter(c => c.filePath === filePath) || [];
|
|
202
|
-
|
|
203
|
-
if (fileChunks.length > 0) {
|
|
204
|
-
return {
|
|
205
|
-
file: filePath,
|
|
206
|
-
chunks: fileChunks.map(chunk => ({
|
|
207
|
-
name: chunk.name,
|
|
208
|
-
type: chunk.subtype,
|
|
209
|
-
purpose: chunk.purpose,
|
|
210
|
-
startLine: chunk.startLine,
|
|
211
|
-
endLine: chunk.endLine,
|
|
212
|
-
complexity: chunk.complexity,
|
|
213
|
-
isExported: chunk.isExported,
|
|
214
|
-
isAsync: chunk.isAsync,
|
|
215
|
-
imports: chunk.includes?.imports || [],
|
|
216
|
-
dependencies: chunk.dependencies || []
|
|
217
|
-
}))
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Fallback: basic file info
|
|
222
|
-
const content = readFileSync(fullPath, 'utf8');
|
|
223
|
-
return {
|
|
224
|
-
file: filePath,
|
|
225
|
-
lines: content.split('\n').length,
|
|
226
|
-
size: content.length,
|
|
227
|
-
type: this.getFileType(filePath),
|
|
228
|
-
message: 'AST parsing requires semantic analysis to be run first'
|
|
229
|
-
};
|
|
230
|
-
} catch (error) {
|
|
231
|
-
throw new Error(`AST parsing failed: ${error.message}`);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Execute safe CLI commands (restricted set)
|
|
237
|
-
*/
|
|
238
|
-
async runCommand(command, options = {}) {
|
|
239
|
-
const { timeout = 10000, cwd } = options;
|
|
240
|
-
|
|
241
|
-
// Whitelist of safe commands
|
|
242
|
-
const safeCommands = [
|
|
243
|
-
'ls', 'find', 'grep', 'wc', 'head', 'tail',
|
|
244
|
-
'git status', 'git log', 'git diff', 'git branch',
|
|
245
|
-
'npm list', 'npm outdated', 'npm audit',
|
|
246
|
-
'node --version', 'npm --version'
|
|
247
|
-
];
|
|
248
|
-
|
|
249
|
-
const isCommandSafe = safeCommands.some(safe => command.startsWith(safe));
|
|
250
|
-
|
|
251
|
-
if (!isCommandSafe) {
|
|
252
|
-
throw new Error(`Command not allowed: ${command}. Only safe read-only commands are permitted.`);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
try {
|
|
256
|
-
const { stdout, stderr } = await execAsync(command, {
|
|
257
|
-
cwd: cwd || this.cntxServer.CWD,
|
|
258
|
-
timeout,
|
|
259
|
-
maxBuffer: 1024 * 1024 // 1MB limit
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
return {
|
|
263
|
-
command,
|
|
264
|
-
stdout: stdout.trim(),
|
|
265
|
-
stderr: stderr.trim(),
|
|
266
|
-
success: true
|
|
267
|
-
};
|
|
268
|
-
} catch (error) {
|
|
269
|
-
return {
|
|
270
|
-
command,
|
|
271
|
-
stdout: error.stdout || '',
|
|
272
|
-
stderr: error.stderr || error.message,
|
|
273
|
-
success: false,
|
|
274
|
-
error: error.message
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Get full semantic analysis
|
|
281
|
-
*/
|
|
282
|
-
async getSemanticAnalysis(options = {}) {
|
|
283
|
-
try {
|
|
284
|
-
const analysis = await this.cntxServer.getSemanticAnalysis();
|
|
285
|
-
|
|
286
|
-
if (!analysis) {
|
|
287
|
-
return { message: 'No semantic analysis available. Run semantic analysis first.' };
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const { includeCode = false, maxChunks = 50 } = options;
|
|
291
|
-
|
|
292
|
-
return {
|
|
293
|
-
timestamp: analysis.timestamp,
|
|
294
|
-
summary: analysis.summary,
|
|
295
|
-
chunks: analysis.chunks.slice(0, maxChunks).map(chunk => ({
|
|
296
|
-
...chunk,
|
|
297
|
-
code: includeCode ? chunk.code : this.truncateContent(chunk.code, 200),
|
|
298
|
-
bundles: chunk.bundles || []
|
|
299
|
-
})),
|
|
300
|
-
totalChunks: analysis.chunks?.length || 0,
|
|
301
|
-
truncated: analysis.chunks?.length > maxChunks
|
|
302
|
-
};
|
|
303
|
-
} catch (error) {
|
|
304
|
-
throw new Error(`Failed to get semantic analysis: ${error.message}`);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// Helper methods
|
|
309
|
-
|
|
310
|
-
getFileBundles(filePath) {
|
|
311
|
-
const bundles = [];
|
|
312
|
-
for (const [bundleName, bundle] of this.cntxServer.bundles) {
|
|
313
|
-
if (bundle.files.includes(filePath)) {
|
|
314
|
-
bundles.push(bundleName);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
return bundles;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
getFileSize(filePath) {
|
|
321
|
-
try {
|
|
322
|
-
const fullPath = join(this.cntxServer.CWD, filePath);
|
|
323
|
-
const stats = statSync(fullPath);
|
|
324
|
-
return stats.size;
|
|
325
|
-
} catch {
|
|
326
|
-
return 0;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
getFileType(filePath) {
|
|
331
|
-
const ext = extname(filePath).toLowerCase();
|
|
332
|
-
const typeMap = {
|
|
333
|
-
'.js': 'javascript',
|
|
334
|
-
'.jsx': 'javascript',
|
|
335
|
-
'.ts': 'typescript',
|
|
336
|
-
'.tsx': 'typescript',
|
|
337
|
-
'.json': 'json',
|
|
338
|
-
'.md': 'markdown',
|
|
339
|
-
'.css': 'css',
|
|
340
|
-
'.html': 'html',
|
|
341
|
-
'.py': 'python',
|
|
342
|
-
'.java': 'java',
|
|
343
|
-
'.go': 'go',
|
|
344
|
-
'.rs': 'rust'
|
|
345
|
-
};
|
|
346
|
-
return typeMap[ext] || 'text';
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
getMimeType(filePath) {
|
|
350
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
351
|
-
const mimeTypes = {
|
|
352
|
-
'.js': 'application/javascript',
|
|
353
|
-
'.jsx': 'application/javascript',
|
|
354
|
-
'.ts': 'application/typescript',
|
|
355
|
-
'.tsx': 'application/typescript',
|
|
356
|
-
'.json': 'application/json',
|
|
357
|
-
'.md': 'text/markdown',
|
|
358
|
-
'.css': 'text/css',
|
|
359
|
-
'.html': 'text/html'
|
|
360
|
-
};
|
|
361
|
-
return mimeTypes[ext] || 'text/plain';
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
truncateContent(content, maxLength = 1000) {
|
|
365
|
-
if (!content || content.length <= maxLength) return content;
|
|
366
|
-
return content.substring(0, maxLength) + '...';
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
export default AgentTools;
|