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