cntx-ui 2.0.13 → 2.0.15

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 (44) hide show
  1. package/bin/cntx-ui.js +137 -55
  2. package/lib/agent-runtime.js +1480 -0
  3. package/lib/agent-tools.js +368 -0
  4. package/lib/api-router.js +978 -0
  5. package/lib/bundle-manager.js +471 -0
  6. package/lib/configuration-manager.js +725 -0
  7. package/lib/file-system-manager.js +472 -0
  8. package/lib/heuristics-manager.js +425 -0
  9. package/lib/mcp-server.js +1054 -1
  10. package/lib/semantic-splitter.js +7 -14
  11. package/lib/simple-vector-store.js +329 -0
  12. package/lib/websocket-manager.js +470 -0
  13. package/package.json +10 -3
  14. package/server.js +662 -1933
  15. package/templates/activities/README.md +67 -0
  16. package/templates/activities/activities/create-project-bundles/README.md +83 -0
  17. package/templates/activities/activities/create-project-bundles/notes.md +102 -0
  18. package/templates/activities/activities/create-project-bundles/progress.md +63 -0
  19. package/templates/activities/activities/create-project-bundles/tasks.md +39 -0
  20. package/templates/activities/activities.json +219 -0
  21. package/templates/activities/lib/.markdownlint.jsonc +18 -0
  22. package/templates/activities/lib/create-activity.mdc +63 -0
  23. package/templates/activities/lib/generate-tasks.mdc +64 -0
  24. package/templates/activities/lib/process-task-list.mdc +52 -0
  25. package/templates/agent-config.yaml +78 -0
  26. package/templates/agent-instructions.md +218 -0
  27. package/templates/agent-rules/capabilities/activities-system.md +147 -0
  28. package/templates/agent-rules/capabilities/bundle-system.md +131 -0
  29. package/templates/agent-rules/capabilities/vector-search.md +135 -0
  30. package/templates/agent-rules/core/codebase-navigation.md +91 -0
  31. package/templates/agent-rules/core/performance-hierarchy.md +48 -0
  32. package/templates/agent-rules/core/response-formatting.md +120 -0
  33. package/templates/agent-rules/project-specific/architecture.md +145 -0
  34. package/templates/config.json +76 -0
  35. package/templates/hidden-files.json +14 -0
  36. package/web/dist/assets/heuristics-manager-browser-DfonOP5I.js +1 -0
  37. package/web/dist/assets/index-dF3qg-y_.js +2486 -0
  38. package/web/dist/assets/index-h5FGSg_P.css +1 -0
  39. package/web/dist/cntx-ui.svg +18 -0
  40. package/web/dist/index.html +25 -8
  41. package/lib/semantic-integration.js +0 -441
  42. package/web/dist/assets/index-Ci1Q-YrQ.js +0 -611
  43. package/web/dist/assets/index-IUp4q_fr.css +0 -1
  44. package/web/dist/vite.svg +0 -21
@@ -0,0 +1,471 @@
1
+ /**
2
+ * Bundle Manager for cntx-ui
3
+ * Handles bundle generation, XML creation, and file organization
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.verbose = verbose;
14
+ this._isScanning = false;
15
+ }
16
+
17
+ // === Bundle Generation ===
18
+
19
+ async generateAllBundles() {
20
+ this._isScanning = true;
21
+
22
+ try {
23
+ const bundles = this.configManager.getBundles();
24
+
25
+ const totalBundles = bundles.size;
26
+ let processedBundles = 0;
27
+
28
+ for (const [name] of bundles) {
29
+ if (this.verbose) {
30
+ processedBundles++;
31
+ const progress = `📊 Generating bundles: ${processedBundles}/${totalBundles} (${name})`;
32
+ process.stdout.write(`\r${progress.padEnd(80)}`); // Pad to clear previous longer messages
33
+ }
34
+ await this.generateBundle(name);
35
+ }
36
+
37
+ if (this.verbose) {
38
+ process.stdout.write('\r' + ''.padEnd(80) + '\r'); // Clear the line
39
+ }
40
+ this.configManager.saveBundleStates();
41
+ } finally {
42
+ this._isScanning = false;
43
+ }
44
+ }
45
+
46
+ async generateBundle(name) {
47
+ const bundles = this.configManager.getBundles();
48
+ const bundle = bundles.get(name);
49
+
50
+ if (!bundle) {
51
+ throw new Error(`Bundle "${name}" not found`);
52
+ }
53
+
54
+ // Only regenerate from patterns if no manual files exist
55
+ // This preserves manual file management while allowing pattern-based initialization
56
+ if (!bundle.files || bundle.files.length === 0) {
57
+ // Get all files matching bundle patterns
58
+ const allFiles = this.fileSystemManager.getAllFiles();
59
+ const bundleFiles = allFiles.filter(file =>
60
+ bundle.patterns.some(pattern =>
61
+ this.fileSystemManager.matchesPattern(file, pattern)
62
+ )
63
+ );
64
+
65
+ // Convert to relative paths for storage (portable across environments)
66
+ bundle.files = bundleFiles.map(file => this.fileSystemManager.relativePath(file));
67
+ }
68
+ // If bundle.files already exists, preserve manual file management
69
+
70
+ // Ensure bundle.files always contains relative paths for storage consistency
71
+ bundle.files = bundle.files.map(file =>
72
+ file.startsWith('/') ? this.fileSystemManager.relativePath(file) : file
73
+ );
74
+
75
+ // Convert relative paths to absolute for XML generation
76
+ const absoluteFiles = bundle.files.map(file =>
77
+ this.fileSystemManager.absolutePath(file)
78
+ );
79
+ bundle.content = this.generateBundleXML(name, absoluteFiles);
80
+ bundle.size = Buffer.byteLength(bundle.content, 'utf8');
81
+ bundle.generated = new Date().toISOString();
82
+ bundle.changed = false;
83
+
84
+ return bundle;
85
+ }
86
+
87
+ generateBundleXML(bundleName, files) {
88
+ const projectInfo = this.getProjectInfo();
89
+ const categorizedFiles = this.categorizeFiles(files);
90
+ const entryPoints = this.identifyEntryPoints(files);
91
+ const bundlePurpose = this.getBundlePurpose(bundleName);
92
+
93
+ let xml = `<?xml version="1.0" encoding="UTF-8"?>
94
+ <codebase>
95
+ <project_info>
96
+ <name>${this.escapeXml(projectInfo.name)}</name>
97
+ <bundle_name>${this.escapeXml(bundleName)}</bundle_name>
98
+ <bundle_purpose>${this.escapeXml(bundlePurpose)}</bundle_purpose>
99
+ <total_files>${files.length}</total_files>
100
+ <generated_at>${new Date().toISOString()}</generated_at>
101
+ </project_info>
102
+
103
+ <overview>
104
+ <description>
105
+ This bundle contains ${files.length} files organized into different categories.
106
+ ${bundlePurpose}
107
+ </description>
108
+ <entry_points>
109
+ ${entryPoints.map(file => ` <file>${this.escapeXml(file)}</file>`).join('\n')}
110
+ </entry_points>
111
+ </overview>
112
+
113
+ <file_structure>`;
114
+
115
+ // Add categorized files
116
+ Object.entries(categorizedFiles).forEach(([category, categoryFiles]) => {
117
+ if (categoryFiles.length > 0) {
118
+ xml += `
119
+ <group name="${category}" description="${this.getTypeDescription(category)}">`;
120
+
121
+ categoryFiles.forEach(file => {
122
+ xml += `
123
+ ${this.generateFileXML(file)}`;
124
+ });
125
+
126
+ xml += `
127
+ </group>`;
128
+ }
129
+ });
130
+
131
+ xml += `
132
+ </file_structure>
133
+ </codebase>`;
134
+
135
+ return xml;
136
+ }
137
+
138
+ // === File Organization ===
139
+
140
+ categorizeFiles(files) {
141
+ const categories = {
142
+ 'entry_points': [],
143
+ 'components': [],
144
+ 'hooks': [],
145
+ 'utilities': [],
146
+ 'types': [],
147
+ 'styles': [],
148
+ 'tests': [],
149
+ 'configuration': [],
150
+ 'documentation': [],
151
+ 'other': []
152
+ };
153
+
154
+ files.forEach(file => {
155
+ const ext = extname(file).toLowerCase();
156
+ const fileName = basename(file).toLowerCase();
157
+ const filePath = file.toLowerCase();
158
+
159
+ // Entry points
160
+ if (fileName.match(/^(main|index|app)\.(js|jsx|ts|tsx)$/)) {
161
+ categories.entry_points.push(file);
162
+ }
163
+ // Components
164
+ else if (ext.match(/\.(jsx|tsx|vue)$/) || filePath.includes('/components/')) {
165
+ categories.components.push(file);
166
+ }
167
+ // Hooks
168
+ else if (filePath.includes('/hooks/') || fileName.startsWith('use') && ext.match(/\.(js|ts)$/)) {
169
+ categories.hooks.push(file);
170
+ }
171
+ // Utilities
172
+ else if (filePath.includes('/utils/') || filePath.includes('/helpers/') || filePath.includes('/lib/')) {
173
+ categories.utilities.push(file);
174
+ }
175
+ // Types
176
+ else if (fileName.includes('.d.ts') || filePath.includes('/types/') || fileName.includes('types')) {
177
+ categories.types.push(file);
178
+ }
179
+ // Styles
180
+ else if (ext.match(/\.(css|scss|sass|less|styl)$/)) {
181
+ categories.styles.push(file);
182
+ }
183
+ // Tests
184
+ else if (fileName.includes('.test.') || fileName.includes('.spec.') || filePath.includes('/test/') || filePath.includes('/__tests__/')) {
185
+ categories.tests.push(file);
186
+ }
187
+ // Configuration
188
+ else if (ext.match(/\.(json|yaml|yml|toml|ini)$/) || fileName.includes('config')) {
189
+ categories.configuration.push(file);
190
+ }
191
+ // Documentation
192
+ else if (ext.match(/\.(md|txt|rst)$/)) {
193
+ categories.documentation.push(file);
194
+ }
195
+ // Other
196
+ else {
197
+ categories.other.push(file);
198
+ }
199
+ });
200
+
201
+ return categories;
202
+ }
203
+
204
+ identifyEntryPoints(files) {
205
+ const entryPoints = [];
206
+ const entryPatterns = [
207
+ /^(main|index|app)\.(js|jsx|ts|tsx)$/i,
208
+ /^server\.(js|ts)$/i,
209
+ /^app\.(js|jsx|ts|tsx)$/i
210
+ ];
211
+
212
+ files.forEach(file => {
213
+ const fileName = basename(file);
214
+ if (entryPatterns.some(pattern => pattern.test(fileName))) {
215
+ entryPoints.push(file);
216
+ }
217
+ });
218
+
219
+ return entryPoints;
220
+ }
221
+
222
+ getBundlePurpose(bundleName) {
223
+ const purposes = {
224
+ 'master': 'Complete codebase overview with all project files',
225
+ 'frontend': 'User interface components, styling, and client-side logic',
226
+ 'backend': 'Server-side logic, API endpoints, and business logic',
227
+ 'components': 'Reusable UI components and their associated styles',
228
+ 'utilities': 'Shared utility functions and helper modules',
229
+ 'configuration': 'Project configuration files and environment setup',
230
+ 'tests': 'Test files and testing utilities',
231
+ 'documentation': 'Project documentation and README files',
232
+ 'types': 'TypeScript type definitions and interfaces',
233
+ 'styles': 'CSS, SCSS, and other styling files'
234
+ };
235
+
236
+ return purposes[bundleName] || `Files matching the ${bundleName} bundle patterns`;
237
+ }
238
+
239
+ getTypeDescription(type) {
240
+ const descriptions = {
241
+ 'entry_points': 'Main application entry points and bootstrap files',
242
+ 'components': 'Reusable UI components and their implementations',
243
+ 'hooks': 'Custom React hooks and composable functions',
244
+ 'utilities': 'Shared utility functions and helper modules',
245
+ 'types': 'TypeScript type definitions and interfaces',
246
+ 'styles': 'CSS, SCSS, and other styling files',
247
+ 'tests': 'Test files and testing utilities',
248
+ 'configuration': 'Configuration files and environment setup',
249
+ 'documentation': 'Documentation, README files, and guides',
250
+ 'other': 'Other project files not fitting specific categories'
251
+ };
252
+
253
+ return descriptions[type] || 'Project files';
254
+ }
255
+
256
+ // === File Processing ===
257
+
258
+ generateFileXML(file) {
259
+ try {
260
+ const stats = this.getFileStats(file);
261
+ const content = readFileSync(file, 'utf8');
262
+ const role = this.getFileRole(file);
263
+ const relativePath = relative(this.configManager.CWD, file);
264
+
265
+ return `<file path="${this.escapeXml(relativePath)}" role="${this.escapeXml(role)}" size="${stats.size}" modified="${stats.mtime.toISOString()}">
266
+ <![CDATA[${content}]]>
267
+ </file>`;
268
+ } catch (error) {
269
+ const relativePath = relative(this.configManager.CWD, file);
270
+ return `<file path="${this.escapeXml(relativePath)}" role="error" error="${this.escapeXml(error.message)}">
271
+ <!-- File could not be read -->
272
+ </file>`;
273
+ }
274
+ }
275
+
276
+ getFileRole(file) {
277
+ const fileName = basename(file).toLowerCase();
278
+ const filePath = file.toLowerCase();
279
+ const ext = extname(file).toLowerCase();
280
+
281
+ if (fileName.match(/^(main|index|app)\.(js|jsx|ts|tsx)$/)) {
282
+ return 'entry_point';
283
+ }
284
+ if (fileName.includes('config') || fileName.includes('setup')) {
285
+ return 'configuration';
286
+ }
287
+ if (fileName.includes('readme') || ext === '.md') {
288
+ return 'documentation';
289
+ }
290
+
291
+ return 'implementation';
292
+ }
293
+
294
+ getFileStats(filePath) {
295
+ try {
296
+ const stats = statSync(filePath);
297
+ return {
298
+ size: stats.size,
299
+ mtime: stats.mtime,
300
+ ctime: stats.ctime
301
+ };
302
+ } catch (error) {
303
+ return {
304
+ size: 0,
305
+ mtime: new Date(0),
306
+ ctime: new Date(0)
307
+ };
308
+ }
309
+ }
310
+
311
+ escapeXml(text) {
312
+ if (typeof text !== 'string') {
313
+ text = String(text);
314
+ }
315
+ return text
316
+ .replace(/&/g, '&amp;')
317
+ .replace(/</g, '&lt;')
318
+ .replace(/>/g, '&gt;')
319
+ .replace(/"/g, '&quot;')
320
+ .replace(/'/g, '&apos;');
321
+ }
322
+
323
+ // === Bundle State Management ===
324
+
325
+ markBundlesChanged(filename) {
326
+ const bundles = this.configManager.getBundles();
327
+
328
+ bundles.forEach((bundle, name) => {
329
+ const matchesBundle = bundle.patterns.some(pattern =>
330
+ this.fileSystemManager.matchesPattern(filename, pattern)
331
+ );
332
+
333
+ if (matchesBundle) {
334
+ bundle.changed = true;
335
+ }
336
+ });
337
+ }
338
+
339
+ getFileListWithVisibility(bundleName) {
340
+ try {
341
+ const allFiles = this.fileSystemManager.getAllFiles();
342
+ const bundle = this.configManager.getBundles().get(bundleName);
343
+
344
+ if (!bundle) {
345
+ return [];
346
+ }
347
+
348
+ return allFiles.map(file => {
349
+ const matchesBundle = bundle.patterns.some(pattern =>
350
+ this.fileSystemManager.matchesPattern(file, pattern)
351
+ );
352
+
353
+ const isHidden = this.configManager.isFileHidden(file, bundleName);
354
+ const relativePath = relative(this.configManager.CWD, file);
355
+
356
+ return {
357
+ path: relativePath,
358
+ fullPath: file,
359
+ included: matchesBundle && !isHidden,
360
+ hidden: isHidden,
361
+ matchesPattern: matchesBundle
362
+ };
363
+ });
364
+ } catch (error) {
365
+ console.error('Failed to get file list with visibility:', error.message);
366
+ return [];
367
+ }
368
+ }
369
+
370
+ // === Project Information ===
371
+
372
+ getProjectInfo() {
373
+ try {
374
+ const packagePath = this.configManager.CWD + '/package.json';
375
+ const pkg = JSON.parse(readFileSync(packagePath, 'utf8'));
376
+ return {
377
+ name: pkg.name || 'Unknown Project',
378
+ version: pkg.version || '1.0.0',
379
+ description: pkg.description || 'No description available'
380
+ };
381
+ } catch (error) {
382
+ return {
383
+ name: 'Unknown Project',
384
+ version: '1.0.0',
385
+ description: 'No description available'
386
+ };
387
+ }
388
+ }
389
+
390
+ // === Bundle Operations ===
391
+
392
+ async regenerateBundle(bundleName) {
393
+ // Notify WebSocket clients that sync has started
394
+ if (this.webSocketManager) {
395
+ this.webSocketManager.onBundleSyncStarted(bundleName);
396
+ }
397
+
398
+ try {
399
+ const bundle = await this.generateBundle(bundleName);
400
+ this.configManager.saveBundleStates();
401
+
402
+ // Notify WebSocket clients that sync completed successfully
403
+ if (this.webSocketManager) {
404
+ this.webSocketManager.onBundleSyncCompleted(bundleName);
405
+ }
406
+
407
+ return bundle;
408
+ } catch (error) {
409
+ // Notify WebSocket clients that sync failed
410
+ if (this.webSocketManager) {
411
+ this.webSocketManager.onBundleSyncFailed(bundleName, error);
412
+ }
413
+ throw error;
414
+ }
415
+ }
416
+
417
+ async regenerateChangedBundles() {
418
+ const bundles = this.configManager.getBundles();
419
+ const changedBundles = [];
420
+
421
+ for (const [name, bundle] of bundles) {
422
+ if (bundle.changed) {
423
+ await this.generateBundle(name);
424
+ changedBundles.push(name);
425
+ }
426
+ }
427
+
428
+ if (changedBundles.length > 0) {
429
+ this.configManager.saveBundleStates();
430
+ }
431
+
432
+ return changedBundles;
433
+ }
434
+
435
+ getBundleContent(bundleName) {
436
+ const bundle = this.configManager.getBundles().get(bundleName);
437
+ return bundle ? bundle.content : null;
438
+ }
439
+
440
+ getBundleInfo(bundleName) {
441
+ const bundle = this.configManager.getBundles().get(bundleName);
442
+ if (!bundle) return null;
443
+
444
+ return {
445
+ name: bundleName,
446
+ fileCount: bundle.files.length,
447
+ size: bundle.size,
448
+ generated: bundle.generated,
449
+ changed: bundle.changed,
450
+ patterns: bundle.patterns
451
+ };
452
+ }
453
+
454
+ getAllBundleInfo() {
455
+ const bundles = this.configManager.getBundles();
456
+ return Array.from(bundles.entries()).map(([name, bundle]) => ({
457
+ name,
458
+ fileCount: bundle.files.length,
459
+ size: bundle.size,
460
+ generated: bundle.generated,
461
+ changed: bundle.changed,
462
+ patterns: bundle.patterns
463
+ }));
464
+ }
465
+
466
+ // === Getters ===
467
+
468
+ get isScanning() {
469
+ return this._isScanning;
470
+ }
471
+ }