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,305 @@
1
+ /**
2
+ * File System Manager for cntx-ui
3
+ * Handles file operations, pattern matching, and directory traversal
4
+ */
5
+ import { readdirSync, readFileSync, statSync, existsSync, watch } from 'fs';
6
+ import { join, relative, extname, basename } from 'path';
7
+ export default class FileSystemManager {
8
+ CWD;
9
+ verbose;
10
+ watchers;
11
+ ignorePatterns;
12
+ constructor(cwd = process.cwd(), options = {}) {
13
+ this.CWD = cwd;
14
+ this.verbose = options.verbose || false;
15
+ this.watchers = [];
16
+ this.ignorePatterns = [];
17
+ this.loadIgnorePatterns();
18
+ }
19
+ loadIgnorePatterns() {
20
+ const ignorePath = join(this.CWD, '.cntxignore');
21
+ if (existsSync(ignorePath)) {
22
+ try {
23
+ const content = readFileSync(ignorePath, 'utf8');
24
+ const patterns = content.split('\n')
25
+ .map(line => line.trim())
26
+ .filter(line => line && !line.startsWith('#'));
27
+ this.ignorePatterns = patterns;
28
+ if (this.verbose)
29
+ console.log(`📋 Loaded ${patterns.length} patterns from .cntxignore`);
30
+ }
31
+ catch (e) {
32
+ console.error('Failed to load .cntxignore:', e.message);
33
+ }
34
+ }
35
+ }
36
+ // === File Traversal ===
37
+ getAllFiles(dir = this.CWD, files = []) {
38
+ try {
39
+ const items = readdirSync(dir);
40
+ for (const item of items) {
41
+ const fullPath = join(dir, item);
42
+ // Skip if should be ignored
43
+ if (this.shouldIgnoreAnything(item, fullPath)) {
44
+ continue;
45
+ }
46
+ try {
47
+ const stat = statSync(fullPath);
48
+ if (stat.isDirectory()) {
49
+ this.getAllFiles(fullPath, files);
50
+ }
51
+ else if (stat.isFile()) {
52
+ if (!this.shouldIgnoreFile(fullPath)) {
53
+ files.push(fullPath);
54
+ }
55
+ }
56
+ }
57
+ catch (error) {
58
+ // Skip files we can't stat (permission issues, broken symlinks, etc.)
59
+ continue;
60
+ }
61
+ }
62
+ }
63
+ catch (error) {
64
+ if (this.verbose) {
65
+ console.warn(`Cannot read directory ${dir}: ${error.message}`);
66
+ }
67
+ }
68
+ return files;
69
+ }
70
+ getFileTree() {
71
+ const files = this.getAllFiles();
72
+ return files.map(file => {
73
+ const stats = this.getFileStats(file);
74
+ const relativePath = relative(this.CWD, file);
75
+ return {
76
+ path: relativePath,
77
+ fullPath: file,
78
+ size: stats.size,
79
+ modified: stats.mtime.toISOString(),
80
+ type: this.getFileType(file)
81
+ };
82
+ });
83
+ }
84
+ // === Pattern Matching ===
85
+ matchesPattern(path, pattern) {
86
+ // Convert glob pattern to regex
87
+ let regexPattern = pattern
88
+ .replace(/\./g, '\\.') // Escape dots
89
+ .replace(/\*\*/g, '___GLOBSTAR___') // Temporary replace **
90
+ .replace(/\*/g, '[^/]*') // * matches anything except /
91
+ .replace(/___GLOBSTAR___/g, '.*') // ** matches anything including /
92
+ .replace(/\?/g, '[^/]'); // ? matches single char except /
93
+ // Ensure pattern matches from start or after a directory separator
94
+ if (!regexPattern.startsWith('.*') && !regexPattern.startsWith('[^/]*')) {
95
+ regexPattern = '(^|/)' + regexPattern;
96
+ }
97
+ // Ensure pattern matches to end or before a directory separator
98
+ if (!regexPattern.endsWith('.*') && !regexPattern.endsWith('[^/]*')) {
99
+ regexPattern = regexPattern + '($|/)';
100
+ }
101
+ const regex = new RegExp(regexPattern);
102
+ const relativePath = relative(this.CWD, path);
103
+ return regex.test(relativePath) || regex.test(path);
104
+ }
105
+ shouldIgnoreFile(filePath) {
106
+ const relativePath = relative(this.CWD, filePath);
107
+ return this.ignorePatterns.some(pattern => this.matchesPattern(filePath, pattern) ||
108
+ this.matchesPattern(relativePath, pattern));
109
+ }
110
+ shouldIgnoreAnything(itemName, fullPath) {
111
+ // Hardcoded bad directories and files to always ignore
112
+ const badDirs = [
113
+ 'node_modules', '.git', '.svn', '.hg', '.bzr', '_darcs',
114
+ 'CVS', '.cvs', 'RCS', 'SCCS', '{arch}', '.arch-ids',
115
+ '.monotone', '_MTN', '.fslckout', '_FOSSIL_',
116
+ '.fos', 'BitKeeper', 'ChangeSet', '.teamcity',
117
+ '.idea', '.vscode', '.vs', '.gradle', '.settings',
118
+ 'target', 'build', 'dist', 'out', 'bin', 'obj',
119
+ '.next', '.nuxt', '.vite', '.tmp', '.temp',
120
+ '__pycache__', '.pytest_cache', '.coverage',
121
+ '.nyc_output', 'coverage', 'lcov-report'
122
+ ];
123
+ const badExtensions = [
124
+ '.log', '.tmp', '.temp', '.cache', '.pid', '.lock',
125
+ '.swp', '.swo', '.DS_Store', 'Thumbs.db', '.env',
126
+ '.min.js', '.min.css', '.map', '.pyc', '.pyo',
127
+ '.class', '.jar', '.exe', '.dll', '.so', '.dylib',
128
+ '.o', '.a', '.obj', '.lib', '.pdb'
129
+ ];
130
+ const badFiles = [
131
+ '.gitignore', '.gitkeep', '.gitattributes',
132
+ '.eslintcache', '.prettierignore',
133
+ 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml',
134
+ 'npm-debug.log', 'yarn-debug.log', 'yarn-error.log'
135
+ ];
136
+ // Check bad directories
137
+ if (badDirs.includes(itemName)) {
138
+ return true;
139
+ }
140
+ // Check bad extensions
141
+ const ext = extname(itemName);
142
+ if (badExtensions.includes(ext)) {
143
+ return true;
144
+ }
145
+ // Check bad files
146
+ if (badFiles.includes(itemName)) {
147
+ return true;
148
+ }
149
+ // Check if it's a hidden file/directory (starts with .)
150
+ if (itemName.startsWith('.') && itemName !== '.cntx') {
151
+ return true;
152
+ }
153
+ // Check ignore patterns if loaded
154
+ if (this.ignorePatterns.length > 0) {
155
+ const relativePath = relative(this.CWD, fullPath);
156
+ if (this.ignorePatterns.some(pattern => this.matchesPattern(fullPath, pattern) ||
157
+ this.matchesPattern(relativePath, pattern))) {
158
+ return true;
159
+ }
160
+ }
161
+ return false;
162
+ }
163
+ // === File Metadata ===
164
+ getFileStats(filePath) {
165
+ try {
166
+ return statSync(filePath);
167
+ }
168
+ catch (error) {
169
+ // Return a mock stats object for missing files
170
+ return {
171
+ size: 0,
172
+ mtime: new Date(0),
173
+ ctime: new Date(0),
174
+ isDirectory: () => false,
175
+ isFile: () => false
176
+ };
177
+ }
178
+ }
179
+ getFileType(filePath) {
180
+ const ext = extname(filePath).toLowerCase();
181
+ const fileName = basename(filePath).toLowerCase();
182
+ // Programming languages
183
+ if (ext.match(/\.(js|jsx|mjs|cjs)$/))
184
+ return 'javascript';
185
+ if (ext.match(/\.(ts|tsx)$/))
186
+ return 'typescript';
187
+ if (ext.match(/\.(py|pyw)$/))
188
+ return 'python';
189
+ if (ext.match(/\.(java|class)$/))
190
+ return 'java';
191
+ if (ext.match(/\.(c|h)$/))
192
+ return 'c';
193
+ if (ext.match(/\.(cpp|cxx|cc|hpp|hxx)$/))
194
+ return 'cpp';
195
+ if (ext.match(/\.(cs)$/))
196
+ return 'csharp';
197
+ if (ext.match(/\.(go)$/))
198
+ return 'go';
199
+ if (ext.match(/\.(rs)$/))
200
+ return 'rust';
201
+ if (ext.match(/\.(php)$/))
202
+ return 'php';
203
+ if (ext.match(/\.(rb)$/))
204
+ return 'ruby';
205
+ // Web technologies
206
+ if (ext.match(/\.(html|htm)$/))
207
+ return 'html';
208
+ if (ext.match(/\.(css|scss|sass|less|styl)$/))
209
+ return 'stylesheet';
210
+ if (ext.match(/\.(vue)$/))
211
+ return 'vue';
212
+ // Data formats
213
+ if (ext.match(/\.(json)$/))
214
+ return 'json';
215
+ if (ext.match(/\.(xml)$/))
216
+ return 'xml';
217
+ if (ext.match(/\.(yaml|yml)$/))
218
+ return 'yaml';
219
+ if (ext.match(/\.(toml)$/))
220
+ return 'toml';
221
+ if (ext.match(/\.(ini)$/))
222
+ return 'ini';
223
+ if (ext.match(/\.(csv)$/))
224
+ return 'csv';
225
+ // Documentation
226
+ if (ext.match(/\.(md|markdown)$/))
227
+ return 'markdown';
228
+ if (ext.match(/\.(txt)$/))
229
+ return 'text';
230
+ if (ext.match(/\.(rst)$/))
231
+ return 'restructuredtext';
232
+ // Media
233
+ if (ext.match(/\.(png|jpg|jpeg|gif|svg|webp|bmp|ico)$/))
234
+ return 'image';
235
+ if (ext.match(/\.(mp4|avi|mov|wmv|flv|webm)$/))
236
+ return 'video';
237
+ if (ext.match(/\.(mp3|wav|flac|aac|ogg)$/))
238
+ return 'audio';
239
+ // Archives
240
+ if (ext.match(/\.(zip|tar|gz|bz2|xz|7z|rar)$/))
241
+ return 'archive';
242
+ // Configuration
243
+ if (fileName.includes('config') || fileName.includes('setup'))
244
+ return 'configuration';
245
+ return 'unknown';
246
+ }
247
+ // === File Watching ===
248
+ startWatching(onFileChange) {
249
+ try {
250
+ // Watch the current working directory recursively
251
+ const watcher = watch(this.CWD, { recursive: true }, (eventType, filename) => {
252
+ if (filename && !this.shouldIgnoreAnything(basename(filename), join(this.CWD, filename))) {
253
+ onFileChange?.(eventType, filename);
254
+ }
255
+ });
256
+ this.watchers.push(watcher);
257
+ if (this.verbose) {
258
+ console.log('📁 File watcher started');
259
+ }
260
+ }
261
+ catch (error) {
262
+ if (this.verbose) {
263
+ console.error('Failed to start file watcher:', error.message);
264
+ }
265
+ }
266
+ }
267
+ stopWatching() {
268
+ this.watchers.forEach(watcher => {
269
+ try {
270
+ watcher.close();
271
+ }
272
+ catch (error) {
273
+ if (this.verbose) {
274
+ console.error('Failed to close watcher:', error.message);
275
+ }
276
+ }
277
+ });
278
+ this.watchers = [];
279
+ if (this.verbose) {
280
+ console.log('📁 File watchers stopped');
281
+ }
282
+ }
283
+ // === Utilities ===
284
+ setIgnorePatterns(patterns) {
285
+ this.ignorePatterns = patterns;
286
+ }
287
+ relativePath(filePath) {
288
+ return relative(this.CWD, filePath);
289
+ }
290
+ fullPath(relativePath) {
291
+ return join(this.CWD, relativePath);
292
+ }
293
+ isValidPath(filePath) {
294
+ try {
295
+ return existsSync(filePath);
296
+ }
297
+ catch (error) {
298
+ return false;
299
+ }
300
+ }
301
+ // === Cleanup ===
302
+ destroy() {
303
+ this.stopWatching();
304
+ }
305
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Function-Level Semantic Chunker
3
+ * Extracts individual functions/methods/components as discrete chunks
4
+ * with intelligent context inclusion
5
+ */
6
+ import { readFileSync, existsSync } from 'fs';
7
+ import { extname, join } from 'path';
8
+ import { glob } from 'glob';
9
+ export default class FunctionLevelChunker {
10
+ options;
11
+ constructor(options = {}) {
12
+ this.options = {
13
+ minChunkSize: 100,
14
+ maxChunkSize: 10000,
15
+ includeImports: true,
16
+ includeTypes: true,
17
+ ...options
18
+ };
19
+ }
20
+ /**
21
+ * Main entry point - extract function chunks from project
22
+ */
23
+ async extractFunctionChunks(projectPath, patterns = ['**/*.{js,jsx,ts,tsx,mjs}']) {
24
+ console.log('🔍 Starting function-level semantic chunking...');
25
+ const files = await this.findFiles(projectPath, patterns);
26
+ console.log(`📁 Found ${files.length} files to scan for functions`);
27
+ const allFunctions = [];
28
+ for (const filePath of files) {
29
+ try {
30
+ const fileFunctions = this.extractFunctionsFromFile(filePath, projectPath);
31
+ allFunctions.push(...fileFunctions);
32
+ }
33
+ catch (error) {
34
+ console.warn(`Failed to extract from ${filePath}: ${error.message}`);
35
+ }
36
+ }
37
+ console.log(`📦 Found ${allFunctions.length} functions across project`);
38
+ const chunks = this.createFunctionChunks(allFunctions);
39
+ console.log(`🧩 Created ${chunks.length} semantic function chunks`);
40
+ return chunks;
41
+ }
42
+ /**
43
+ * Find files matching patterns
44
+ */
45
+ async findFiles(projectPath, patterns) {
46
+ const files = [];
47
+ for (const pattern of patterns) {
48
+ const matches = await glob(pattern, {
49
+ cwd: projectPath,
50
+ ignore: ['node_modules/**', 'dist/**', '.git/**', '*.test.*', '*.spec.*']
51
+ });
52
+ files.push(...matches);
53
+ }
54
+ return [...new Set(files)];
55
+ }
56
+ /**
57
+ * Extract functions from a single file
58
+ */
59
+ extractFunctionsFromFile(relativePath, projectPath) {
60
+ const fullPath = join(projectPath, relativePath);
61
+ if (!existsSync(fullPath))
62
+ return [];
63
+ const content = readFileSync(fullPath, 'utf8');
64
+ const lines = content.split('\n');
65
+ const ext = extname(relativePath);
66
+ // For now, use regex-based extraction as primary
67
+ // In a full implementation, we'd use treesitter here
68
+ return this.extractWithRegex(content, lines, relativePath);
69
+ }
70
+ /**
71
+ * Simple regex-based extraction for functions and methods
72
+ */
73
+ extractWithRegex(content, lines, filePath) {
74
+ const functions = [];
75
+ // Patterns for common function declarations
76
+ const patterns = [
77
+ // function name() {
78
+ /function\s+([a-zA-Z0-9_]+)\s*\(/g,
79
+ // const name = () => {
80
+ /(?:const|let|var)\s+([a-zA-Z0-9_]+)\s*=\s*(?:async\s*)?\([^)]*\)\s*=>/g,
81
+ // name(params) { (inside class or object)
82
+ /^\s*([a-zA-Z0-9_]+)\s*\([^)]*\)\s*\{/gm
83
+ ];
84
+ for (const pattern of patterns) {
85
+ let match;
86
+ while ((match = pattern.exec(content)) !== null) {
87
+ const name = match[1];
88
+ const startIndex = match.index;
89
+ // Find line number
90
+ const beforeMatch = content.substring(0, startIndex);
91
+ const startLine = beforeMatch.split('\n').length;
92
+ // Extract function body (simple brace matching)
93
+ const body = this.extractFunctionBody(content, startIndex, lines, startLine);
94
+ if (body && body.length > this.options.minChunkSize) {
95
+ functions.push({
96
+ name,
97
+ code: body,
98
+ filePath,
99
+ startLine,
100
+ type: 'function'
101
+ });
102
+ }
103
+ }
104
+ }
105
+ return functions;
106
+ }
107
+ /**
108
+ * Extract function body using brace matching
109
+ */
110
+ extractFunctionBody(content, startIndex, lines, startLine) {
111
+ const openingBraceIndex = content.indexOf('{', startIndex);
112
+ if (openingBraceIndex === -1)
113
+ return null;
114
+ let braceCount = 1;
115
+ let i = openingBraceIndex + 1;
116
+ while (braceCount > 0 && i < content.length) {
117
+ if (content[i] === '{')
118
+ braceCount++;
119
+ if (content[i] === '}')
120
+ braceCount--;
121
+ i++;
122
+ }
123
+ if (braceCount === 0) {
124
+ return content.substring(startIndex, i);
125
+ }
126
+ return null;
127
+ }
128
+ /**
129
+ * Create semantic chunks from extracted functions
130
+ */
131
+ createFunctionChunks(functions) {
132
+ return functions.map(func => ({
133
+ id: `${func.filePath}:${func.name}:${func.startLine}`,
134
+ name: func.name,
135
+ filePath: func.filePath,
136
+ type: 'function',
137
+ subtype: func.type || 'unknown',
138
+ code: func.code,
139
+ startLine: func.startLine,
140
+ purpose: 'Implementation logic', // Default purpose
141
+ complexity: { score: 0, level: 'low' } // Placeholder
142
+ }));
143
+ }
144
+ }