cntx-ui 2.0.13 → 3.0.0

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