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
package/server.js CHANGED
@@ -1,17 +1,33 @@
1
- import { readFileSync, writeFileSync, existsSync, mkdirSync, watch, readdirSync, statSync } from 'fs';
2
- import { join, dirname, relative, extname, basename } from 'path';
1
+ /**
2
+ * Refactored cntx-ui Server
3
+ * Lean orchestration layer using modular architecture
4
+ */
5
+
3
6
  import { createServer } from 'http';
4
- import { WebSocketServer } from 'ws';
7
+ import { join, dirname } from 'path';
5
8
  import { fileURLToPath } from 'url';
6
- import path from 'path';
9
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync, cpSync } from 'fs';
10
+ import * as fs from 'fs';
11
+ import { homedir } from 'os';
12
+
13
+ // Import our modular components
14
+ import ConfigurationManager from './lib/configuration-manager.js';
15
+ import FileSystemManager from './lib/file-system-manager.js';
16
+ import BundleManager from './lib/bundle-manager.js';
17
+ import APIRouter from './lib/api-router.js';
18
+ import WebSocketManager from './lib/websocket-manager.js';
19
+
20
+ // Import existing lib modules
7
21
  import { startMCPTransport } from './lib/mcp-transport.js';
8
22
  import SemanticSplitter from './lib/semantic-splitter.js';
9
- import { homedir } from 'os';
23
+ import SimpleVectorStore from './lib/simple-vector-store.js';
24
+ import { MCPServer } from './lib/mcp-server.js';
10
25
 
11
26
  const __dirname = dirname(fileURLToPath(import.meta.url));
12
27
 
28
+ // Utility function for content types
13
29
  function getContentType(filePath) {
14
- const ext = path.extname(filePath).toLowerCase();
30
+ const ext = filePath.substring(filePath.lastIndexOf('.')).toLowerCase();
15
31
  const contentTypes = {
16
32
  '.html': 'text/html',
17
33
  '.js': 'application/javascript',
@@ -30,2134 +46,847 @@ export class CntxServer {
30
46
  constructor(cwd = process.cwd(), options = {}) {
31
47
  this.CWD = cwd;
32
48
  this.CNTX_DIR = join(cwd, '.cntx');
33
- this.isQuietMode = options.quiet || false;
34
- this.CONFIG_FILE = join(this.CNTX_DIR, 'config.json');
35
- this.BUNDLES_FILE = join(this.CNTX_DIR, 'bundles.json');
36
- this.HIDDEN_FILES_CONFIG = join(this.CNTX_DIR, 'hidden-files.json');
37
- this.IGNORE_FILE = join(cwd, '.cntxignore');
38
- this.CURSOR_RULES_FILE = join(cwd, '.cursorrules');
39
- this.CLAUDE_MD_FILE = join(cwd, 'CLAUDE.md');
40
-
41
- this.bundles = new Map();
42
- this.ignorePatterns = [];
43
- this.watchers = [];
44
- this.clients = new Set();
45
- this.isScanning = false;
46
- this.mcpServer = null;
49
+ this.verbose = options.verbose || false;
47
50
  this.mcpServerStarted = false;
51
+ this.mcpServer = null;
52
+ this.initMessages = []; // Track initialization messages
48
53
 
49
- this.hiddenFilesConfig = {
50
- globalHidden: [], // Files hidden across all bundles
51
- bundleSpecific: {}, // Files hidden per bundle: { bundleName: [filePaths] }
52
- userIgnorePatterns: [], // User-added ignore patterns
53
- disabledSystemPatterns: [] // System patterns the user disabled
54
- };
54
+ // Initialize modular components
55
+ this.configManager = new ConfigurationManager(cwd, { verbose: this.verbose });
56
+ this.fileSystemManager = new FileSystemManager(cwd, { verbose: this.verbose });
57
+ this.bundleManager = new BundleManager(this.configManager, this.fileSystemManager, this.verbose);
58
+ this.webSocketManager = new WebSocketManager(this.bundleManager, this.configManager, { verbose: this.verbose });
55
59
 
56
- // Semantic splitting (parallel to bundle system)
60
+ // Initialize semantic analysis components
57
61
  this.semanticSplitter = new SemanticSplitter({
58
62
  maxChunkSize: 2000,
59
63
  includeContext: true,
60
64
  groupRelated: true,
61
65
  minFunctionSize: 50
62
66
  });
63
- this.semanticCache = null;
64
- this.lastSemanticAnalysis = null;
65
- }
66
67
 
67
- init() {
68
- if (!existsSync(this.CNTX_DIR)) mkdirSync(this.CNTX_DIR, { recursive: true });
69
- this.loadConfig();
70
- this.loadHiddenFilesConfig();
71
- this.loadIgnorePatterns();
72
- this.loadBundleStates();
73
- this.startWatching();
74
- this.generateAllBundles();
75
- }
68
+ this.vectorStore = new SimpleVectorStore({
69
+ modelName: 'Xenova/all-MiniLM-L6-v2',
70
+ collectionName: 'code-chunks'
71
+ });
76
72
 
77
- loadConfig() {
78
- // Clear existing bundles to ensure deleted ones are removed
79
- this.bundles.clear();
80
-
81
- if (existsSync(this.CONFIG_FILE)) {
82
- const config = JSON.parse(readFileSync(this.CONFIG_FILE, 'utf8'));
83
- Object.entries(config.bundles || {}).forEach(([name, patterns]) => {
84
- this.bundles.set(name, {
85
- patterns: Array.isArray(patterns) ? patterns : [patterns],
86
- files: [],
87
- content: '',
88
- changed: false,
89
- lastGenerated: null,
90
- size: 0
91
- });
92
- });
93
- }
73
+ this.semanticCache = null;
74
+ this.lastSemanticAnalysis = null;
75
+ this.vectorStoreInitialized = false;
76
+
77
+ // Create semantic analysis manager object for API router
78
+ this.semanticAnalysisManager = {
79
+ getSemanticAnalysis: () => this.getSemanticAnalysis(),
80
+ refreshSemanticAnalysis: () => this.refreshSemanticAnalysis(),
81
+ exportSemanticChunk: (chunkName) => this.exportSemanticChunk(chunkName),
82
+ lastSemanticAnalysis: this.lastSemanticAnalysis
83
+ };
94
84
 
95
- if (!this.bundles.has('master')) {
96
- this.bundles.set('master', {
97
- patterns: ['**/*'],
98
- files: [],
99
- content: '',
100
- changed: false,
101
- lastGenerated: null,
102
- size: 0
103
- });
104
- }
105
- }
85
+ // Create activity manager placeholder
86
+ this.activityManager = {
87
+ loadActivities: () => this.loadActivities(),
88
+ executeActivity: (id) => this.executeActivity(id),
89
+ stopActivity: (id) => this.stopActivity(id)
90
+ };
106
91
 
107
- loadHiddenFilesConfig() {
108
- if (existsSync(this.HIDDEN_FILES_CONFIG)) {
109
- try {
110
- const config = JSON.parse(readFileSync(this.HIDDEN_FILES_CONFIG, 'utf8'));
111
- this.hiddenFilesConfig = { ...this.hiddenFilesConfig, ...config };
112
- } catch (e) {
113
- if (!this.isQuietMode) console.warn('Could not load hidden files config:', e.message);
114
- }
115
- }
116
- }
92
+ // Initialize API router with all managers
93
+ this.apiRouter = new APIRouter(
94
+ this.configManager,
95
+ this.bundleManager,
96
+ this.fileSystemManager,
97
+ this.semanticAnalysisManager,
98
+ this.vectorStore,
99
+ this.activityManager
100
+ );
117
101
 
118
- saveHiddenFilesConfig() {
119
- try {
120
- writeFileSync(this.HIDDEN_FILES_CONFIG, JSON.stringify(this.hiddenFilesConfig, null, 2));
121
- } catch (e) {
122
- if (!this.isQuietMode) console.error('Failed to save hidden files config:', e.message);
123
- }
102
+ // Add references for cross-module communication
103
+ this.bundleManager.fileSystemManager = this.fileSystemManager;
104
+ this.bundleManager.webSocketManager = this.webSocketManager;
105
+ this.apiRouter.mcpServerStarted = this.mcpServerStarted;
124
106
  }
125
107
 
126
- isFileHidden(filePath, bundleName = null) {
127
- // Check global hidden files
128
- if (this.hiddenFilesConfig.globalHidden.includes(filePath)) {
129
- return true;
130
- }
108
+ // Progress bar utility
109
+ async showProgressBar(message, minTime = 500) {
110
+ const startTime = Date.now();
111
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
112
+ let frameIndex = 0;
131
113
 
132
- // Check bundle-specific hidden files
133
- if (bundleName && this.hiddenFilesConfig.bundleSpecific[bundleName]) {
134
- return this.hiddenFilesConfig.bundleSpecific[bundleName].includes(filePath);
135
- }
114
+ const interval = setInterval(() => {
115
+ process.stdout.write(`\r${frames[frameIndex]} ${message}`);
116
+ frameIndex = (frameIndex + 1) % frames.length;
117
+ }, 80);
136
118
 
137
- return false;
138
- }
139
-
140
- toggleFileVisibility(filePath, bundleName = null, forceHide = null) {
141
- if (bundleName) {
142
- // Bundle-specific hiding
143
- if (!this.hiddenFilesConfig.bundleSpecific[bundleName]) {
144
- this.hiddenFilesConfig.bundleSpecific[bundleName] = [];
145
- }
119
+ return () => {
120
+ clearInterval(interval);
121
+ const elapsed = Date.now() - startTime;
122
+ const remaining = Math.max(0, minTime - elapsed);
146
123
 
147
- const bundleHidden = this.hiddenFilesConfig.bundleSpecific[bundleName];
148
- const isCurrentlyHidden = bundleHidden.includes(filePath);
149
-
150
- if (forceHide === null) {
151
- // Toggle current state
152
- if (isCurrentlyHidden) {
153
- this.hiddenFilesConfig.bundleSpecific[bundleName] = bundleHidden.filter(f => f !== filePath);
154
- } else {
155
- bundleHidden.push(filePath);
156
- }
157
- } else {
158
- // Force to specific state
159
- if (forceHide && !isCurrentlyHidden) {
160
- bundleHidden.push(filePath);
161
- } else if (!forceHide && isCurrentlyHidden) {
162
- this.hiddenFilesConfig.bundleSpecific[bundleName] = bundleHidden.filter(f => f !== filePath);
163
- }
124
+ if (remaining > 0) {
125
+ return new Promise(resolve => setTimeout(resolve, remaining));
164
126
  }
165
- } else {
166
- // Global hiding
167
- const isCurrentlyHidden = this.hiddenFilesConfig.globalHidden.includes(filePath);
168
-
169
- if (forceHide === null) {
170
- // Toggle current state
171
- if (isCurrentlyHidden) {
172
- this.hiddenFilesConfig.globalHidden = this.hiddenFilesConfig.globalHidden.filter(f => f !== filePath);
173
- } else {
174
- this.hiddenFilesConfig.globalHidden.push(filePath);
175
- }
176
- } else {
177
- // Force to specific state
178
- if (forceHide && !isCurrentlyHidden) {
179
- this.hiddenFilesConfig.globalHidden.push(filePath);
180
- } else if (!forceHide && isCurrentlyHidden) {
181
- this.hiddenFilesConfig.globalHidden = this.hiddenFilesConfig.globalHidden.filter(f => f !== filePath);
182
- }
183
- }
184
- }
185
-
186
- this.saveHiddenFilesConfig();
127
+ return Promise.resolve();
128
+ };
187
129
  }
188
130
 
189
- bulkToggleFileVisibility(filePaths, bundleName = null, forceHide = null) {
190
- filePaths.forEach(filePath => {
191
- this.toggleFileVisibility(filePath, bundleName, forceHide);
192
- });
193
- }
131
+ // Single progress bar for initialization
132
+ async showInitProgress(steps) {
133
+ const totalSteps = steps.length;
134
+ let currentStep = 0;
194
135
 
195
- addUserIgnorePattern(pattern) {
196
- if (!this.hiddenFilesConfig.userIgnorePatterns.includes(pattern)) {
197
- this.hiddenFilesConfig.userIgnorePatterns.push(pattern);
198
- this.saveHiddenFilesConfig();
199
- this.loadIgnorePatterns();
200
- this.generateAllBundles();
201
- return true;
202
- }
203
- return false;
204
- }
136
+ const updateProgress = (stepName, completed = false) => {
137
+ const progress = Math.round((currentStep / totalSteps) * 100);
138
+ const barLength = 30;
139
+ const filledLength = Math.round((progress / 100) * barLength);
140
+ const bar = '█'.repeat(filledLength) + '░'.repeat(barLength - filledLength);
205
141
 
206
- removeUserIgnorePattern(pattern) {
207
- const index = this.hiddenFilesConfig.userIgnorePatterns.indexOf(pattern);
208
- if (index > -1) {
209
- this.hiddenFilesConfig.userIgnorePatterns.splice(index, 1);
210
- this.saveHiddenFilesConfig();
211
- this.loadIgnorePatterns();
212
- this.generateAllBundles();
213
- return true;
214
- }
215
- return false;
216
- }
142
+ // Clear the line and show progress
143
+ process.stdout.write(`\r[${bar}] ${progress}% - ${stepName}${' '.repeat(20)}`);
144
+ };
217
145
 
218
- toggleSystemIgnorePattern(pattern) {
219
- const index = this.hiddenFilesConfig.disabledSystemPatterns.indexOf(pattern);
220
- if (index > -1) {
221
- // Re-enable the pattern
222
- this.hiddenFilesConfig.disabledSystemPatterns.splice(index, 1);
223
- } else {
224
- // Disable the pattern
225
- this.hiddenFilesConfig.disabledSystemPatterns.push(pattern);
226
- }
146
+ // Initialize progress bar
147
+ updateProgress(steps[0]);
227
148
 
228
- this.saveHiddenFilesConfig();
229
- this.loadIgnorePatterns();
230
- this.generateAllBundles();
231
- }
149
+ return {
150
+ next: (stepName, minTime = 800) => {
151
+ return new Promise(async (resolve) => {
152
+ const startTime = Date.now();
232
153
 
233
- loadIgnorePatterns() {
234
- const systemPatterns = [
235
- // Version control
236
- '**/.git/**',
237
- '**/.svn/**',
238
- '**/.hg/**',
239
-
240
- // Dependencies
241
- '**/node_modules/**',
242
- '**/vendor/**',
243
- '**/.pnp/**',
244
-
245
- // Build outputs
246
- '**/dist/**',
247
- '**/build/**',
248
- '**/out/**',
249
- '**/.next/**',
250
- '**/.nuxt/**',
251
- '**/target/**',
252
-
253
- // Package files
254
- '**/*.tgz',
255
- '**/*.tar.gz',
256
- '**/*.zip',
257
- '**/*.rar',
258
- '**/*.7z',
259
-
260
- // Logs
261
- '**/*.log',
262
- '**/logs/**',
263
-
264
- // Cache directories
265
- '**/.cache/**',
266
- '**/.parcel-cache/**',
267
- '**/.nyc_output/**',
268
- '**/coverage/**',
269
- '**/.pytest_cache/**',
270
- '**/__pycache__/**',
271
-
272
- // IDE/Editor files
273
- '**/.vscode/**',
274
- '**/.idea/**',
275
- '**/*.swp',
276
- '**/*.swo',
277
- '**/*~',
278
-
279
- // OS files
280
- '**/.DS_Store',
281
- '**/Thumbs.db',
282
- '**/desktop.ini',
283
-
284
- // Environment files
285
- '**/.env',
286
- '**/.env.local',
287
- '**/.env.*.local',
288
-
289
- // Lock files
290
- '**/package-lock.json',
291
- '**/yarn.lock',
292
- '**/pnpm-lock.yaml',
293
- '**/Cargo.lock',
294
-
295
- // cntx-ui specific
296
- '**/.cntx/**'
297
- ];
298
-
299
- // Read from .cntxignore file
300
- let filePatterns = [];
301
- if (existsSync(this.IGNORE_FILE)) {
302
- filePatterns = readFileSync(this.IGNORE_FILE, 'utf8')
303
- .split('\n')
304
- .map(line => line.trim())
305
- .filter(line => line && !line.startsWith('#'));
306
- }
154
+ // Move to next step
155
+ currentStep++;
307
156
 
308
- // Combine all patterns
309
- this.ignorePatterns = [
310
- // System patterns (filtered by disabled list)
311
- ...systemPatterns.filter(pattern =>
312
- !this.hiddenFilesConfig.disabledSystemPatterns.includes(pattern)
313
- ),
314
- // File patterns
315
- ...filePatterns.filter(pattern =>
316
- !systemPatterns.includes(pattern) &&
317
- !this.hiddenFilesConfig.userIgnorePatterns.includes(pattern)
318
- ),
319
- // User-added patterns
320
- ...this.hiddenFilesConfig.userIgnorePatterns
321
- ];
322
-
323
- // Update .cntxignore file with current patterns
324
- const allPatterns = [
325
- '# System patterns',
326
- ...systemPatterns.map(pattern =>
327
- this.hiddenFilesConfig.disabledSystemPatterns.includes(pattern)
328
- ? `# ${pattern}`
329
- : pattern
330
- ),
331
- '',
332
- '# User patterns',
333
- ...this.hiddenFilesConfig.userIgnorePatterns,
334
- '',
335
- '# File-specific patterns (edit manually)',
336
- ...filePatterns.filter(pattern =>
337
- !systemPatterns.includes(pattern) &&
338
- !this.hiddenFilesConfig.userIgnorePatterns.includes(pattern)
339
- )
340
- ];
341
-
342
- writeFileSync(this.IGNORE_FILE, allPatterns.join('\n'));
343
- }
157
+ if (currentStep < totalSteps) {
158
+ updateProgress(steps[currentStep]);
159
+ }
344
160
 
345
- loadBundleStates() {
346
- if (existsSync(this.BUNDLES_FILE)) {
347
- try {
348
- const savedBundles = JSON.parse(readFileSync(this.BUNDLES_FILE, 'utf8'));
349
- Object.entries(savedBundles).forEach(([name, data]) => {
350
- if (this.bundles.has(name)) {
351
- const bundle = this.bundles.get(name);
352
- bundle.content = data.content || '';
353
- bundle.lastGenerated = data.lastGenerated;
354
- bundle.size = data.size || 0;
161
+ // Add random delay between 200-800ms on top of minimum time
162
+ const randomDelay = Math.floor(Math.random() * 600) + 200;
163
+ const totalDelay = minTime + randomDelay;
164
+
165
+ // Wait minimum time + random delay
166
+ const elapsed = Date.now() - startTime;
167
+ const remaining = Math.max(0, totalDelay - elapsed);
168
+ if (remaining > 0) {
169
+ await new Promise(resolve => setTimeout(resolve, remaining));
355
170
  }
171
+
172
+ resolve();
356
173
  });
357
- } catch (e) {
358
- if (!this.isQuietMode) console.warn('Could not load bundle states:', e.message);
174
+ },
175
+ complete: () => {
176
+ const progress = 100;
177
+ const barLength = 30;
178
+ const bar = '█'.repeat(barLength);
179
+ process.stdout.write(`\r[${bar}] ${progress}% - Complete${' '.repeat(20)}\n`);
359
180
  }
360
- }
361
- }
362
-
363
- saveBundleStates() {
364
- const bundleStates = {};
365
- this.bundles.forEach((bundle, name) => {
366
- bundleStates[name] = {
367
- content: bundle.content,
368
- lastGenerated: bundle.lastGenerated,
369
- size: bundle.size
370
- };
371
- });
372
- writeFileSync(this.BUNDLES_FILE, JSON.stringify(bundleStates, null, 2));
181
+ };
373
182
  }
374
183
 
375
- // Cursor Rules Methods
376
- loadCursorRules() {
377
- if (existsSync(this.CURSOR_RULES_FILE)) {
378
- return readFileSync(this.CURSOR_RULES_FILE, 'utf8');
184
+ // Helper method to add initialization messages
185
+ addInitMessage(message) {
186
+ if (this.verbose) {
187
+ this.initMessages.push(message);
379
188
  }
380
- return this.getDefaultCursorRules();
381
189
  }
382
190
 
383
- getDefaultCursorRules() {
384
- // Get project info for context
385
- let projectInfo = { name: 'unknown', description: '', type: 'general' };
386
- const pkgPath = join(this.CWD, 'package.json');
387
-
388
- if (existsSync(pkgPath)) {
389
- try {
390
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
391
- projectInfo = {
392
- name: pkg.name || 'unknown',
393
- description: pkg.description || '',
394
- type: this.detectProjectType(pkg)
395
- };
396
- } catch (e) {
397
- // Use defaults
398
- }
399
- }
191
+ // === Initialization ===
400
192
 
401
- return this.generateCursorRulesTemplate(projectInfo);
402
- }
403
-
404
- detectProjectType(pkg) {
405
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
193
+ async init(options = {}) {
194
+ if (!existsSync(this.CNTX_DIR)) mkdirSync(this.CNTX_DIR, { recursive: true });
406
195
 
407
- if (deps.react || deps['@types/react']) return 'react';
408
- if (deps.vue || deps['@vue/cli']) return 'vue';
409
- if (deps.angular || deps['@angular/core']) return 'angular';
410
- if (deps.express || deps.fastify || deps.koa) return 'node';
411
- if (deps.next || deps.nuxt || deps.gatsby) return 'fullstack';
412
- if (deps.typescript || deps['@types/node']) return 'typescript';
413
- if (pkg.type === 'module' || deps.vite || deps.webpack) return 'modern-js';
196
+ const { skipFileWatcher = false, skipBundleGeneration = false } = options;
414
197
 
415
- return 'general';
416
- }
198
+ const steps = skipFileWatcher
199
+ ? ['Loading configuration', 'Loading semantic cache']
200
+ : ['Loading configuration', 'Setting up file watcher', 'Loading semantic cache', 'Starting file watcher', 'Generating bundles'];
417
201
 
418
- generateCursorRulesTemplate(projectInfo) {
419
- const bundlesList = Array.from(this.bundles.keys()).join(', ');
420
-
421
- const templates = {
422
- react: `# ${projectInfo.name} - React Project Rules
423
-
424
- ## Project Context
425
- - **Project**: ${projectInfo.name}
426
- - **Type**: React Application
427
- - **Description**: ${projectInfo.description}
428
-
429
- ## Development Guidelines
430
-
431
- ### Code Style
432
- - Use TypeScript for all new components
433
- - Prefer functional components with hooks
434
- - Use Tailwind CSS for styling
435
- - Follow React best practices and hooks rules
436
-
437
- ### File Organization
438
- - Components in \`src/components/\`
439
- - Custom hooks in \`src/hooks/\`
440
- - Utilities in \`src/lib/\`
441
- - Types in \`src/types/\`
442
-
443
- ### Naming Conventions
444
- - PascalCase for components
445
- - camelCase for functions and variables
446
- - kebab-case for files and folders
447
- - Use descriptive, meaningful names
448
-
449
- ### Bundle Context
450
- This project uses cntx-ui for file bundling. Current bundles: ${bundlesList}
451
- - **ui**: React components and styles
452
- - **api**: API routes and utilities
453
- - **config**: Configuration files
454
- - **docs**: Documentation
455
-
456
- ### AI Assistant Instructions
457
- - When suggesting code changes, consider the current bundle structure
458
- - Prioritize TypeScript and modern React patterns
459
- - Suggest Tailwind classes for styling
460
- - Keep components focused and reusable
461
- - Always include proper TypeScript types
462
- - Consider bundle organization when suggesting file locations
463
-
464
- ## Custom Rules
465
- Add your specific project rules and preferences below:
466
-
467
- ### Team Preferences
468
- - [Add team coding standards]
469
- - [Add preferred libraries/frameworks]
470
- - [Add project-specific guidelines]
471
-
472
- ### Architecture Notes
473
- - [Document key architectural decisions]
474
- - [Note important patterns to follow]
475
- - [List critical dependencies]
476
- `,
477
-
478
- node: `# ${projectInfo.name} - Node.js Project Rules
479
-
480
- ## Project Context
481
- - **Project**: ${projectInfo.name}
482
- - **Type**: Node.js Backend
483
- - **Description**: ${projectInfo.description}
484
-
485
- ## Development Guidelines
486
-
487
- ### Code Style
488
- - Use ES modules (import/export)
489
- - TypeScript preferred for type safety
490
- - Follow Node.js best practices
491
- - Use async/await over promises
492
-
493
- ### File Organization
494
- - Routes in \`src/routes/\`
495
- - Middleware in \`src/middleware/\`
496
- - Models in \`src/models/\`
497
- - Utilities in \`src/utils/\`
498
-
499
- ### Bundle Context
500
- This project uses cntx-ui for file bundling. Current bundles: ${bundlesList}
501
- - **api**: Core API logic and routes
502
- - **config**: Environment and configuration
503
- - **docs**: API documentation
504
-
505
- ### AI Assistant Instructions
506
- - Focus on scalable backend architecture
507
- - Suggest proper error handling
508
- - Consider security best practices
509
- - Optimize for performance and maintainability
510
- - Consider bundle organization when suggesting file locations
511
-
512
- ## Custom Rules
513
- Add your specific project rules and preferences below:
514
-
515
- ### Team Preferences
516
- - [Add team coding standards]
517
- - [Add preferred libraries/frameworks]
518
- - [Add project-specific guidelines]
519
-
520
- ### Architecture Notes
521
- - [Document key architectural decisions]
522
- - [Note important patterns to follow]
523
- - [List critical dependencies]
524
- `,
525
-
526
- general: `# ${projectInfo.name} - Project Rules
527
-
528
- ## Project Context
529
- - **Project**: ${projectInfo.name}
530
- - **Description**: ${projectInfo.description}
531
-
532
- ## Development Guidelines
533
-
534
- ### Code Quality
535
- - Write clean, readable code
536
- - Follow consistent naming conventions
537
- - Add comments for complex logic
538
- - Maintain proper file organization
539
-
540
- ### Bundle Management
541
- This project uses cntx-ui for intelligent file bundling. Current bundles: ${bundlesList}
542
- - **master**: Complete project overview
543
- - **config**: Configuration and setup files
544
- - **docs**: Documentation and README files
545
-
546
- ### AI Assistant Instructions
547
- - When helping with code, consider the project structure
548
- - Suggest improvements for maintainability
549
- - Follow established patterns in the codebase
550
- - Help optimize bundle configurations when needed
551
- - Consider bundle organization when suggesting file locations
552
-
553
- ## Custom Rules
554
- Add your specific project rules and preferences below:
555
-
556
- ### Team Preferences
557
- - [Add team coding standards]
558
- - [Add preferred libraries/frameworks]
559
- - [Add project-specific guidelines]
560
-
561
- ### Architecture Notes
562
- - [Document key architectural decisions]
563
- - [Note important patterns to follow]
564
- - [List critical dependencies]
565
- `
566
- };
202
+ const progress = await this.showInitProgress(steps);
567
203
 
568
- return templates[projectInfo.type] || templates.general;
569
- }
204
+ // Step 1: Loading configuration
205
+ this.configManager.loadConfig();
206
+ this.configManager.loadHiddenFilesConfig();
207
+ this.configManager.loadIgnorePatterns();
208
+ this.configManager.loadBundleStates();
209
+ await progress.next(steps[0], 800);
570
210
 
571
- saveCursorRules(content) {
572
- writeFileSync(this.CURSOR_RULES_FILE, content, 'utf8');
573
- }
211
+ if (!skipFileWatcher) {
212
+ // Step 2: Setting up file watcher
213
+ this.fileSystemManager.setIgnorePatterns(this.configManager.getIgnorePatterns());
214
+ await progress.next(steps[1], 400);
215
+ }
574
216
 
575
- loadClaudeMd() {
576
- if (existsSync(this.CLAUDE_MD_FILE)) {
577
- return readFileSync(this.CLAUDE_MD_FILE, 'utf8');
217
+ // Step 3: Loading semantic cache
218
+ const cacheData = this.configManager.loadSemanticCache();
219
+ if (cacheData) {
220
+ this.semanticCache = cacheData.analysis;
221
+ this.lastSemanticAnalysis = cacheData.timestamp;
578
222
  }
579
- return this.getDefaultClaudeMd();
580
- }
223
+ await progress.next(skipFileWatcher ? steps[1] : steps[2], 800);
581
224
 
582
- getDefaultClaudeMd() {
583
- // Get project info for context
584
- let projectInfo = { name: 'unknown', description: '', type: 'general' };
585
- const pkgPath = join(this.CWD, 'package.json');
225
+ if (!skipFileWatcher) {
226
+ // Step 4: Starting file watcher
227
+ this.startWatching();
228
+ await progress.next(steps[3], 600);
586
229
 
587
- if (existsSync(pkgPath)) {
588
- try {
589
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
590
- projectInfo = {
591
- name: pkg.name || 'unknown',
592
- description: pkg.description || '',
593
- type: this.detectProjectType(pkg)
594
- };
595
- } catch (e) {
596
- // Use defaults if package.json is invalid
230
+ // Step 5: Generating bundles
231
+ if (!skipBundleGeneration) {
232
+ this.bundleManager.generateAllBundles();
233
+ await progress.next(steps[4], 1200);
597
234
  }
598
235
  }
599
236
 
600
- return this.generateClaudeMdTemplate(projectInfo);
237
+ // Complete progress bar
238
+ progress.complete();
601
239
  }
602
240
 
603
- generateClaudeMdTemplate(projectInfo) {
604
- const { name, description, type } = projectInfo;
605
-
606
- let template = `# ${name}
607
-
608
- ${description ? `${description}\n\n` : ''}## Project Structure
609
-
610
- This project uses cntx-ui for bundle management and AI context organization.
611
-
612
- ### Bundles
613
-
614
- `;
615
-
616
- // Add bundle information
617
- this.bundles.forEach((bundle, bundleName) => {
618
- template += `- **${bundleName}**: ${bundle.files.length} files\n`;
619
- });
241
+ // Display initialization summary
242
+ displayInitSummary() {
243
+ const summary = [];
620
244
 
621
- template += `
622
- ### Development Guidelines
623
-
624
- - Follow the existing code style and patterns
625
- - Use TypeScript for type safety
626
- - Write meaningful commit messages
627
- - Test changes thoroughly
628
-
629
- ### Key Files
630
-
631
- - \`.cntx/config.json\` - Bundle configuration
632
- - \`.cursorrules\` - AI assistant rules
633
- - \`CLAUDE.md\` - Project context for Claude
634
- `;
635
-
636
- if (type === 'react') {
637
- template += `
638
- ### React Specific
639
-
640
- - Use functional components with hooks
641
- - Follow React best practices
642
- - Use TypeScript interfaces for props
643
- `;
644
- } else if (type === 'node') {
645
- template += `
646
- ### Node.js Specific
647
-
648
- - Use ES modules (import/export)
649
- - Follow async/await patterns
650
- - Proper error handling
651
- `;
245
+ // Add semantic cache info
246
+ if (this.semanticCache) {
247
+ summary.push(`Loaded semantic cache (${this.semanticCache.chunks.length} chunks with embeddings)`);
652
248
  }
653
249
 
654
- return template;
655
- }
656
-
657
- saveClaudeMd(content) {
658
- writeFileSync(this.CLAUDE_MD_FILE, content, 'utf8');
659
- }
250
+ // Add ignore patterns info
251
+ const ignorePatterns = this.configManager.getIgnorePatterns();
252
+ if (ignorePatterns.length > 0) {
253
+ summary.push(`Loaded ${ignorePatterns.length} ignore patterns`);
254
+ }
660
255
 
661
- shouldIgnoreFile(filePath) {
662
- const relativePath = relative(this.CWD, filePath).replace(/\\\\/g, '/');
256
+ // Add bundle info
257
+ const bundles = this.bundleManager.getAllBundleInfo();
258
+ if (bundles.length > 0) {
259
+ summary.push(`Generated ${bundles.length} bundles`);
260
+ }
663
261
 
664
- // Hard-coded critical ignores
665
- if (relativePath.startsWith('node_modules/')) return true;
666
- if (relativePath.startsWith('.git/')) return true;
667
- if (relativePath.startsWith('.cntx/')) return true;
262
+ // Add file watcher info
263
+ summary.push('File watcher started');
264
+ summary.push('WebSocket server initialized');
668
265
 
669
- return this.ignorePatterns.some(pattern => this.matchesPattern(relativePath, pattern));
266
+ // Display summary
267
+ if (summary.length > 0) {
268
+ console.log('Initialization complete:');
269
+ summary.forEach(msg => console.log(` • ${msg}`));
270
+ console.log('');
271
+ }
670
272
  }
671
273
 
672
- matchesPattern(path, pattern) {
673
- if (pattern === '**/*') return true;
674
- if (pattern === '*') return !path.includes('/');
675
- if (pattern === path) return true;
676
-
677
- let regexPattern = pattern
678
- .replace(/\\/g, '/')
679
- .replace(/\./g, '\\.')
680
- .replace(/\?/g, '.');
274
+ // === File Watching ===
681
275
 
682
- regexPattern = regexPattern.replace(/\*\*/g, 'DOUBLESTAR');
683
- regexPattern = regexPattern.replace(/\*/g, '[^/]*');
684
- regexPattern = regexPattern.replace(/DOUBLESTAR/g, '.*');
276
+ startWatching() {
277
+ this.fileSystemManager.startWatching(async (eventType, filename) => {
278
+ if (this.verbose) {
279
+ console.log(`📁 File ${eventType}: ${filename}`);
280
+ }
685
281
 
686
- try {
687
- const regex = new RegExp('^' + regexPattern + '$');
688
- return regex.test(path);
689
- } catch (e) {
690
- if (!this.isQuietMode) console.log(`Regex error for pattern "${pattern}": ${e.message}`);
691
- return false;
692
- }
693
- }
282
+ // Skip processing files in .cntx directory to prevent infinite loops
283
+ if (filename.startsWith('.cntx/')) {
284
+ if (this.verbose) {
285
+ console.log(`📁 Skipping .cntx file: ${filename}`);
286
+ }
287
+ return;
288
+ }
694
289
 
695
- shouldIgnoreAnything(itemName, fullPath) {
696
- // DIRECTORY NAME IGNORES (anywhere in the project)
697
- const badDirectories = [
698
- 'node_modules',
699
- '.git',
700
- '.svn',
701
- '.hg',
702
- 'vendor',
703
- '__pycache__',
704
- '.pytest_cache',
705
- '.venv',
706
- 'venv',
707
- 'env',
708
- '.env',
709
- 'dist',
710
- 'build',
711
- 'out',
712
- '.next',
713
- '.nuxt',
714
- 'coverage',
715
- '.nyc_output',
716
- '.cache',
717
- '.parcel-cache',
718
- '.vercel',
719
- '.netlify',
720
- 'tmp',
721
- 'temp',
722
- '.tmp',
723
- '.temp',
724
- 'logs',
725
- '*.egg-info',
726
- '.cntx'
727
- ];
728
-
729
- if (badDirectories.includes(itemName)) {
730
- return true;
731
- }
290
+ // Mark affected bundles as changed
291
+ this.bundleManager.markBundlesChanged(filename);
732
292
 
733
- // FILE EXTENSION IGNORES
734
- const badExtensions = [
735
- // Logs
736
- '.log', '.logs',
737
- // OS files
738
- '.DS_Store', '.Thumbs.db', 'desktop.ini',
739
- // Editor files
740
- '.vscode', '.idea', '*.swp', '*.swo', '*~',
741
- // Media files (large and useless for AI)
742
- '.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp', '.svg',
743
- '.mp4', '.avi', '.mov', '.wmv', '.flv', '.webm', '.mkv',
744
- '.mp3', '.wav', '.flac', '.aac', '.ogg', '.wma',
745
- '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
746
- '.zip', '.tar', '.gz', '.rar', '.7z', '.bz2',
747
- '.exe', '.dll', '.so', '.dylib', '.app', '.dmg', '.pkg',
748
- // Cache/temp files
749
- '.cache', '.tmp', '.temp', '.lock',
750
- // Compiled files
751
- '.pyc', '.pyo', '.class', '.o', '.obj', '.a', '.lib'
752
- ];
753
-
754
- const fileExt = extname(itemName).toLowerCase();
755
- if (badExtensions.includes(fileExt)) {
756
- return true;
757
- }
293
+ // Invalidate semantic cache if needed
294
+ this.invalidateSemanticCache();
758
295
 
759
- // FILE NAME PATTERNS
760
- const badFilePatterns = [
761
- /^\..*/, // Hidden files starting with .
762
- /.*\.lock$/, // Lock files
763
- /.*\.min\.js$/, // Minified JS
764
- /.*\.min\.css$/, // Minified CSS
765
- /.*\.map$/, // Source maps
766
- /package-lock\.json$/,
767
- /yarn\.lock$/,
768
- /pnpm-lock\.yaml$/,
769
- /Thumbs\.db$/,
770
- /\.DS_Store$/
771
- ];
772
-
773
- if (badFilePatterns.some(pattern => pattern.test(itemName))) {
774
- return true;
775
- }
296
+ // Notify WebSocket clients
297
+ this.webSocketManager.onFileChanged(filename, eventType);
776
298
 
777
- // PATH-BASED IGNORES (from your .cntxignore)
778
- return this.ignorePatterns.some(pattern => this.matchesPattern(fullPath, pattern));
299
+ // Automatically regenerate affected bundles after a short delay
300
+ setTimeout(async () => {
301
+ await this.regenerateChangedBundles(filename);
302
+ }, 1000); // 1 second delay to batch multiple rapid changes
303
+ });
779
304
  }
780
305
 
781
- getAllFiles(dir = this.CWD, files = []) {
306
+ async regenerateChangedBundles(filename) {
782
307
  try {
783
- const items = readdirSync(dir);
784
- for (const item of items) {
785
- const fullPath = join(dir, item);
786
- const relativePath = relative(this.CWD, fullPath).replace(/\\\\/g, '/');
308
+ const bundles = this.configManager.getBundles();
309
+ const affectedBundles = [];
787
310
 
788
- // BULLETPROOF IGNORES - check directory/file names directly
789
- const shouldIgnore = this.shouldIgnoreAnything(item, relativePath);
311
+ // Find which bundles are affected by this file
312
+ bundles.forEach((bundle, name) => {
313
+ const matchesBundle = bundle.patterns.some(pattern =>
314
+ this.fileSystemManager.matchesPattern(filename, pattern)
315
+ );
790
316
 
791
- if (shouldIgnore) {
792
- continue; // Don't even log it, just skip
317
+ if (matchesBundle && bundle.changed) {
318
+ affectedBundles.push(name);
793
319
  }
320
+ });
794
321
 
795
- const stat = statSync(fullPath);
796
- if (stat.isDirectory()) {
797
- this.getAllFiles(fullPath, files);
798
- } else {
799
- files.push(relativePath);
322
+ // Regenerate each affected bundle
323
+ for (const bundleName of affectedBundles) {
324
+ if (this.verbose) {
325
+ console.log(`🔄 Auto-regenerating bundle: ${bundleName}`);
800
326
  }
327
+ await this.bundleManager.regenerateBundle(bundleName);
801
328
  }
802
- } catch (e) {
803
- // Skip directories we can't read
804
- }
805
329
 
806
- return files;
330
+ } catch (error) {
331
+ console.error('Failed to auto-regenerate bundles:', error.message);
332
+ }
807
333
  }
808
334
 
809
- startWatching() {
810
- const watcher = watch(this.CWD, { recursive: true }, (eventType, filename) => {
811
- if (filename && !this.isScanning) {
812
- const fullPath = join(this.CWD, filename);
813
- if (!this.shouldIgnoreFile(fullPath)) {
814
- if (!this.isQuietMode) console.log(`File ${eventType}: ${filename}`);
815
- this.markBundlesChanged(filename.replace(/\\\\/g, '/'));
816
- this.invalidateSemanticCache(); // Invalidate semantic cache on file changes
817
- this.broadcastUpdate();
818
- }
819
- }
820
- });
821
- this.watchers.push(watcher);
822
- }
335
+ // === HTTP Server ===
823
336
 
824
- getFileTree() {
825
- const allFiles = this.getAllFiles();
826
- const fileData = allFiles.map(file => {
827
- const fullPath = join(this.CWD, file);
828
- try {
829
- const stat = statSync(fullPath);
830
- return {
831
- path: file,
832
- size: stat.size,
833
- modified: stat.mtime
834
- };
835
- } catch (e) {
836
- return {
837
- path: file,
838
- size: 0,
839
- modified: new Date()
840
- };
841
- }
842
- });
843
- return fileData;
844
- }
337
+ async handleRequest(req, res) {
338
+ const url = new URL(req.url, `http://${req.headers.host}`);
845
339
 
846
- markBundlesChanged(filename) {
847
- this.bundles.forEach((bundle, name) => {
848
- if (bundle.patterns.some(pattern => this.matchesPattern(filename, pattern))) {
849
- bundle.changed = true;
850
- }
851
- });
852
- }
340
+ // Add CORS headers for all requests
341
+ res.setHeader('Access-Control-Allow-Origin', '*');
342
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
343
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
853
344
 
854
- generateAllBundles() {
855
- this.isScanning = true;
856
- if (!this.isQuietMode) console.log('Scanning files and generating bundles...');
345
+ // Handle preflight requests
346
+ if (req.method === 'OPTIONS') {
347
+ res.writeHead(200);
348
+ res.end();
349
+ return;
350
+ }
857
351
 
858
- this.bundles.forEach((bundle, name) => {
859
- this.generateBundle(name);
860
- });
352
+ try {
353
+ // Handle API routes
354
+ if (url.pathname.startsWith('/api/')) {
355
+ return await this.apiRouter.handleRequest(req, res, url);
356
+ }
861
357
 
862
- this.saveBundleStates();
863
- this.isScanning = false;
864
- if (!this.isQuietMode) console.log('Bundle generation complete');
865
- }
358
+ // Handle static files
359
+ return this.handleStaticFile(req, res, url);
866
360
 
867
- generateBundle(name) {
868
- const bundle = this.bundles.get(name);
869
- if (!bundle) return;
361
+ } catch (error) {
362
+ console.error('Request handling error:', error);
363
+ res.writeHead(500, { 'Content-Type': 'application/json' });
364
+ res.end(JSON.stringify({ error: 'Internal server error' }));
365
+ }
366
+ }
870
367
 
871
- if (!this.isQuietMode) console.log(`Generating bundle: ${name}`);
872
- const allFiles = this.getAllFiles();
368
+ handleStaticFile(req, res, url) {
369
+ const webDir = join(__dirname, 'web', 'dist');
370
+ let filePath = join(webDir, url.pathname === '/' ? 'index.html' : url.pathname);
873
371
 
874
- // Filter files by bundle patterns
875
- let bundleFiles = allFiles.filter(file =>
876
- bundle.patterns.some(pattern => this.matchesPattern(file, pattern))
877
- );
372
+ // Security check - ensure path is within web directory
373
+ if (!filePath.startsWith(webDir)) {
374
+ res.writeHead(403, { 'Content-Type': 'text/plain' });
375
+ res.end('Forbidden');
376
+ return;
377
+ }
878
378
 
879
- // Remove hidden files
880
- bundleFiles = bundleFiles.filter(file => !this.isFileHidden(file, name));
379
+ if (!existsSync(filePath)) {
380
+ // For SPA routing, serve index.html for non-API routes
381
+ if (!url.pathname.startsWith('/api/')) {
382
+ filePath = join(webDir, 'index.html');
383
+ } else {
384
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
385
+ res.end('Not Found');
386
+ return;
387
+ }
388
+ }
881
389
 
882
- bundle.files = bundleFiles;
883
- bundle.content = this.generateBundleXML(name, bundle.files);
884
- bundle.changed = false;
885
- bundle.lastGenerated = new Date().toISOString();
886
- bundle.size = Buffer.byteLength(bundle.content, 'utf8');
390
+ try {
391
+ const content = readFileSync(filePath);
392
+ const contentType = getContentType(filePath);
887
393
 
888
- if (!this.isQuietMode) console.log(`Generated bundle '${name}' with ${bundle.files.length} files (${(bundle.size / 1024).toFixed(1)}kb)`);
394
+ res.writeHead(200, { 'Content-Type': contentType });
395
+ res.end(content);
396
+ } catch (error) {
397
+ res.writeHead(500, { 'Content-Type': 'text/plain' });
398
+ res.end('Error reading file');
399
+ }
889
400
  }
890
401
 
891
- generateBundleXML(bundleName, files) {
892
- let xml = `<?xml version="1.0" encoding="UTF-8"?>
893
- <cntx:bundle xmlns:cntx="https://cntx.dev/schema" name="${bundleName}" generated="${new Date().toISOString()}">
894
- `;
402
+ // === Semantic Analysis (Legacy methods for compatibility) ===
895
403
 
896
- // Project information
897
- const pkgPath = join(this.CWD, 'package.json');
898
- if (existsSync(pkgPath)) {
899
- try {
900
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
901
- xml += ` <cntx:project>
902
- <cntx:name>${this.escapeXml(pkg.name || 'unknown')}</cntx:name>
903
- <cntx:version>${pkg.version || '0.0.0'}</cntx:version>
904
- `;
905
- if (pkg.description) xml += ` <cntx:description>${this.escapeXml(pkg.description)}</cntx:description>
906
- `;
907
- xml += ` </cntx:project>
908
- `;
909
- } catch (e) {
910
- xml += ` <cntx:project><cntx:error>Could not parse package.json</cntx:error></cntx:project>
911
- `;
404
+ async getSemanticAnalysis() {
405
+ // First, try to load from cache
406
+ if (!this.semanticCache) {
407
+ const cacheData = this.configManager.loadSemanticCache();
408
+ if (cacheData) {
409
+ this.semanticCache = cacheData.analysis;
410
+ this.lastSemanticAnalysis = cacheData.timestamp;
411
+ return cacheData.analysis;
912
412
  }
913
413
  }
914
414
 
915
- // Bundle overview section
916
- const filesByType = this.categorizeFiles(files);
917
- const entryPoints = this.identifyEntryPoints(files);
918
-
919
- xml += ` <cntx:overview>
920
- <cntx:purpose>${this.escapeXml(this.getBundlePurpose(bundleName))}</cntx:purpose>
921
- <cntx:file-types>
922
- `;
923
-
924
- Object.entries(filesByType).forEach(([type, typeFiles]) => {
925
- xml += ` <cntx:type name="${type}" count="${typeFiles.length}" />
926
- `;
927
- });
928
-
929
- xml += ` </cntx:file-types>
930
- `;
415
+ // Check if we need to refresh the semantic analysis
416
+ const shouldRefresh = !this.semanticCache || !this.lastSemanticAnalysis;
931
417
 
932
- if (entryPoints.length > 0) {
933
- xml += ` <cntx:entry-points>
934
- `;
935
- entryPoints.forEach(file => {
936
- xml += ` <cntx:file>${file}</cntx:file>
937
- `;
938
- });
939
- xml += ` </cntx:entry-points>
940
- `;
941
- }
418
+ if (shouldRefresh) {
419
+ try {
420
+ // Auto-discover JavaScript/TypeScript files in the entire project
421
+ const patterns = ['**/*.{js,jsx,ts,tsx,mjs}'];
942
422
 
943
- xml += ` </cntx:overview>
944
- `;
423
+ // Load bundle configuration for chunk grouping
424
+ let bundleConfig = null;
425
+ if (existsSync(this.configManager.CONFIG_FILE)) {
426
+ bundleConfig = JSON.parse(readFileSync(this.configManager.CONFIG_FILE, 'utf8'));
427
+ }
945
428
 
946
- // Files organized by type
947
- xml += ` <cntx:files count="${files.length}">
948
- `;
429
+ this.semanticCache = await this.semanticSplitter.extractSemanticChunks(this.CWD, patterns, bundleConfig);
430
+ this.lastSemanticAnalysis = Date.now();
949
431
 
950
- // Entry points first
951
- if (entryPoints.length > 0) {
952
- xml += ` <cntx:group type="entry-points" description="Main entry files for this bundle">
953
- `;
954
- entryPoints.forEach(file => {
955
- xml += this.generateFileXML(file);
956
- });
957
- xml += ` </cntx:group>
958
- `;
959
- }
432
+ // Only enhance chunks with embeddings if they don't already have them
433
+ await this.enhanceSemanticChunksIfNeeded(this.semanticCache);
960
434
 
961
- // Then organize by file type
962
- Object.entries(filesByType).forEach(([type, typeFiles]) => {
963
- if (type === 'entry-points') return; // Already handled above
435
+ // Save to disk cache
436
+ this.configManager.saveSemanticCache(this.semanticCache);
964
437
 
965
- const remainingFiles = typeFiles.filter(file => !entryPoints.includes(file));
966
- if (remainingFiles.length > 0) {
967
- xml += ` <cntx:group type="${type}" description="${this.getTypeDescription(type)}">
968
- `;
969
- remainingFiles.forEach(file => {
970
- xml += this.generateFileXML(file);
971
- });
972
- xml += ` </cntx:group>
973
- `;
438
+ console.log('🔍 Semantic analysis complete');
439
+ } catch (error) {
440
+ console.error('Semantic analysis failed:', error.message);
441
+ throw new Error(`Semantic analysis failed: ${error.message}`);
974
442
  }
975
- });
443
+ }
976
444
 
977
- xml += ` </cntx:files>
978
- </cntx:bundle>`;
979
- return xml;
445
+ return this.semanticCache;
980
446
  }
981
447
 
982
- categorizeFiles(files) {
983
- const categories = {
984
- 'components': [],
985
- 'hooks': [],
986
- 'utilities': [],
987
- 'configuration': [],
988
- 'styles': [],
989
- 'types': [],
990
- 'tests': [],
991
- 'documentation': [],
992
- 'other': []
993
- };
448
+ async refreshSemanticAnalysis() {
449
+ console.log('🔄 Forcing semantic analysis refresh...');
994
450
 
995
- files.forEach(file => {
996
- const ext = extname(file).toLowerCase();
997
- const basename = file.toLowerCase();
998
-
999
- if (basename.includes('component') || file.includes('/components/') ||
1000
- ext === '.jsx' || ext === '.tsx' && !basename.includes('test')) {
1001
- categories.components.push(file);
1002
- } else if (basename.includes('hook') || file.includes('/hooks/')) {
1003
- categories.hooks.push(file);
1004
- } else if (basename.includes('util') || file.includes('/utils/') ||
1005
- basename.includes('helper') || file.includes('/lib/')) {
1006
- categories.utilities.push(file);
1007
- } else if (ext === '.json' || basename.includes('config') ||
1008
- ext === '.yaml' || ext === '.yml' || ext === '.toml') {
1009
- categories.configuration.push(file);
1010
- } else if (ext === '.css' || ext === '.scss' || ext === '.less') {
1011
- categories.styles.push(file);
1012
- } else if (basename.includes('type') || ext === '.d.ts' ||
1013
- file.includes('/types/')) {
1014
- categories.types.push(file);
1015
- } else if (basename.includes('test') || basename.includes('spec') ||
1016
- file.includes('/test/') || file.includes('/__tests__/')) {
1017
- categories.tests.push(file);
1018
- } else if (ext === '.md' || basename.includes('readme') ||
1019
- basename.includes('doc')) {
1020
- categories.documentation.push(file);
1021
- } else {
1022
- categories.other.push(file);
1023
- }
1024
- });
451
+ // Clear memory cache
452
+ this.semanticCache = null;
453
+ this.lastSemanticAnalysis = null;
1025
454
 
1026
- // Remove empty categories
1027
- Object.keys(categories).forEach(key => {
1028
- if (categories[key].length === 0) {
1029
- delete categories[key];
1030
- }
1031
- });
455
+ // Remove disk cache file
456
+ this.configManager.invalidateSemanticCache();
1032
457
 
1033
- return categories;
458
+ return this.getSemanticAnalysis();
1034
459
  }
1035
460
 
1036
- identifyEntryPoints(files) {
1037
- const entryPoints = [];
461
+ async enhanceSemanticChunksIfNeeded(analysis) {
462
+ if (!analysis || !analysis.chunks) return;
1038
463
 
1039
- files.forEach(file => {
1040
- const basename = file.toLowerCase();
1041
-
1042
- // Common entry point patterns
1043
- if (basename.includes('main.') || basename.includes('index.') ||
1044
- basename.includes('app.') || basename === 'server.js' ||
1045
- file.endsWith('/App.tsx') || file.endsWith('/App.jsx') ||
1046
- file.endsWith('/main.tsx') || file.endsWith('/main.js') ||
1047
- file.endsWith('/index.tsx') || file.endsWith('/index.js')) {
1048
- entryPoints.push(file);
1049
- }
1050
- });
1051
-
1052
- return entryPoints;
1053
- }
464
+ const chunksNeedingEmbeddings = analysis.chunks.filter(chunk => !chunk.embedding);
1054
465
 
1055
- getBundlePurpose(bundleName) {
1056
- const purposes = {
1057
- 'master': 'Complete project overview with all source files',
1058
- 'frontend': 'User interface components, pages, and client-side logic',
1059
- 'backend': 'Server-side logic, APIs, and backend services',
1060
- 'api': 'API endpoints, routes, and server communication logic',
1061
- 'server': 'Main server application and core backend functionality',
1062
- 'components': 'Reusable UI components and interface elements',
1063
- 'ui-components': 'User interface components and design system elements',
1064
- 'config': 'Configuration files, settings, and environment setup',
1065
- 'docs': 'Documentation, README files, and project guides',
1066
- 'utils': 'Utility functions, helpers, and shared libraries',
1067
- 'types': 'TypeScript type definitions and interfaces',
1068
- 'tests': 'Test files, test utilities, and testing configuration'
1069
- };
466
+ if (chunksNeedingEmbeddings.length === 0) {
467
+ console.log('✅ All chunks already have embeddings');
468
+ return;
469
+ }
1070
470
 
1071
- return purposes[bundleName] || `Bundle containing ${bundleName}-related files`;
1072
- }
471
+ console.log(`🔧 Enhancing ${chunksNeedingEmbeddings.length} chunks with embeddings...`);
1073
472
 
1074
- getTypeDescription(type) {
1075
- const descriptions = {
1076
- 'components': 'React/UI components and interface elements',
1077
- 'hooks': 'Custom React hooks and state management',
1078
- 'utilities': 'Helper functions, utilities, and shared libraries',
1079
- 'configuration': 'Configuration files, settings, and build configs',
1080
- 'styles': 'CSS, SCSS, and styling files',
1081
- 'types': 'TypeScript type definitions and interfaces',
1082
- 'tests': 'Test files and testing utilities',
1083
- 'documentation': 'README files, docs, and guides',
1084
- 'other': 'Additional project files'
1085
- };
473
+ // Initialize vector store if needed
474
+ if (!this.vectorStoreInitialized) {
475
+ await this.vectorStore.init();
476
+ this.vectorStoreInitialized = true;
477
+ }
1086
478
 
1087
- return descriptions[type] || `Files categorized as ${type}`;
479
+ // Add embeddings to chunks that need them
480
+ for (const chunk of chunksNeedingEmbeddings) {
481
+ try {
482
+ const content = this.getChunkContentForEmbedding(chunk);
483
+ chunk.embedding = await this.vectorStore.generateEmbedding(content);
484
+ } catch (error) {
485
+ console.error(`Failed to generate embedding for chunk ${chunk.id}:`, error.message);
486
+ }
487
+ }
1088
488
  }
1089
489
 
1090
- generateFileXML(file) {
1091
- const fullPath = join(this.CWD, file);
1092
- let fileXml = ` <cntx:file path="${file}" ext="${extname(file)}">
1093
- `;
490
+ getChunkContentForEmbedding(chunk) {
491
+ let content = chunk.content || '';
1094
492
 
1095
- try {
1096
- const stat = statSync(fullPath);
1097
- const content = readFileSync(fullPath, 'utf8');
1098
-
1099
- // Add role indicator for certain files
1100
- const role = this.getFileRole(file);
1101
- const roleAttr = role ? ` role="${role}"` : '';
493
+ if (chunk.businessDomains?.length > 0) {
494
+ content += ' ' + chunk.businessDomains.join(' ');
495
+ }
1102
496
 
1103
- fileXml = ` <cntx:file path="${file}" ext="${extname(file)}"${roleAttr}>
1104
- `;
1105
- fileXml += ` <cntx:meta size="${stat.size}" modified="${stat.mtime.toISOString()}" lines="${content.split('\n').length}" />
1106
- <cntx:content><![CDATA[${content}]]></cntx:content>
1107
- `;
1108
- } catch (e) {
1109
- fileXml += ` <cntx:error>Could not read file: ${e.message}</cntx:error>
1110
- `;
497
+ if (chunk.technicalPatterns?.length > 0) {
498
+ content += ' ' + chunk.technicalPatterns.join(' ');
1111
499
  }
1112
500
 
1113
- fileXml += ` </cntx:file>
1114
- `;
1115
- return fileXml;
501
+ return content.trim();
1116
502
  }
1117
503
 
1118
- getFileRole(file) {
1119
- const basename = file.toLowerCase();
504
+ async exportSemanticChunk(chunkName) {
505
+ const analysis = await this.getSemanticAnalysis();
506
+ const chunk = analysis.chunks.find(c => c.name === chunkName || c.id === chunkName);
1120
507
 
1121
- if (basename.includes('main.') || basename.includes('index.')) return 'entry-point';
1122
- if (basename.includes('app.')) return 'main-component';
1123
- if (file === 'server.js') return 'server-entry';
1124
- if (basename.includes('config')) return 'configuration';
1125
- if (basename.includes('package.json')) return 'package-config';
1126
- if (basename.includes('readme')) return 'documentation';
508
+ if (!chunk) {
509
+ throw new Error(`Chunk "${chunkName}" not found`);
510
+ }
1127
511
 
1128
- return null;
512
+ return this.bundleManager.generateFileXML(chunk.filePath);
1129
513
  }
1130
514
 
1131
- escapeXml(text) {
1132
- return String(text)
1133
- .replace(/&/g, '&amp;')
1134
- .replace(/</g, '&lt;')
1135
- .replace(/>/g, '&gt;')
1136
- .replace(/"/g, '&quot;')
1137
- .replace(/'/g, '&apos;');
515
+ invalidateSemanticCache() {
516
+ this.semanticCache = null;
517
+ this.lastSemanticAnalysis = null;
1138
518
  }
1139
519
 
1140
- getFileStats(filePath) {
1141
- try {
1142
- const fullPath = join(this.CWD, filePath);
1143
- const stat = statSync(fullPath);
1144
- return {
1145
- size: stat.size,
1146
- mtime: stat.mtime
1147
- };
1148
- } catch (e) {
1149
- return {
1150
- size: 0,
1151
- mtime: new Date()
1152
- };
1153
- }
1154
- }
520
+ // === Activity Management (Placeholder) ===
1155
521
 
1156
- getFileListWithVisibility(bundleName = null) {
1157
- const allFiles = this.getAllFiles();
1158
-
1159
- return allFiles.map(filePath => {
1160
- const fileStats = this.getFileStats(filePath);
1161
- const isGloballyHidden = this.hiddenFilesConfig.globalHidden.includes(filePath);
1162
- const bundleHidden = bundleName ? this.isFileHidden(filePath, bundleName) : false;
1163
-
1164
- // Determine which bundles this file appears in
1165
- const inBundles = [];
1166
- this.bundles.forEach((bundle, name) => {
1167
- const matchesPattern = bundle.patterns.some(pattern => this.matchesPattern(filePath, pattern));
1168
- const notHidden = !this.isFileHidden(filePath, name);
1169
- if (matchesPattern && notHidden) {
1170
- inBundles.push(name);
1171
- }
1172
- });
522
+ async loadActivities() {
523
+ try {
524
+ const activitiesPath = join(this.CWD, '.cntx', 'activities');
525
+ const activitiesJsonPath = join(activitiesPath, 'activities.json');
1173
526
 
1174
- return {
1175
- path: filePath,
1176
- size: fileStats.size,
1177
- modified: fileStats.mtime,
1178
- visible: !isGloballyHidden && !bundleHidden,
1179
- globallyHidden: isGloballyHidden,
1180
- bundleHidden: bundleHidden,
1181
- inBundles: inBundles,
1182
- matchesIgnorePattern: this.shouldIgnoreFile(join(this.CWD, filePath))
1183
- };
1184
- });
1185
- }
527
+ console.log('DEBUG: Looking for activities at:', activitiesJsonPath);
528
+ console.log('DEBUG: File exists:', fs.existsSync(activitiesJsonPath));
529
+ console.log('DEBUG: CWD is:', this.CWD);
1186
530
 
1187
- startServer(port = 3333) {
1188
- const server = createServer((req, res) => {
1189
- const url = new URL(req.url, `http://localhost:${port}`);
1190
-
1191
- // CORS headers for ALL requests - MUST be first
1192
- res.setHeader('Access-Control-Allow-Origin', '*');
1193
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
1194
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
1195
- res.setHeader('Access-Control-Max-Age', '86400');
1196
-
1197
- // Handle preflight OPTIONS requests
1198
- if (req.method === 'OPTIONS') {
1199
- res.writeHead(200);
1200
- res.end();
1201
- return;
531
+ if (!fs.existsSync(activitiesJsonPath)) {
532
+ console.log('Activities file not found, returning empty array');
533
+ return [];
1202
534
  }
1203
535
 
1204
- // Serve static files for web interface
1205
- if (url.pathname === '/' || url.pathname.startsWith('/assets/') || url.pathname.endsWith('.js') || url.pathname.endsWith('.css') || url.pathname.endsWith('.ico')) {
1206
- const webDistPath = path.join(__dirname, 'web', 'dist');
1207
-
1208
- if (url.pathname === '/') {
1209
- // Serve index.html for root
1210
- const indexPath = path.join(webDistPath, 'index.html');
1211
- if (existsSync(indexPath)) {
1212
- try {
1213
- const content = readFileSync(indexPath, 'utf8');
1214
- res.writeHead(200, { 'Content-Type': 'text/html' });
1215
- res.end(content);
1216
- return;
1217
- } catch (e) {
1218
- if (!this.isQuietMode) console.error('Error serving index.html:', e);
1219
- }
1220
- }
1221
-
1222
- // Fallback if no web interface built
1223
- res.writeHead(200, { 'Content-Type': 'text/html' });
1224
- res.end(`
1225
- <!DOCTYPE html>
1226
- <html>
1227
- <head>
1228
- <title>cntx-ui Server</title>
1229
- <style>
1230
- body { font-family: system-ui, sans-serif; margin: 40px; }
1231
- .container { max-width: 600px; }
1232
- .api-link { background: #f5f5f5; padding: 10px; border-radius: 5px; margin: 10px 0; }
1233
- code { background: #f0f0f0; padding: 2px 5px; border-radius: 3px; }
1234
- </style>
1235
- </head>
1236
- <body>
1237
- <div class="container">
1238
- <h1>🚀 cntx-ui Server Running</h1>
1239
- <p>Your cntx-ui server is running successfully!</p>
1240
-
1241
- <h2>Available APIs:</h2>
1242
- <div class="api-link">
1243
- <strong>Bundles:</strong> <a href="/api/bundles">/api/bundles</a>
1244
- </div>
1245
- <div class="api-link">
1246
- <strong>Configuration:</strong> <a href="/api/config">/api/config</a>
1247
- </div>
1248
- <div class="api-link">
1249
- <strong>Files:</strong> <a href="/api/files">/api/files</a>
1250
- </div>
1251
- <div class="api-link">
1252
- <strong>Status:</strong> <a href="/api/status">/api/status</a>
1253
- </div>
1254
-
1255
- <h2>Web Interface:</h2>
1256
- <p>The web interface is not available because it wasn't built when this package was published.</p>
1257
- <p>To enable the web interface, the package maintainer needs to run:</p>
1258
- <pre><code>cd web && npm install && npm run build</code></pre>
1259
-
1260
- <h2>CLI Usage:</h2>
1261
- <p>You can still use all CLI commands:</p>
1262
- <ul>
1263
- <li><code>cntx-ui status</code> - Check current status</li>
1264
- <li><code>cntx-ui bundle master</code> - Generate specific bundle</li>
1265
- <li><code>cntx-ui init</code> - Initialize configuration</li>
1266
- </ul>
1267
- </div>
1268
- </body>
1269
- </html>
1270
- `);
1271
- return;
1272
- } else {
1273
- // Serve other static assets
1274
- const filePath = path.join(webDistPath, url.pathname);
1275
- if (existsSync(filePath)) {
1276
- try {
1277
- const content = readFileSync(filePath);
1278
- const contentType = getContentType(filePath);
1279
- res.writeHead(200, { 'Content-Type': contentType });
1280
- res.end(content);
1281
- return;
1282
- } catch (e) {
1283
- if (!this.isQuietMode) console.error('Error serving static file:', e);
1284
- }
536
+ const activitiesData = JSON.parse(fs.readFileSync(activitiesJsonPath, 'utf8'));
537
+
538
+ return activitiesData.map((activity, index) => {
539
+ // Extract the actual directory name from the references field
540
+ let activityId = activity.title.toLowerCase().replace(/[^a-z0-9]/g, '-');
541
+ if (activity.references && activity.references.length > 0) {
542
+ // Extract directory name from path like ".cntx/activities/activities/refactor-js-to-ts/README.md"
543
+ const refPath = activity.references[0];
544
+ const pathParts = refPath.split('/');
545
+ if (pathParts.length >= 4) {
546
+ activityId = pathParts[3]; // activities/activities/{this-part}/README.md
1285
547
  }
1286
548
  }
1287
- }
1288
-
1289
- // API Routes
1290
- console.log('🔍 Processing API request:', url.pathname);
1291
-
1292
- if (url.pathname === '/api/bundles') {
1293
- res.writeHead(200, { 'Content-Type': 'application/json' });
1294
- const bundleData = Array.from(this.bundles.entries()).map(([name, bundle]) => ({
1295
- name,
1296
- changed: bundle.changed,
1297
- fileCount: bundle.files.length,
1298
- content: bundle.content.substring(0, 5000) + (bundle.content.length > 5000 ? '...' : ''),
1299
- files: bundle.files,
1300
- lastGenerated: bundle.lastGenerated,
1301
- size: bundle.size
1302
- }));
1303
- res.end(JSON.stringify(bundleData));
1304
-
1305
- } else if (url.pathname === '/api/semantic-chunks') {
1306
- console.log('🔍 Semantic chunks route matched! URL:', url.pathname);
1307
- this.getSemanticAnalysis()
1308
- .then(analysis => {
1309
- console.log('✅ Semantic analysis successful');
1310
- res.writeHead(200, { 'Content-Type': 'application/json' });
1311
- res.end(JSON.stringify(analysis));
1312
- })
1313
- .catch(error => {
1314
- console.error('❌ Semantic analysis failed:', error.message);
1315
- res.writeHead(500, { 'Content-Type': 'application/json' });
1316
- res.end(JSON.stringify({ error: error.message }));
1317
- });
1318
-
1319
- } else if (url.pathname === '/api/semantic-chunks/export') {
1320
- if (req.method === 'POST') {
1321
- let body = '';
1322
- req.on('data', chunk => body += chunk);
1323
- req.on('end', () => {
1324
- try {
1325
- const { chunkName } = JSON.parse(body);
1326
- this.exportSemanticChunk(chunkName)
1327
- .then(xmlContent => {
1328
- res.writeHead(200, { 'Content-Type': 'application/xml' });
1329
- res.end(xmlContent);
1330
- })
1331
- .catch(error => {
1332
- res.writeHead(500, { 'Content-Type': 'application/json' });
1333
- res.end(JSON.stringify({ error: error.message }));
1334
- });
1335
- } catch (error) {
1336
- res.writeHead(500, { 'Content-Type': 'application/json' });
1337
- res.end(JSON.stringify({ error: error.message }));
1338
- }
1339
- });
1340
- } else {
1341
- res.writeHead(405);
1342
- res.end('Method not allowed');
1343
- }
1344
-
1345
- } else if (url.pathname === '/api/bundles-from-chunk') {
1346
- if (req.method === 'POST') {
1347
- let body = '';
1348
- req.on('data', chunk => body += chunk);
1349
- req.on('end', () => {
1350
- try {
1351
- const { chunkName, files } = JSON.parse(body);
1352
- this.createBundleFromChunk(chunkName, files)
1353
- .then(() => {
1354
- res.writeHead(200, { 'Content-Type': 'application/json' });
1355
- res.end(JSON.stringify({ success: true }));
1356
- })
1357
- .catch(error => {
1358
- res.writeHead(500, { 'Content-Type': 'application/json' });
1359
- res.end(JSON.stringify({ error: error.message }));
1360
- });
1361
- } catch (error) {
1362
- res.writeHead(500, { 'Content-Type': 'application/json' });
1363
- res.end(JSON.stringify({ error: error.message }));
1364
- }
1365
- });
1366
- } else {
1367
- res.writeHead(405);
1368
- res.end('Method not allowed');
1369
- }
1370
-
1371
- } else if (url.pathname.startsWith('/api/bundles/')) {
1372
- const bundleName = url.pathname.split('/').pop();
1373
- const bundle = this.bundles.get(bundleName);
1374
- if (bundle) {
1375
- res.writeHead(200, { 'Content-Type': 'application/xml' });
1376
- res.end(bundle.content);
1377
- } else {
1378
- res.writeHead(404);
1379
- res.end('Bundle not found');
1380
- }
1381
-
1382
- } else if (url.pathname.startsWith('/api/regenerate/')) {
1383
- const bundleName = url.pathname.split('/').pop();
1384
- if (this.bundles.has(bundleName)) {
1385
- this.generateBundle(bundleName);
1386
- this.saveBundleStates();
1387
- this.broadcastUpdate();
1388
- res.writeHead(200);
1389
- res.end('OK');
1390
- } else {
1391
- res.writeHead(404);
1392
- res.end('Bundle not found');
1393
- }
1394
-
1395
- } else if (url.pathname === '/api/files') {
1396
- res.writeHead(200, { 'Content-Type': 'application/json' });
1397
- const fileTree = this.getFileTree();
1398
- res.end(JSON.stringify(fileTree));
1399
-
1400
- } else if (url.pathname === '/api/config') {
1401
- if (req.method === 'GET') {
1402
- res.writeHead(200, { 'Content-Type': 'application/json' });
1403
- if (existsSync(this.CONFIG_FILE)) {
1404
- const config = readFileSync(this.CONFIG_FILE, 'utf8');
1405
- res.end(config);
1406
- } else {
1407
- const defaultConfig = {
1408
- bundles: {
1409
- master: ['**/*'],
1410
- api: ['src/api.js'],
1411
- ui: ['src/component.jsx', 'src/*.jsx'],
1412
- config: ['package.json', 'package-lock.json', '*.config.*'],
1413
- docs: ['README.md', '*.md']
1414
- }
1415
- };
1416
- res.end(JSON.stringify(defaultConfig));
1417
- }
1418
- } else if (req.method === 'POST') {
1419
- let body = '';
1420
- req.on('data', chunk => body += chunk);
1421
- req.on('end', () => {
1422
- try {
1423
- if (!this.isQuietMode) console.log('🔍 Received config save request');
1424
- const config = JSON.parse(body);
1425
- if (!this.isQuietMode) console.log('📝 Config to save:', JSON.stringify(config, null, 2));
1426
-
1427
- // Ensure .cntx directory exists
1428
- if (!existsSync(this.CNTX_DIR)) {
1429
- if (!this.isQuietMode) console.log('📁 Creating .cntx directory...');
1430
- mkdirSync(this.CNTX_DIR, { recursive: true });
1431
- }
1432
-
1433
- // Write config file
1434
- if (!this.isQuietMode) console.log('💾 Writing config to:', this.CONFIG_FILE);
1435
- writeFileSync(this.CONFIG_FILE, JSON.stringify(config, null, 2));
1436
- if (!this.isQuietMode) console.log('✅ Config file written successfully');
1437
-
1438
- // Reload configuration
1439
- this.loadConfig();
1440
- this.generateAllBundles();
1441
- this.broadcastUpdate();
1442
-
1443
- res.writeHead(200, { 'Content-Type': 'text/plain' });
1444
- res.end('OK');
1445
- if (!this.isQuietMode) console.log('✅ Config save response sent');
1446
-
1447
- } catch (e) {
1448
- if (!this.isQuietMode) console.error('❌ Config save error:', e);
1449
- res.writeHead(400, { 'Content-Type': 'text/plain' });
1450
- res.end(`Error: ${e.message}`);
1451
- }
1452
- });
1453
-
1454
- req.on('error', (err) => {
1455
- if (!this.isQuietMode) console.error('❌ Request error:', err);
1456
- if (!res.headersSent) {
1457
- res.writeHead(500, { 'Content-Type': 'text/plain' });
1458
- res.end('Internal Server Error');
1459
- }
1460
- });
1461
- }
1462
-
1463
- } else if (url.pathname === '/api/cursor-rules') {
1464
- if (req.method === 'GET') {
1465
- res.writeHead(200, { 'Content-Type': 'text/plain' });
1466
- const rules = this.loadCursorRules();
1467
- res.end(rules);
1468
- } else if (req.method === 'POST') {
1469
- let body = '';
1470
- req.on('data', chunk => body += chunk);
1471
- req.on('end', () => {
1472
- try {
1473
- const { content } = JSON.parse(body);
1474
- this.saveCursorRules(content);
1475
- res.writeHead(200);
1476
- res.end('OK');
1477
- } catch (e) {
1478
- res.writeHead(400);
1479
- res.end('Invalid request');
1480
- }
1481
- });
1482
- }
1483
-
1484
- } else if (url.pathname === '/api/cursor-rules/templates') {
1485
- res.writeHead(200, { 'Content-Type': 'application/json' });
1486
- const templates = {
1487
- react: this.generateCursorRulesTemplate({ name: 'My React App', description: 'React application', type: 'react' }),
1488
- node: this.generateCursorRulesTemplate({ name: 'My Node App', description: 'Node.js backend', type: 'node' }),
1489
- general: this.generateCursorRulesTemplate({ name: 'My Project', description: 'General project', type: 'general' })
549
+ const activityDir = join(activitiesPath, 'activities', activityId);
550
+
551
+ // Load markdown files
552
+ const files = {
553
+ readme: this.loadMarkdownFile(join(activityDir, 'README.md')),
554
+ progress: this.loadMarkdownFile(join(activityDir, 'progress.md')),
555
+ tasks: this.loadMarkdownFile(join(activityDir, 'tasks.md')),
556
+ notes: this.loadMarkdownFile(join(activityDir, 'notes.md'))
1490
557
  };
1491
- res.end(JSON.stringify(templates));
1492
-
1493
- } else if (url.pathname === '/api/claude-md') {
1494
- if (req.method === 'GET') {
1495
- res.writeHead(200, { 'Content-Type': 'text/plain' });
1496
- const claudeMd = this.loadClaudeMd();
1497
- res.end(claudeMd);
1498
- } else if (req.method === 'POST') {
1499
- let body = '';
1500
- req.on('data', chunk => body += chunk);
1501
- req.on('end', () => {
1502
- try {
1503
- const { content } = JSON.parse(body);
1504
- this.saveClaudeMd(content);
1505
- res.writeHead(200);
1506
- res.end('OK');
1507
- } catch (e) {
1508
- res.writeHead(400);
1509
- res.end('Invalid request');
1510
- }
1511
- });
1512
- }
1513
-
1514
- } else if (url.pathname === '/api/test-pattern') {
1515
- if (req.method === 'POST') {
1516
- let body = '';
1517
- req.on('data', chunk => body += chunk);
1518
- req.on('end', () => {
1519
- try {
1520
- const { pattern } = JSON.parse(body);
1521
- const allFiles = this.getAllFiles();
1522
- const matchingFiles = allFiles.filter(file =>
1523
- this.matchesPattern(file, pattern)
1524
- );
1525
-
1526
- res.writeHead(200, { 'Content-Type': 'application/json' });
1527
- res.end(JSON.stringify(matchingFiles));
1528
- } catch (e) {
1529
- res.writeHead(400);
1530
- res.end('Invalid request');
1531
- }
1532
- });
1533
- } else {
1534
- res.writeHead(405);
1535
- res.end('Method not allowed');
1536
- }
1537
-
1538
- } else if (url.pathname === '/api/hidden-files') {
1539
- if (req.method === 'GET') {
1540
- res.writeHead(200, { 'Content-Type': 'application/json' });
1541
- const stats = {
1542
- totalFiles: this.getAllFiles().length,
1543
- globallyHidden: this.hiddenFilesConfig.globalHidden.length,
1544
- bundleSpecificHidden: this.hiddenFilesConfig.bundleSpecific,
1545
- ignorePatterns: {
1546
- system: [
1547
- { pattern: '**/.git/**', active: !this.hiddenFilesConfig.disabledSystemPatterns.includes('**/.git/**') },
1548
- { pattern: '**/node_modules/**', active: !this.hiddenFilesConfig.disabledSystemPatterns.includes('**/node_modules/**') },
1549
- { pattern: '**/.cntx/**', active: !this.hiddenFilesConfig.disabledSystemPatterns.includes('**/.cntx/**') }
1550
- ],
1551
- user: this.hiddenFilesConfig.userIgnorePatterns,
1552
- disabled: this.hiddenFilesConfig.disabledSystemPatterns
1553
- }
1554
- };
1555
- res.end(JSON.stringify(stats));
1556
- } else if (req.method === 'POST') {
1557
- let body = '';
1558
- req.on('data', chunk => body += chunk);
1559
- req.on('end', () => {
1560
- try {
1561
- const { action, filePath, filePaths, bundleName, forceHide } = JSON.parse(body);
1562
-
1563
- if (action === 'toggle' && filePath) {
1564
- this.toggleFileVisibility(filePath, bundleName, forceHide);
1565
- } else if (action === 'bulk-toggle' && filePaths) {
1566
- this.bulkToggleFileVisibility(filePaths, bundleName, forceHide);
1567
- }
1568
-
1569
- this.generateAllBundles();
1570
- this.broadcastUpdate();
1571
-
1572
- res.writeHead(200, { 'Content-Type': 'application/json' });
1573
- res.end(JSON.stringify({ success: true }));
1574
- } catch (e) {
1575
- res.writeHead(400, { 'Content-Type': 'application/json' });
1576
- res.end(JSON.stringify({ error: e.message }));
1577
- }
1578
- });
1579
- }
1580
-
1581
- } else if (url.pathname === '/api/files-with-visibility') {
1582
- const bundleName = url.searchParams.get('bundle');
1583
- res.writeHead(200, { 'Content-Type': 'application/json' });
1584
- const files = this.getFileListWithVisibility(bundleName);
1585
- res.end(JSON.stringify(files));
1586
-
1587
- } else if (url.pathname === '/api/ignore-patterns') {
1588
- if (req.method === 'GET') {
1589
- res.writeHead(200, { 'Content-Type': 'application/json' });
1590
-
1591
- // Read file patterns
1592
- let filePatterns = [];
1593
- if (existsSync(this.IGNORE_FILE)) {
1594
- filePatterns = readFileSync(this.IGNORE_FILE, 'utf8')
1595
- .split('\n')
1596
- .map(line => line.trim())
1597
- .filter(line => line && !line.startsWith('#'));
1598
- }
1599
-
1600
- const systemPatterns = ['**/.git/**', '**/node_modules/**', '**/.cntx/**'];
1601
-
1602
- const patterns = {
1603
- system: systemPatterns.map(pattern => ({
1604
- pattern,
1605
- active: !this.hiddenFilesConfig.disabledSystemPatterns.includes(pattern)
1606
- })),
1607
- user: this.hiddenFilesConfig.userIgnorePatterns.map(pattern => ({ pattern, active: true })),
1608
- file: filePatterns.filter(pattern =>
1609
- !systemPatterns.includes(pattern) &&
1610
- !this.hiddenFilesConfig.userIgnorePatterns.includes(pattern)
1611
- ).map(pattern => ({ pattern, active: true }))
1612
- };
1613
- res.end(JSON.stringify(patterns));
1614
-
1615
- } else if (req.method === 'POST') {
1616
- let body = '';
1617
- req.on('data', chunk => body += chunk);
1618
- req.on('end', () => {
1619
- try {
1620
- const { action, pattern } = JSON.parse(body);
1621
- let success = false;
1622
-
1623
- switch (action) {
1624
- case 'add':
1625
- success = this.addUserIgnorePattern(pattern);
1626
- break;
1627
- case 'remove':
1628
- success = this.removeUserIgnorePattern(pattern);
1629
- break;
1630
- case 'toggle-system':
1631
- this.toggleSystemIgnorePattern(pattern);
1632
- success = true;
1633
- break;
1634
- }
1635
-
1636
- this.broadcastUpdate();
1637
- res.writeHead(200, { 'Content-Type': 'application/json' });
1638
- res.end(JSON.stringify({ success }));
1639
- } catch (e) {
1640
- res.writeHead(400, { 'Content-Type': 'application/json' });
1641
- res.end(JSON.stringify({ error: e.message }));
1642
- }
1643
- });
1644
- }
1645
-
1646
- } else if (url.pathname === '/api/bundle-visibility-stats') {
1647
- res.writeHead(200, { 'Content-Type': 'application/json' });
1648
- const stats = {};
1649
-
1650
- this.bundles.forEach((bundle, bundleName) => {
1651
- const allFiles = this.getAllFiles();
1652
- const matchingFiles = allFiles.filter(file =>
1653
- bundle.patterns.some(pattern => this.matchesPattern(file, pattern))
1654
- );
1655
-
1656
- const visibleFiles = matchingFiles.filter(file => !this.isFileHidden(file, bundleName));
1657
- const hiddenFiles = matchingFiles.length - visibleFiles.length;
1658
-
1659
- stats[bundleName] = {
1660
- total: matchingFiles.length,
1661
- visible: visibleFiles.length,
1662
- hidden: hiddenFiles,
1663
- patterns: bundle.patterns
1664
- };
1665
- });
1666
-
1667
- res.end(JSON.stringify(stats));
1668
-
1669
- } else if (url.pathname.startsWith('/api/bundle-categories/')) {
1670
- const bundleName = url.pathname.split('/').pop();
1671
- const bundle = this.bundles.get(bundleName);
1672
-
1673
- if (bundle) {
1674
- const filesByType = this.categorizeFiles(bundle.files);
1675
- const entryPoints = this.identifyEntryPoints(bundle.files);
1676
-
1677
- res.writeHead(200, { 'Content-Type': 'application/json' });
1678
- res.end(JSON.stringify({
1679
- purpose: this.getBundlePurpose(bundleName),
1680
- filesByType,
1681
- entryPoints,
1682
- totalFiles: bundle.files.length
1683
- }));
1684
- } else {
1685
- res.writeHead(404);
1686
- res.end('Bundle not found');
1687
- }
1688
558
 
1689
- } else if (url.pathname === '/api/reset-hidden-files') {
1690
- if (req.method === 'POST') {
1691
- let body = '';
1692
- req.on('data', chunk => body += chunk);
1693
- req.on('end', () => {
1694
- try {
1695
- const { scope, bundleName } = JSON.parse(body);
1696
-
1697
- if (scope === 'global') {
1698
- this.hiddenFilesConfig.globalHidden = [];
1699
- } else if (scope === 'bundle' && bundleName) {
1700
- delete this.hiddenFilesConfig.bundleSpecific[bundleName];
1701
- } else if (scope === 'all') {
1702
- this.hiddenFilesConfig.globalHidden = [];
1703
- this.hiddenFilesConfig.bundleSpecific = {};
1704
- }
1705
-
1706
- this.saveHiddenFilesConfig();
1707
- this.generateAllBundles();
1708
- this.broadcastUpdate();
1709
-
1710
- res.writeHead(200, { 'Content-Type': 'application/json' });
1711
- res.end(JSON.stringify({ success: true }));
1712
- } catch (e) {
1713
- res.writeHead(400, { 'Content-Type': 'application/json' });
1714
- res.end(JSON.stringify({ error: e.message }));
1715
- }
1716
- });
1717
- }
1718
-
1719
- } else if (url.pathname === '/api/mcp-status') {
1720
- res.writeHead(200, { 'Content-Type': 'application/json' });
1721
-
1722
- // Simple check - MCP is available if we can find package.json
1723
- let isAccessible = true;
1724
- let testResult = 'available';
1725
-
1726
- // Check if package.json exists using existing imports
1727
- try {
1728
- const packagePath = join(this.CWD, 'package.json');
1729
- if (existsSync(packagePath)) {
1730
- testResult = 'local_package_found';
1731
- } else {
1732
- testResult = 'using_global_npx';
1733
- }
1734
- } catch (error) {
1735
- testResult = 'check_failed';
1736
- }
559
+ // Calculate progress from progress.md file
560
+ const progress = this.parseProgressFromMarkdown(files.progress);
1737
561
 
1738
- const mcpStatus = {
1739
- running: isAccessible,
1740
- accessible: isAccessible,
1741
- testResult: testResult,
1742
- command: 'npx cntx-ui mcp',
1743
- workingDirectory: this.CWD,
1744
- lastChecked: new Date().toISOString(),
1745
- trackingEnabled: this.mcpServerStarted || false
1746
- };
1747
- res.end(JSON.stringify(mcpStatus, null, 2));
1748
-
1749
- } else if (url.pathname === '/api/status') {
1750
- res.writeHead(200, { 'Content-Type': 'application/json' });
1751
- const statusInfo = {
1752
- server: {
1753
- version: '2.0.8',
1754
- workingDirectory: this.CWD,
1755
- startTime: new Date().toISOString(),
1756
- isScanning: this.isScanning
1757
- },
1758
- bundles: {
1759
- count: this.bundles.size,
1760
- names: Array.from(this.bundles.keys()),
1761
- totalFiles: Array.from(this.bundles.values()).reduce((sum, bundle) => sum + bundle.files.length, 0)
1762
- },
1763
- mcp: {
1764
- available: true,
1765
- serverStarted: this.mcpServerStarted,
1766
- command: 'npx cntx-ui mcp',
1767
- setupScript: './examples/claude-mcp-setup.sh'
1768
- },
1769
- files: {
1770
- total: this.getAllFiles().length,
1771
- hiddenGlobally: this.hiddenFilesConfig.globalHidden.length,
1772
- ignorePatterns: this.ignorePatterns.length
1773
- }
562
+ return {
563
+ id: activityId,
564
+ name: activity.title,
565
+ description: activity.description,
566
+ status: activity.status === 'todo' ? 'pending' : activity.status,
567
+ priority: activity.tags?.includes('high') ? 'high' : activity.tags?.includes('low') ? 'low' : 'medium',
568
+ progress,
569
+ updatedAt: new Date().toISOString(),
570
+ category: activity.tags?.[0] || 'general',
571
+ files,
572
+ tags: activity.tags
1774
573
  };
1775
- res.end(JSON.stringify(statusInfo, null, 2));
574
+ });
575
+ } catch (error) {
576
+ console.error('Failed to load activities:', error);
577
+ return [];
578
+ }
579
+ }
1776
580
 
1777
- } else {
1778
- res.writeHead(404);
1779
- res.end('Not found');
581
+ loadMarkdownFile(filePath) {
582
+ try {
583
+ if (fs.existsSync(filePath)) {
584
+ return fs.readFileSync(filePath, 'utf8');
1780
585
  }
1781
- });
1782
-
1783
- const wss = new WebSocketServer({ server });
1784
- wss.on('connection', (ws) => {
1785
- this.clients.add(ws);
1786
- ws.on('close', () => this.clients.delete(ws));
1787
- this.sendUpdate(ws);
1788
- });
586
+ return 'No content available';
587
+ } catch (error) {
588
+ return `Error loading file: ${error.message}`;
589
+ }
590
+ }
1789
591
 
1790
- server.listen(port, () => {
1791
- if (!this.isQuietMode) {
1792
- console.log(`🚀 cntx-ui API running at http://localhost:${port}`);
1793
- console.log(`📁 Watching: ${this.CWD}`);
1794
- console.log(`📦 Bundles: ${Array.from(this.bundles.keys()).join(', ')}`);
592
+ parseProgressFromMarkdown(progressContent) {
593
+ try {
594
+ if (!progressContent || progressContent === 'No content available') {
595
+ return 0;
1795
596
  }
1796
- });
1797
597
 
1798
- return server;
1799
- }
598
+ // Look for "Overall Completion: XX%" pattern
599
+ const overallMatch = progressContent.match(/(?:Overall Completion|Progress):\s*(\d+)%/i);
600
+ if (overallMatch) {
601
+ return parseInt(overallMatch[1], 10);
602
+ }
1800
603
 
1801
- broadcastUpdate() {
1802
- this.clients.forEach(client => this.sendUpdate(client));
1803
- }
604
+ // Fallback: count completed tasks vs total tasks in checkbox format
605
+ const taskMatches = progressContent.match(/- \[([x✓✅\s])\]/gi);
606
+ if (taskMatches && taskMatches.length > 0) {
607
+ const completedTasks = taskMatches.filter(match =>
608
+ match.includes('[x]') || match.includes('[✓]') || match.includes('[✅]')
609
+ ).length;
610
+ return Math.round((completedTasks / taskMatches.length) * 100);
611
+ }
1804
612
 
1805
- sendUpdate(client) {
1806
- if (client.readyState === 1) {
1807
- const bundleData = Array.from(this.bundles.entries()).map(([name, bundle]) => ({
1808
- name,
1809
- changed: bundle.changed,
1810
- fileCount: bundle.files.length,
1811
- content: bundle.content.substring(0, 2000) + (bundle.content.length > 2000 ? '...' : ''),
1812
- files: bundle.files,
1813
- lastGenerated: bundle.lastGenerated,
1814
- size: bundle.size
1815
- }));
1816
- client.send(JSON.stringify(bundleData));
613
+ return 0;
614
+ } catch (error) {
615
+ console.error('Error parsing progress:', error);
616
+ return 0;
1817
617
  }
1818
618
  }
1819
619
 
1820
- cleanup() {
1821
- this.watchers.forEach(watcher => watcher.close());
1822
- this.saveBundleStates();
620
+ async executeActivity(activityId) {
621
+ // Placeholder - would execute specific activity
622
+ return { success: false, message: 'Activity execution not implemented' };
1823
623
  }
1824
624
 
1825
- // Semantic Chunking Methods
1826
- async getSemanticAnalysis() {
1827
- // Force refresh always for now (TODO: implement proper cache invalidation)
1828
- this.semanticCache = null // Clear cache
1829
- const shouldRefresh = true
1830
-
1831
- console.log('🔍 Cache check - shouldRefresh:', shouldRefresh, 'lastAnalysis:', this.lastSemanticAnalysis, 'now:', Date.now());
1832
-
1833
- if (shouldRefresh) {
1834
- try {
1835
- // Auto-discover JavaScript/TypeScript files in the entire project
1836
- const patterns = ['**/*.{js,jsx,ts,tsx,mjs}'];
1837
-
1838
- // Load bundle configuration for chunk grouping
1839
- let bundleConfig = null;
1840
- if (existsSync(this.CONFIG_FILE)) {
1841
- bundleConfig = JSON.parse(readFileSync(this.CONFIG_FILE, 'utf8'));
1842
- }
1843
-
1844
- this.semanticCache = await this.semanticSplitter.extractSemanticChunks(this.CWD, patterns, bundleConfig);
1845
- this.lastSemanticAnalysis = Date.now();
1846
-
1847
- // Debug logging
1848
- console.log('🔍 Semantic analysis complete. Sample chunk keys:',
1849
- this.semanticCache.chunks.length > 0 ? Object.keys(this.semanticCache.chunks[0]) : 'No chunks');
1850
- if (this.semanticCache.chunks.length > 0) {
1851
- console.log('🔍 Sample chunk businessDomains:', this.semanticCache.chunks[0].businessDomains);
1852
- }
1853
- } catch (error) {
1854
- console.error('Semantic analysis failed:', error.message);
1855
- throw new Error(`Semantic analysis failed: ${error.message}`);
1856
- }
1857
- }
1858
-
1859
- return this.semanticCache;
625
+ async stopActivity(activityId) {
626
+ // Placeholder - would stop running activity
627
+ return { success: false, message: 'Activity stopping not implemented' };
1860
628
  }
1861
629
 
1862
- async exportSemanticChunk(chunkName) {
1863
- const analysis = await this.getSemanticAnalysis();
1864
- const chunk = analysis.chunks.find(c => c.name === chunkName);
630
+ // === MCP Server Integration ===
1865
631
 
1866
- if (!chunk) {
1867
- throw new Error(`Chunk "${chunkName}" not found`);
1868
- }
632
+ startMCPServer() {
633
+ if (!this.mcpServer) {
634
+ this.mcpServer = new MCPServer(this);
635
+ this.mcpServerStarted = true;
636
+ this.apiRouter.mcpServerStarted = true;
1869
637
 
1870
- // Generate XML content for the chunk files
1871
- let xmlContent = `<?xml version="1.0" encoding="UTF-8"?>\n`;
1872
- xmlContent += `<codebase_context semantic_chunk="${chunkName}">\n`;
1873
- xmlContent += ` <chunk_info>\n`;
1874
- xmlContent += ` <name>${chunkName}</name>\n`;
1875
- xmlContent += ` <purpose>${chunk.purpose || 'No description'}</purpose>\n`;
1876
- xmlContent += ` <file_count>${chunk.files.length}</file_count>\n`;
1877
- xmlContent += ` <size>${chunk.size} bytes</size>\n`;
1878
- xmlContent += ` <complexity>${chunk.complexity?.level || 'unknown'}</complexity>\n`;
1879
- xmlContent += ` <tags>${(chunk.tags || []).join(', ')}</tags>\n`;
1880
- xmlContent += ` </chunk_info>\n\n`;
1881
-
1882
- // Add each function in the chunk
1883
- if (chunk.functions) {
1884
- for (const func of chunk.functions) {
1885
- xmlContent += ` <function name="${func.name}" file="${func.filePath}">\n`;
1886
- xmlContent += ` <signature>${func.signature}</signature>\n`;
1887
- xmlContent += ` <type>${func.type}</type>\n`;
1888
- xmlContent += ` <lines>${func.startLine}-${func.endLine}</lines>\n`;
1889
-
1890
- if (func.context.imports.length > 0) {
1891
- xmlContent += ` <imports>${func.context.imports.join(', ')}</imports>\n`;
1892
- }
1893
-
1894
- xmlContent += ` <code>\n`;
1895
- xmlContent += func.code
1896
- .split('\n')
1897
- .map((line, i) => `${(func.startLine + i).toString().padStart(3)} ${line}`)
1898
- .join('\n');
1899
- xmlContent += `\n </code>\n`;
1900
- xmlContent += ` </function>\n\n`;
1901
- }
1902
- } else {
1903
- // Fallback for file-based chunks
1904
- for (const filePath of chunk.files || []) {
1905
- const fullPath = join(this.CWD, filePath);
1906
- if (existsSync(fullPath)) {
1907
- try {
1908
- const content = readFileSync(fullPath, 'utf8');
1909
- xmlContent += ` <file path="${filePath}">\n`;
1910
- xmlContent += content
1911
- .split('\n')
1912
- .map((line, i) => `${(i + 1).toString().padStart(3)} ${line}`)
1913
- .join('\n');
1914
- xmlContent += `\n </file>\n\n`;
1915
- } catch (error) {
1916
- console.warn(`Could not read file ${filePath}:`, error.message);
1917
- }
1918
- }
638
+ if (this.verbose) {
639
+ console.log('🔗 MCP server started');
1919
640
  }
1920
641
  }
1921
-
1922
- xmlContent += `</codebase_context>`;
1923
- return xmlContent;
1924
642
  }
1925
643
 
1926
- async createBundleFromChunk(chunkName, files) {
1927
- // Load current config
1928
- let config = {};
1929
- if (existsSync(this.CONFIG_FILE)) {
1930
- config = JSON.parse(readFileSync(this.CONFIG_FILE, 'utf8'));
1931
- }
644
+ // === Server Lifecycle ===
1932
645
 
1933
- if (!config.bundles) {
1934
- config.bundles = {};
1935
- }
646
+ async listen(port = 3333, host = 'localhost') {
647
+ const server = createServer((req, res) => {
648
+ this.handleRequest(req, res);
649
+ });
1936
650
 
1937
- // Create bundle with the chunk name and files
1938
- const bundleName = chunkName.toLowerCase().replace(/[-\s]+/g, '-');
1939
- config.bundles[bundleName] = files;
651
+ // Initialize WebSocket server
652
+ this.webSocketManager.initialize(server);
1940
653
 
1941
- // Save config
1942
- writeFileSync(this.CONFIG_FILE, JSON.stringify(config, null, 2));
654
+ // Start server and show progress
655
+ server.listen(port, host, () => {
656
+ console.log('');
657
+ console.log(`🌐 Server running at http://${host}:${port}`);
658
+ console.log(`📊 Serving ${this.bundleManager.getAllBundleInfo().length} bundles from your project`);
659
+ console.log('');
1943
660
 
1944
- // Reload bundles
1945
- this.loadConfig();
1946
- this.generateAllBundles();
1947
- this.saveBundleStates();
1948
- this.broadcastUpdate();
1949
- }
661
+ // Display initialization summary
662
+ this.displayInitSummary();
663
+ });
1950
664
 
1951
- invalidateSemanticCache() {
1952
- this.semanticCache = null;
1953
- this.lastSemanticAnalysis = null;
665
+ // Handle graceful shutdown
666
+ process.on('SIGINT', () => {
667
+ console.log('\n🛑 Shutting down server...');
668
+ this.webSocketManager.close();
669
+ this.fileSystemManager.destroy();
670
+ server.close(() => {
671
+ console.log('✅ Server stopped');
672
+ process.exit(0);
673
+ });
674
+ });
675
+
676
+ return server;
1954
677
  }
1955
678
  }
1956
679
 
1957
- export function startServer(options = {}) {
1958
- const server = new CntxServer(options.cwd, { quiet: options.quiet });
1959
- server.init();
680
+ // Export function for CLI compatibility
681
+ export async function startServer(options = {}) {
682
+ const server = new CntxServer(options.cwd, { verbose: options.verbose });
683
+
684
+ // Show ASCII art first
685
+ const asciiArt = `
686
+ ██████ ███ ██ ████████ ██ ██ ██ ██ ██
687
+ ██ ████ ██ ██ ██ ██ ██ ██ ██
688
+ ██ ██ ██ ██ ██ ███ █████ ██ ██ ██
689
+ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
690
+ ██████ ██ ████ ██ ██ ██ ██████ ██
691
+ `;
692
+ console.log(asciiArt);
693
+ console.log(''); // Add blank line after art
694
+
695
+ // Now start initialization with progress bar
696
+ await server.init();
1960
697
 
1961
698
  if (options.withMcp) {
1962
- server.mcpServerStarted = true;
1963
- if (!server.isQuietMode) {
1964
- console.log('🔗 MCP server tracking enabled - use /api/status to check MCP configuration');
1965
- }
699
+ server.startMCPServer();
1966
700
  }
1967
701
 
1968
- return server.startServer(options.port);
702
+ return await server.listen(options.port, options.host);
1969
703
  }
1970
704
 
1971
- export function startMCPServer(options = {}) {
1972
- const server = new CntxServer(options.cwd, { quiet: true });
1973
- server.init();
1974
- startMCPTransport(server);
1975
- return server;
1976
- }
705
+ // CLI Functions for backward compatibility
706
+ export async function startMCPServer(options = {}) {
707
+ const server = new CntxServer(options.cwd, { verbose: true });
708
+ await server.init();
709
+ server.startMCPServer();
1977
710
 
1978
- export function generateBundle(name = 'master', cwd = process.cwd(), options = {}) {
1979
- const server = new CntxServer(cwd, { quiet: options.quiet });
1980
- server.init();
1981
- server.generateBundle(name);
1982
- server.saveBundleStates();
711
+ // For MCP mode, we don't start the web server, just keep the process alive
712
+ console.log('🔗 MCP server running on stdio...');
1983
713
  }
1984
714
 
1985
- export function initConfig(cwd = process.cwd(), options = {}) {
1986
- const isQuiet = options.quiet || false;
1987
- if (!isQuiet) {
1988
- console.log('🚀 Starting initConfig...');
1989
- console.log('📂 Working directory:', cwd);
1990
- }
715
+ export async function generateBundle(bundleName = 'master') {
716
+ const server = new CntxServer(process.cwd(), { verbose: true });
717
+ await server.init({ skipFileWatcher: true });
718
+
719
+ await server.bundleManager.regenerateBundle(bundleName);
720
+ const bundleInfo = server.bundleManager.getBundleInfo(bundleName);
1991
721
 
1992
- const server = new CntxServer(cwd, { quiet: isQuiet });
1993
- if (!isQuiet) {
1994
- console.log('📁 CNTX_DIR:', server.CNTX_DIR);
1995
- console.log('📄 CONFIG_FILE path:', server.CONFIG_FILE);
722
+ if (!bundleInfo) {
723
+ throw new Error(`Bundle '${bundleName}' not found`);
1996
724
  }
1997
725
 
1998
- const defaultConfig = {
1999
- bundles: {
2000
- master: ['**/*']
2001
- }
2002
- };
726
+ return bundleInfo;
727
+ }
2003
728
 
2004
- try {
2005
- // Create .cntx directory
2006
- if (!isQuiet) console.log('🔍 Checking if .cntx directory exists...');
2007
- if (!existsSync(server.CNTX_DIR)) {
2008
- if (!isQuiet) console.log('📁 Creating .cntx directory...');
2009
- mkdirSync(server.CNTX_DIR, { recursive: true });
2010
- if (!isQuiet) console.log('✅ .cntx directory created');
2011
- } else {
2012
- if (!isQuiet) console.log('✅ .cntx directory already exists');
2013
- }
729
+ export async function initConfig() {
730
+ const server = new CntxServer(process.cwd(), { verbose: false });
731
+ const templateDir = join(__dirname, 'templates');
2014
732
 
2015
- // List directory contents before writing config
2016
- if (!isQuiet) {
2017
- console.log('📋 Directory contents before writing config:');
2018
- const beforeFiles = readdirSync(server.CNTX_DIR);
2019
- console.log('Files:', beforeFiles);
2020
- }
733
+ // Initialize directory structure
734
+ if (!existsSync(server.CNTX_DIR)) {
735
+ mkdirSync(server.CNTX_DIR, { recursive: true });
736
+ console.log('📁 Created .cntx directory');
737
+ }
2021
738
 
2022
- // Write config.json
2023
- if (!isQuiet) {
2024
- console.log('📝 Writing config.json...');
2025
- console.log('📄 Config content:', JSON.stringify(defaultConfig, null, 2));
2026
- console.log('📍 Writing to path:', server.CONFIG_FILE);
739
+ // Initialize basic configuration
740
+ server.configManager.loadConfig();
741
+ server.configManager.saveConfig({
742
+ bundles: {
743
+ master: ['**/*']
2027
744
  }
745
+ });
2028
746
 
2029
- writeFileSync(server.CONFIG_FILE, JSON.stringify(defaultConfig, null, 2));
2030
- if (!isQuiet) console.log('✅ writeFileSync completed');
747
+ console.log('⚙️ Basic configuration initialized');
2031
748
 
2032
- // Verify file was created
2033
- if (!isQuiet) {
2034
- console.log('🔍 Checking if config.json exists...');
2035
- const configExists = existsSync(server.CONFIG_FILE);
2036
- console.log('Config exists?', configExists);
749
+ // Copy agent configuration files
750
+ const agentFiles = [
751
+ 'agent-config.yaml',
752
+ 'agent-instructions.md'
753
+ ];
2037
754
 
2038
- if (configExists) {
2039
- const configContent = readFileSync(server.CONFIG_FILE, 'utf8');
2040
- console.log('✅ Config file created successfully');
2041
- console.log('📖 Config content:', configContent);
2042
- } else {
2043
- console.log('❌ Config file was NOT created');
2044
- }
755
+ for (const file of agentFiles) {
756
+ const sourcePath = join(templateDir, file);
757
+ const destPath = join(server.CNTX_DIR, file);
758
+
759
+ if (existsSync(sourcePath) && !existsSync(destPath)) {
760
+ copyFileSync(sourcePath, destPath);
761
+ console.log(`📄 Created ${file}`);
762
+ }
763
+ }
764
+
765
+ // Copy agent-rules directory structure
766
+ const agentRulesSource = join(templateDir, 'agent-rules');
767
+ const agentRulesDest = join(server.CNTX_DIR, 'agent-rules');
768
+
769
+ if (existsSync(agentRulesSource) && !existsSync(agentRulesDest)) {
770
+ cpSync(agentRulesSource, agentRulesDest, { recursive: true });
771
+ console.log('📁 Created agent-rules directory with templates');
772
+ }
773
+
774
+ // Copy activities framework
775
+ const activitiesDir = join(server.CNTX_DIR, 'activities');
776
+ if (!existsSync(activitiesDir)) {
777
+ mkdirSync(activitiesDir, { recursive: true });
778
+ }
779
+
780
+ // Copy activities README
781
+ const activitiesReadmeSource = join(templateDir, 'activities', 'README.md');
782
+ const activitiesReadmeDest = join(activitiesDir, 'README.md');
783
+
784
+ if (existsSync(activitiesReadmeSource) && !existsSync(activitiesReadmeDest)) {
785
+ copyFileSync(activitiesReadmeSource, activitiesReadmeDest);
786
+ console.log('📄 Created activities/README.md');
787
+ }
788
+
789
+ // Copy activities lib directory (MDC templates)
790
+ const activitiesLibSource = join(templateDir, 'activities', 'lib');
791
+ const activitiesLibDest = join(activitiesDir, 'lib');
792
+
793
+ if (existsSync(activitiesLibSource) && !existsSync(activitiesLibDest)) {
794
+ cpSync(activitiesLibSource, activitiesLibDest, { recursive: true });
795
+ console.log('📁 Created activities/lib with MDC templates');
796
+ }
797
+
798
+ // Copy activities.json from templates
799
+ const activitiesJsonPath = join(activitiesDir, 'activities.json');
800
+ const templateActivitiesJsonPath = join(templateDir, 'activities', 'activities.json');
801
+ if (!existsSync(activitiesJsonPath) && existsSync(templateActivitiesJsonPath)) {
802
+ copyFileSync(templateActivitiesJsonPath, activitiesJsonPath);
803
+ console.log('📄 Created activities.json with bundle example activity');
804
+ }
805
+
806
+ // Copy example activity from templates
807
+ const activitiesDestDir = join(activitiesDir, 'activities');
808
+ const templateActivitiesDir = join(templateDir, 'activities', 'activities');
809
+ if (!existsSync(activitiesDestDir) && existsSync(templateActivitiesDir)) {
810
+ cpSync(templateActivitiesDir, activitiesDestDir, { recursive: true });
811
+ console.log('📁 Created example activity with templates');
812
+ }
813
+
814
+ console.log('');
815
+ console.log('🎉 cntx-ui initialized with full scaffolding!');
816
+ console.log('');
817
+ console.log('Next steps:');
818
+ console.log(' 1️⃣ Start the server: cntx-ui watch');
819
+ console.log(' 2️⃣ Open web UI: http://localhost:3333');
820
+ console.log(' 3️⃣ Read .cntx/agent-instructions.md for AI integration');
821
+ console.log(' 4️⃣ Explore .cntx/activities/README.md for project management');
822
+ console.log('');
823
+ console.log('💡 Pro tip: Use "cntx-ui status" to see your project overview');
824
+ }
2045
825
 
2046
- // List directory contents after writing config
2047
- console.log('📋 Directory contents after writing config:');
2048
- const afterFiles = readdirSync(server.CNTX_DIR);
2049
- console.log('Files:', afterFiles);
2050
- }
826
+ export async function getStatus() {
827
+ const server = new CntxServer(process.cwd(), { verbose: true });
828
+ await server.init({ skipFileWatcher: true });
2051
829
 
2052
- } catch (error) {
2053
- if (!isQuiet) {
2054
- console.error('❌ Error in initConfig:', error);
2055
- console.error('Stack trace:', error.stack);
2056
- }
2057
- throw error;
2058
- }
830
+ const bundles = server.bundleManager.getAllBundleInfo();
831
+ const totalFiles = server.fileSystemManager.getAllFiles().length;
2059
832
 
2060
- // Create cursor rules if they don't exist
2061
- try {
2062
- if (!existsSync(server.CURSOR_RULES_FILE)) {
2063
- if (!isQuiet) console.log('📋 Creating cursor rules...');
2064
- const cursorRules = server.getDefaultCursorRules();
2065
- server.saveCursorRules(cursorRules);
2066
- if (!isQuiet) console.log(`📋 Created ${relative(cwd, server.CURSOR_RULES_FILE)} with project-specific rules`);
2067
- }
2068
- } catch (error) {
2069
- if (!isQuiet) console.error('❌ Error creating cursor rules:', error);
2070
- }
833
+ console.log('📊 cntx-ui Status');
834
+ console.log('================');
835
+ console.log(`Total files: ${totalFiles}`);
836
+ console.log(`Bundles: ${bundles.length}`);
2071
837
 
2072
- if (!isQuiet) {
2073
- console.log('✅ cntx-ui initialized successfully!');
2074
- console.log('');
2075
- console.log('🚀 Next step: Start the web interface');
2076
- console.log(' Run: cntx-ui watch');
2077
- console.log('');
2078
- console.log('📱 Then visit: http://localhost:3333');
2079
- console.log(' Follow the setup guide to create your first bundles');
2080
- console.log('');
2081
- console.log('💡 The web interface handles everything - no manual file editing needed!');
2082
- }
2083
- }
838
+ bundles.forEach(bundle => {
839
+ console.log(` • ${bundle.name}: ${bundle.fileCount} files (${Math.round(bundle.size / 1024)}KB)`);
840
+ });
2084
841
 
2085
- export function getStatus(cwd = process.cwd(), options = {}) {
2086
- const server = new CntxServer(cwd, { quiet: options.quiet });
2087
- server.init();
2088
-
2089
- if (!options.quiet) {
2090
- console.log(`📁 Working directory: ${server.CWD}`);
2091
- console.log(`📦 Bundles configured: ${server.bundles.size}`);
2092
- server.bundles.forEach((bundle, name) => {
2093
- const status = bundle.changed ? '🔄 CHANGED' : '✅ SYNCED';
2094
- console.log(` ${name}: ${bundle.files.length} files ${status}`);
2095
- });
2096
-
2097
- const hasCursorRules = existsSync(server.CURSOR_RULES_FILE);
2098
- console.log(`🤖 Cursor rules: ${hasCursorRules ? '✅ Configured' : '❌ Not found'}`);
2099
- }
842
+ return {
843
+ totalFiles,
844
+ bundles: bundles.length,
845
+ bundleDetails: bundles
846
+ };
2100
847
  }
2101
848
 
2102
- export function setupMCP(cwd = process.cwd(), options = {}) {
2103
- const isQuiet = options.quiet || false;
2104
- const projectDir = cwd;
2105
- const projectName = basename(projectDir);
2106
- const configFile = join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
849
+ export function setupMCP() {
850
+ const configPath = join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
851
+ const projectPath = process.cwd();
2107
852
 
2108
- if (!isQuiet) {
2109
- console.log('🔗 Setting up MCP for Claude Desktop...');
2110
- console.log(`📁 Project: ${projectName} (${projectDir})`);
2111
- }
853
+ console.log('🔧 Setting up MCP integration...');
854
+ console.log(`Project: ${projectPath}`);
855
+ console.log(`Claude config: ${configPath}`);
2112
856
 
2113
- // Create config directory if it doesn't exist
2114
- const configDir = dirname(configFile);
2115
- if (!existsSync(configDir)) {
2116
- mkdirSync(configDir, { recursive: true });
2117
- }
2118
-
2119
- // Read existing config or create empty one
2120
- let config = { mcpServers: {} };
2121
- if (existsSync(configFile)) {
2122
- try {
2123
- const configContent = readFileSync(configFile, 'utf8');
2124
- config = JSON.parse(configContent);
2125
- if (!config.mcpServers) config.mcpServers = {};
2126
- } catch (error) {
2127
- if (!isQuiet) console.warn('⚠️ Could not parse existing config, creating new one');
2128
- config = { mcpServers: {} };
857
+ try {
858
+ let config = {};
859
+ if (existsSync(configPath)) {
860
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
2129
861
  }
2130
- }
2131
862
 
2132
- // Add this project's MCP server using shell command format that works with Claude Desktop
2133
- const serverName = `cntx-ui-${projectName}`;
2134
- config.mcpServers[serverName] = {
2135
- command: 'sh',
2136
- args: ['-c', `cd ${projectDir} && npx cntx-ui mcp`],
2137
- cwd: projectDir
2138
- };
2139
-
2140
- // Write updated config
2141
- try {
2142
- writeFileSync(configFile, JSON.stringify(config, null, 2));
863
+ if (!config.mcpServers) {
864
+ config.mcpServers = {};
865
+ }
2143
866
 
2144
- if (!isQuiet) {
2145
- console.log(`✅ Added MCP server: ${serverName}`);
2146
- console.log('📋 Your Claude Desktop config now includes:');
867
+ config.mcpServers['cntx-ui'] = {
868
+ command: 'node',
869
+ args: [join(projectPath, 'bin', 'cntx-ui.js'), 'mcp'],
870
+ env: {}
871
+ };
2147
872
 
2148
- Object.keys(config.mcpServers).forEach(name => {
2149
- if (name.startsWith('cntx-ui-')) {
2150
- console.log(` • ${name}: ${config.mcpServers[name].cwd}`);
2151
- }
2152
- });
873
+ // Ensure directory exists
874
+ mkdirSync(dirname(configPath), { recursive: true });
875
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
2153
876
 
2154
- console.log('🔄 Please restart Claude Desktop to use the updated configuration');
2155
- }
877
+ console.log(' MCP integration configured');
878
+ console.log('💡 Restart Claude Desktop to apply changes');
2156
879
  } catch (error) {
2157
- if (!isQuiet) {
2158
- console.error(' Error writing Claude Desktop config:', error.message);
2159
- console.error('💡 Make sure Claude Desktop is not running and try again');
2160
- }
2161
- throw error;
880
+ console.error('❌ Failed to setup MCP:', error.message);
881
+ console.log('💡 You may need to manually add the configuration to Claude Desktop');
2162
882
  }
2163
883
  }
884
+
885
+ // Auto-start server when run directly
886
+ const isMainModule = import.meta.url === `file://${process.argv[1]}`;
887
+ if (isMainModule) {
888
+ console.log('🚀 Starting cntx-ui server...');
889
+ const server = new CntxServer();
890
+ server.init();
891
+ server.listen(3333, 'localhost');
892
+ }