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
@@ -1,326 +0,0 @@
1
- /**
2
- * Bundle Manager for cntx-ui
3
- * Handles Smart Dynamic Bundles and traditional XML generation
4
- */
5
-
6
- import { readFileSync, statSync } from 'fs';
7
- import { relative, extname, basename, dirname } from 'path';
8
-
9
- export default class BundleManager {
10
- constructor(configManager, fileSystemManager, verbose = false) {
11
- this.configManager = configManager;
12
- this.fileSystemManager = fileSystemManager;
13
- this.db = configManager.dbManager;
14
- this.verbose = verbose;
15
- this._isScanning = false;
16
- }
17
-
18
- /**
19
- * Get all bundle information, including Smart Dynamic Bundles
20
- */
21
- getAllBundleInfo() {
22
- if (this.verbose) console.log('📦 Getting all bundle info...');
23
- const manualBundles = Array.from(this.configManager.getBundles().entries()).map(([name, bundle]) => ({
24
- name,
25
- fileCount: bundle.files?.length || 0,
26
- size: bundle.size || 0,
27
- generated: bundle.generated,
28
- changed: bundle.changed,
29
- patterns: bundle.patterns,
30
- type: 'manual'
31
- }));
32
-
33
- if (this.verbose) console.log(`📦 Found ${manualBundles.length} manual bundles`);
34
-
35
- const smartBundles = this.generateSmartBundleDefinitions();
36
- if (this.verbose) console.log(`📦 Found ${smartBundles.length} smart bundle definitions`);
37
-
38
- // Filter out smart bundles that have no files
39
- const activeSmartBundles = smartBundles.map(b => {
40
- const files = this.resolveSmartBundle(b.name);
41
- return {
42
- ...b,
43
- fileCount: files.length,
44
- files
45
- };
46
- }).filter(b => b.fileCount > 0);
47
-
48
- if (this.verbose) console.log(`📦 Active smart bundles: ${activeSmartBundles.length}`);
49
-
50
- return [...manualBundles, ...activeSmartBundles];
51
- }
52
-
53
- /**
54
- * Generate Smart Bundle definitions from indexed semantic data.
55
- * Uses business domain, directory structure, and technical patterns
56
- * instead of raw AST node types.
57
- */
58
- generateSmartBundleDefinitions() {
59
- const smartBundles = [];
60
- const MIN_CHUNKS = 3; // Skip bundles with fewer than this many chunks
61
-
62
- try {
63
- // 1. Group by business domain (from metadata JSON)
64
- const allRows = this.db.db.prepare('SELECT metadata FROM semantic_chunks WHERE metadata IS NOT NULL').all();
65
- const domainCounts = new Map();
66
- for (const row of allRows) {
67
- try {
68
- const meta = JSON.parse(row.metadata);
69
- for (const domain of (meta.businessDomain || [])) {
70
- domainCounts.set(domain, (domainCounts.get(domain) || 0) + 1);
71
- }
72
- } catch { /* skip malformed */ }
73
- }
74
- for (const [domain, count] of domainCounts) {
75
- if (count < MIN_CHUNKS) continue;
76
- smartBundles.push({
77
- name: `smart:${domain}`,
78
- purpose: domain,
79
- fileCount: count,
80
- type: 'smart',
81
- description: `${count} chunks in the ${domain} domain`
82
- });
83
- }
84
-
85
- // 2. Group by directory structure (components, hooks, pages, api, etc.)
86
- const dirRows = this.db.db.prepare(`
87
- SELECT
88
- CASE
89
- WHEN file_path LIKE '%/hooks/%' THEN 'hooks'
90
- WHEN file_path LIKE '%/components/%' THEN 'components'
91
- WHEN file_path LIKE '%/api/%' OR file_path LIKE '%/services/%' THEN 'api-services'
92
- WHEN file_path LIKE '%/pages/%' OR file_path LIKE '%/routes/%' THEN 'pages'
93
- WHEN file_path LIKE '%/stores/%' OR file_path LIKE '%/store/%' THEN 'state'
94
- WHEN file_path LIKE '%/lib/%' OR file_path LIKE '%/utils/%' THEN 'lib-utils'
95
- WHEN file_path LIKE '%/types/%' THEN 'types'
96
- ELSE NULL
97
- END as dir_group,
98
- COUNT(DISTINCT file_path) as file_cnt
99
- FROM semantic_chunks
100
- GROUP BY dir_group
101
- HAVING dir_group IS NOT NULL
102
- `).all();
103
- for (const row of dirRows) {
104
- if (row.file_cnt < 2) continue;
105
- smartBundles.push({
106
- name: `smart:dir-${row.dir_group}`,
107
- purpose: row.dir_group,
108
- fileCount: row.file_cnt,
109
- type: 'smart',
110
- description: `${row.file_cnt} files in ${row.dir_group} directories`
111
- });
112
- }
113
-
114
- // 3. Group by technical pattern (react-hooks, async-io, event-driven)
115
- const patternCounts = new Map();
116
- for (const row of allRows) {
117
- try {
118
- const meta = JSON.parse(row.metadata);
119
- for (const pattern of (meta.technicalPatterns || [])) {
120
- if (pattern === 'public-api') continue; // Too generic
121
- patternCounts.set(pattern, (patternCounts.get(pattern) || 0) + 1);
122
- }
123
- } catch { /* skip */ }
124
- }
125
- for (const [pattern, count] of patternCounts) {
126
- if (count < MIN_CHUNKS) continue;
127
- smartBundles.push({
128
- name: `smart:pattern-${pattern}`,
129
- purpose: pattern,
130
- fileCount: count,
131
- type: 'smart',
132
- description: `${count} chunks using ${pattern} patterns`
133
- });
134
- }
135
- } catch (e) {
136
- if (this.verbose) console.warn('Smart bundle discovery failed:', e.message);
137
- }
138
- return smartBundles;
139
- }
140
-
141
- /**
142
- * Resolve files for a bundle (Manual or Smart)
143
- */
144
- async resolveBundleFiles(bundleName) {
145
- if (bundleName.startsWith('smart:')) {
146
- return this.resolveSmartBundle(bundleName);
147
- }
148
-
149
- const bundle = this.configManager.getBundles().get(bundleName);
150
- if (!bundle) return [];
151
-
152
- const allFiles = this.fileSystemManager.getAllFiles();
153
- return allFiles.filter(file =>
154
- bundle.patterns.some(pattern => this.fileSystemManager.matchesPattern(file, pattern))
155
- ).map(f => this.fileSystemManager.relativePath(f));
156
- }
157
-
158
- /**
159
- * Resolve a Smart Bundle query against SQLite
160
- */
161
- resolveSmartBundle(bundleName) {
162
- const query = bundleName.replace('smart:', '');
163
- let rows = [];
164
-
165
- if (query.startsWith('dir-')) {
166
- // Directory-based bundle
167
- const dirGroup = query.replace('dir-', '');
168
- const dirPatterns = {
169
- 'hooks': '%/hooks/%',
170
- 'components': '%/components/%',
171
- 'api-services': null, // handled below
172
- 'pages': null,
173
- 'state': null,
174
- 'lib-utils': null,
175
- 'types': '%/types/%'
176
- };
177
- if (dirGroup === 'api-services') {
178
- rows = this.db.db.prepare("SELECT DISTINCT file_path FROM semantic_chunks WHERE file_path LIKE '%/api/%' OR file_path LIKE '%/services/%'").all();
179
- } else if (dirGroup === 'pages') {
180
- rows = this.db.db.prepare("SELECT DISTINCT file_path FROM semantic_chunks WHERE file_path LIKE '%/pages/%' OR file_path LIKE '%/routes/%'").all();
181
- } else if (dirGroup === 'state') {
182
- rows = this.db.db.prepare("SELECT DISTINCT file_path FROM semantic_chunks WHERE file_path LIKE '%/stores/%' OR file_path LIKE '%/store/%'").all();
183
- } else if (dirGroup === 'lib-utils') {
184
- rows = this.db.db.prepare("SELECT DISTINCT file_path FROM semantic_chunks WHERE file_path LIKE '%/lib/%' OR file_path LIKE '%/utils/%'").all();
185
- } else if (dirPatterns[dirGroup]) {
186
- rows = this.db.db.prepare('SELECT DISTINCT file_path FROM semantic_chunks WHERE file_path LIKE ?').all(dirPatterns[dirGroup]);
187
- }
188
- } else if (query.startsWith('pattern-')) {
189
- // Technical pattern bundle — search metadata JSON
190
- const pattern = query.replace('pattern-', '');
191
- const allRows = this.db.db.prepare('SELECT DISTINCT file_path, metadata FROM semantic_chunks WHERE metadata IS NOT NULL').all();
192
- const files = new Set();
193
- for (const row of allRows) {
194
- try {
195
- const meta = JSON.parse(row.metadata);
196
- if ((meta.technicalPatterns || []).includes(pattern)) {
197
- files.add(row.file_path);
198
- }
199
- } catch { /* skip */ }
200
- }
201
- return Array.from(files);
202
- } else {
203
- // Business domain bundle — search metadata JSON
204
- const allRows = this.db.db.prepare('SELECT DISTINCT file_path, metadata FROM semantic_chunks WHERE metadata IS NOT NULL').all();
205
- const files = new Set();
206
- for (const row of allRows) {
207
- try {
208
- const meta = JSON.parse(row.metadata);
209
- if ((meta.businessDomain || []).includes(query)) {
210
- files.add(row.file_path);
211
- }
212
- } catch { /* skip */ }
213
- }
214
- return Array.from(files);
215
- }
216
- return rows.map(r => r.file_path);
217
- }
218
-
219
- // === Bundle Generation ===
220
-
221
- async generateAllBundles() {
222
- this._isScanning = true;
223
- try {
224
- const bundles = this.configManager.getBundles();
225
- for (const [name] of bundles) {
226
- await this.regenerateBundle(name);
227
- }
228
- } finally {
229
- this._isScanning = false;
230
- }
231
- }
232
-
233
- async regenerateBundle(bundleName) {
234
- if (this.verbose) console.log(`🔄 Regenerating bundle: ${bundleName}`);
235
-
236
- const files = await this.resolveBundleFiles(bundleName);
237
- const content = await this.generateBundleXML(bundleName, files);
238
-
239
- const bundleData = {
240
- files,
241
- content,
242
- size: Buffer.byteLength(content, 'utf8'),
243
- generated: new Date().toISOString(),
244
- changed: false
245
- };
246
-
247
- if (!bundleName.startsWith('smart:')) {
248
- const existing = this.configManager.getBundles().get(bundleName);
249
- this.configManager.getBundles().set(bundleName, { ...existing, ...bundleData });
250
- this.configManager.saveBundleStates();
251
- }
252
-
253
- return bundleData;
254
- }
255
-
256
- async generateBundleXML(bundleName, relativeFiles) {
257
- const projectInfo = this.getProjectInfo();
258
- let xml = `<?xml version="1.0" encoding="UTF-8"?>\n<codebase>\n`;
259
- xml += ` <project_info>\n <name>${this.escapeXml(projectInfo.name)}</name>\n <bundle>${this.escapeXml(bundleName)}</bundle>\n </project_info>\n`;
260
-
261
- for (const relPath of relativeFiles) {
262
- xml += await this.generateFileXML(relPath);
263
- }
264
-
265
- xml += `</codebase>`;
266
- return xml;
267
- }
268
-
269
- async generateFileXML(relativePath) {
270
- try {
271
- const fullPath = this.fileSystemManager.fullPath(relativePath);
272
- const content = readFileSync(fullPath, 'utf8');
273
- const chunks = this.db.getChunksByFile(relativePath);
274
-
275
- let xml = ` <file path="${this.escapeXml(relativePath)}">\n`;
276
- if (chunks.length > 0) {
277
- xml += ` <semantic_context>\n`;
278
- chunks.forEach(c => {
279
- xml += ` <chunk name="${this.escapeXml(c.name)}" purpose="${this.escapeXml(c.purpose)}" complexity="${c.complexity?.score || 0}" />\n`;
280
- });
281
- xml += ` </semantic_context>\n`;
282
- }
283
- xml += ` <content><![CDATA[${content}]]></content>\n </file>\n`;
284
- return xml;
285
- } catch (e) {
286
- return ` <file path="${this.escapeXml(relativePath)}" error="${this.escapeXml(e.message)}" />\n`;
287
- }
288
- }
289
-
290
- getProjectInfo() {
291
- try {
292
- const pkg = JSON.parse(readFileSync(this.fileSystemManager.fullPath('package.json'), 'utf8'));
293
- return { name: pkg.name || 'Unknown', version: pkg.version || '1.0.0' };
294
- } catch {
295
- return { name: 'Unknown', version: '1.0.0' };
296
- }
297
- }
298
-
299
- escapeXml(text) {
300
- return String(text).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;');
301
- }
302
-
303
- getBundleContent(bundleName) {
304
- if (bundleName.startsWith('smart:')) {
305
- // For smart bundles, we generate on the fly if not cached
306
- return this.regenerateBundle(bundleName).then(data => data.content);
307
- }
308
- const bundle = this.configManager.getBundles().get(bundleName);
309
- return bundle ? bundle.content : null;
310
- }
311
-
312
- markBundlesChanged(filename) {
313
- this.configManager.getBundles().forEach((bundle, name) => {
314
- if (bundle.patterns?.some(p => this.fileSystemManager.matchesPattern(filename, p))) {
315
- bundle.changed = true;
316
- }
317
- });
318
- }
319
-
320
- getBundleInfo(bundleName) {
321
- if (bundleName.startsWith('smart:')) {
322
- return this.generateSmartBundleDefinitions().find(b => b.name === bundleName);
323
- }
324
- return this.configManager.getBundles().get(bundleName);
325
- }
326
- }