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.
- package/README.md +51 -339
- package/VISION.md +110 -0
- package/bin/cntx-ui-mcp.sh +3 -0
- package/bin/cntx-ui.js +138 -55
- package/lib/agent-runtime.js +301 -0
- package/lib/agent-tools.js +370 -0
- package/lib/api-router.js +1161 -0
- package/lib/bundle-manager.js +236 -0
- package/lib/configuration-manager.js +760 -0
- package/lib/database-manager.js +397 -0
- package/lib/file-system-manager.js +489 -0
- package/lib/heuristics-manager.js +527 -0
- package/lib/mcp-server.js +1125 -2
- package/lib/semantic-splitter.js +225 -491
- package/lib/simple-vector-store.js +98 -0
- package/lib/websocket-manager.js +470 -0
- package/package.json +19 -25
- package/server.js +742 -1935
- package/templates/TOOLS.md +41 -0
- package/templates/activities/README.md +67 -0
- package/templates/activities/activities/create-project-bundles/README.md +84 -0
- package/templates/activities/activities/create-project-bundles/notes.md +98 -0
- package/templates/activities/activities/create-project-bundles/progress.md +63 -0
- package/templates/activities/activities/create-project-bundles/tasks.md +39 -0
- package/templates/activities/activities.json +219 -0
- package/templates/activities/lib/.markdownlint.jsonc +18 -0
- package/templates/activities/lib/create-activity.mdc +63 -0
- package/templates/activities/lib/generate-tasks.mdc +64 -0
- package/templates/activities/lib/process-task-list.mdc +52 -0
- package/templates/agent-config.yaml +65 -0
- package/templates/agent-instructions.md +234 -0
- package/templates/agent-rules/capabilities/activities-system.md +147 -0
- package/templates/agent-rules/capabilities/bundle-system.md +131 -0
- package/templates/agent-rules/capabilities/vector-search.md +135 -0
- package/templates/agent-rules/core/codebase-navigation.md +91 -0
- package/templates/agent-rules/core/performance-hierarchy.md +48 -0
- package/templates/agent-rules/core/response-formatting.md +120 -0
- package/templates/agent-rules/project-specific/architecture.md +145 -0
- package/templates/config.json +76 -0
- package/templates/hidden-files.json +14 -0
- package/web/dist/assets/index-B2OdTzzI.css +1 -0
- package/web/dist/assets/index-D0tBsKiR.js +2016 -0
- package/web/dist/cntx-ui.svg +18 -0
- package/web/dist/index.html +25 -8
- package/lib/semantic-integration.js +0 -441
- package/mcp-config-example.json +0 -9
- package/web/dist/assets/index-Ci1Q-YrQ.js +0 -611
- package/web/dist/assets/index-IUp4q_fr.css +0 -1
- package/web/dist/vite.svg +0 -21
package/server.js
CHANGED
|
@@ -1,17 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Refactored cntx-ui Server
|
|
3
|
+
* Lean orchestration layer using modular architecture
|
|
4
|
+
*/
|
|
5
|
+
|
|
3
6
|
import { createServer } from 'http';
|
|
4
|
-
import {
|
|
7
|
+
import { join, dirname } from 'path';
|
|
5
8
|
import { fileURLToPath } from 'url';
|
|
6
|
-
import
|
|
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
|
|
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 =
|
|
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.
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
68
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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 (
|
|
151
|
-
|
|
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
|
-
|
|
166
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
138
|
+
// Single progress bar for initialization
|
|
139
|
+
async showInitProgress(steps) {
|
|
140
|
+
const totalSteps = steps.length;
|
|
141
|
+
let currentStep = 0;
|
|
194
142
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
219
|
-
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
156
|
+
return {
|
|
157
|
+
next: (stepName, minTime = 800) => {
|
|
158
|
+
return new Promise(async (resolve) => {
|
|
159
|
+
const startTime = Date.now();
|
|
232
160
|
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
}
|
|
358
|
-
|
|
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
|
-
//
|
|
376
|
-
|
|
377
|
-
if (
|
|
378
|
-
|
|
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
|
-
|
|
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
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
|
|
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
|
-
|
|
405
|
-
|
|
232
|
+
if (!skipFileWatcher) {
|
|
233
|
+
// Step 4: Starting file watcher
|
|
234
|
+
this.startWatching();
|
|
235
|
+
await progress.next(steps[3], 600);
|
|
406
236
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
-
|
|
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
|
-
|
|
419
|
-
|
|
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
|
-
|
|
252
|
+
// Generate Agent Manifest
|
|
253
|
+
await this.agentRuntime.generateAgentManifest();
|
|
569
254
|
}
|
|
570
255
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
256
|
+
// Display initialization summary
|
|
257
|
+
displayInitSummary() {
|
|
258
|
+
const summary = [];
|
|
574
259
|
|
|
575
|
-
|
|
576
|
-
if (
|
|
577
|
-
|
|
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
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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
|
-
|
|
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
|
-
|
|
604
|
-
const { name, description, type } = projectInfo;
|
|
289
|
+
// === File Watching ===
|
|
605
290
|
|
|
606
|
-
|
|
291
|
+
startWatching() {
|
|
292
|
+
this.fileSystemManager.startWatching(async (eventType, filename) => {
|
|
293
|
+
if (this.verbose) {
|
|
294
|
+
console.log(`📁 File ${eventType}: ${filename}`);
|
|
295
|
+
}
|
|
607
296
|
|
|
608
|
-
|
|
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
|
-
|
|
305
|
+
// Mark affected bundles as changed
|
|
306
|
+
this.bundleManager.markBundlesChanged(filename);
|
|
611
307
|
|
|
612
|
-
|
|
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
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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
|
-
|
|
637
|
-
|
|
638
|
-
|
|
332
|
+
if (matchesBundle && bundle.changed) {
|
|
333
|
+
affectedBundles.push(name);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
639
336
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
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
|
-
|
|
649
|
-
-
|
|
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
|
-
|
|
662
|
-
const relativePath = relative(this.CWD, filePath).replace(/\\\\/g, '/');
|
|
350
|
+
// === HTTP Server ===
|
|
663
351
|
|
|
664
|
-
|
|
665
|
-
|
|
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
|
-
|
|
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
|
-
|
|
673
|
-
if (
|
|
674
|
-
|
|
675
|
-
|
|
360
|
+
// Handle preflight requests
|
|
361
|
+
if (req.method === 'OPTIONS') {
|
|
362
|
+
res.writeHead(200);
|
|
363
|
+
res.end();
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
676
366
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
.
|
|
680
|
-
|
|
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
|
-
|
|
683
|
-
|
|
684
|
-
regexPattern = regexPattern.replace(/DOUBLESTAR/g, '.*');
|
|
373
|
+
// Handle static files
|
|
374
|
+
return this.handleStaticFile(req, res, url);
|
|
685
375
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
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
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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
|
-
//
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
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
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
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
|
|
784
|
-
|
|
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
|
-
|
|
789
|
-
|
|
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
|
-
|
|
792
|
-
continue; // Don't even log it, just skip
|
|
793
|
-
}
|
|
417
|
+
// === Semantic Analysis (Legacy methods for compatibility) ===
|
|
794
418
|
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
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
|
-
|
|
433
|
+
console.warn('Failed to load chunks from SQLite, performing fresh analysis...');
|
|
804
434
|
}
|
|
805
435
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
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
|
-
|
|
825
|
-
|
|
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
|
-
|
|
847
|
-
|
|
848
|
-
|
|
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
|
-
|
|
859
|
-
this.
|
|
860
|
-
});
|
|
452
|
+
// 4. Trigger background embedding enhancement
|
|
453
|
+
this.enhanceSemanticChunksIfNeeded(this.semanticCache);
|
|
861
454
|
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
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
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
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
|
-
|
|
472
|
+
return this.getSemanticAnalysis();
|
|
889
473
|
}
|
|
890
474
|
|
|
891
|
-
|
|
892
|
-
|
|
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
|
-
//
|
|
897
|
-
const
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
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
|
-
|
|
916
|
-
|
|
917
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
951
|
-
if (
|
|
952
|
-
|
|
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
|
-
//
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
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
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
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
|
-
|
|
1091
|
-
const
|
|
1092
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1114
|
-
`;
|
|
1115
|
-
return fileXml;
|
|
523
|
+
return this.bundleManager.generateFileXML(chunk.filePath);
|
|
1116
524
|
}
|
|
1117
525
|
|
|
1118
|
-
|
|
1119
|
-
|
|
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
|
-
|
|
1132
|
-
return String(text)
|
|
1133
|
-
.replace(/&/g, '&')
|
|
1134
|
-
.replace(/</g, '<')
|
|
1135
|
-
.replace(/>/g, '>')
|
|
1136
|
-
.replace(/"/g, '"')
|
|
1137
|
-
.replace(/'/g, ''');
|
|
1138
|
-
}
|
|
531
|
+
// === Activity Management (Placeholder) ===
|
|
1139
532
|
|
|
1140
|
-
|
|
533
|
+
async loadActivities() {
|
|
1141
534
|
try {
|
|
1142
|
-
const
|
|
1143
|
-
const
|
|
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
|
-
|
|
1157
|
-
|
|
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
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
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
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
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
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
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
|
-
|
|
1539
|
-
|
|
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
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
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
|
-
|
|
585
|
+
});
|
|
586
|
+
} catch (error) {
|
|
587
|
+
console.error('Failed to load activities:', error);
|
|
588
|
+
return [];
|
|
589
|
+
}
|
|
590
|
+
}
|
|
1776
591
|
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
592
|
+
loadMarkdownFile(filePath) {
|
|
593
|
+
try {
|
|
594
|
+
if (fs.existsSync(filePath)) {
|
|
595
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
1780
596
|
}
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
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
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1802
|
-
|
|
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
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
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
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
631
|
+
async executeActivity(activityId) {
|
|
632
|
+
// Placeholder - would execute specific activity
|
|
633
|
+
return { success: false, message: 'Activity execution not implemented' };
|
|
1823
634
|
}
|
|
1824
635
|
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
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
|
-
|
|
1863
|
-
const analysis = await this.getSemanticAnalysis();
|
|
1864
|
-
const chunk = analysis.chunks.find(c => c.name === chunkName);
|
|
641
|
+
// === MCP Server Integration ===
|
|
1865
642
|
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
643
|
+
startMCPServer() {
|
|
644
|
+
if (!this.mcpServer) {
|
|
645
|
+
this.mcpServer = new MCPServer(this);
|
|
646
|
+
this.mcpServerStarted = true;
|
|
1869
647
|
|
|
1870
|
-
|
|
1871
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
656
|
+
async listen(port = 3333, host = 'localhost') {
|
|
657
|
+
const server = createServer((req, res) => {
|
|
658
|
+
this.handleRequest(req, res);
|
|
659
|
+
});
|
|
1936
660
|
|
|
1937
|
-
//
|
|
1938
|
-
|
|
1939
|
-
config.bundles[bundleName] = files;
|
|
661
|
+
// Initialize WebSocket server
|
|
662
|
+
this.webSocketManager.initialize(server);
|
|
1940
663
|
|
|
1941
|
-
//
|
|
1942
|
-
|
|
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
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
this.saveBundleStates();
|
|
1948
|
-
this.broadcastUpdate();
|
|
1949
|
-
}
|
|
671
|
+
// Display initialization summary
|
|
672
|
+
this.displayInitSummary();
|
|
673
|
+
});
|
|
1950
674
|
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
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
|
-
|
|
1958
|
-
|
|
1959
|
-
server.
|
|
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.
|
|
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.
|
|
712
|
+
return await server.listen(options.port, options.host);
|
|
1969
713
|
}
|
|
1970
714
|
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
server.
|
|
1974
|
-
|
|
1975
|
-
|
|
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
|
-
|
|
1979
|
-
|
|
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
|
|
1986
|
-
const
|
|
1987
|
-
|
|
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
|
-
|
|
1999
|
-
|
|
2000
|
-
master: ['**/*']
|
|
2001
|
-
}
|
|
2002
|
-
};
|
|
729
|
+
await server.bundleManager.regenerateBundle(bundleName);
|
|
730
|
+
const bundleInfo = server.bundleManager.getBundleInfo(bundleName);
|
|
2003
731
|
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
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
|
-
|
|
2033
|
-
|
|
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
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
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
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
}
|
|
2057
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2073
|
-
|
|
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
|
-
|
|
2086
|
-
const
|
|
2087
|
-
|
|
827
|
+
// Copy agent configuration files
|
|
828
|
+
const agentFiles = [
|
|
829
|
+
'agent-config.yaml',
|
|
830
|
+
'agent-instructions.md'
|
|
831
|
+
];
|
|
2088
832
|
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
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
|
|
2103
|
-
const
|
|
2104
|
-
|
|
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
|
-
|
|
2109
|
-
|
|
2110
|
-
console.log(`📁 Project: ${projectName} (${projectDir})`);
|
|
2111
|
-
}
|
|
908
|
+
const bundles = server.bundleManager.getAllBundleInfo();
|
|
909
|
+
const totalFiles = server.fileSystemManager.getAllFiles().length;
|
|
2112
910
|
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
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
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
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
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
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
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
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
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
931
|
+
console.log('🔧 Setting up MCP integration...');
|
|
932
|
+
console.log(`Project: ${projectPath}`);
|
|
933
|
+
console.log(`Claude config: ${configPath}`);
|
|
2147
934
|
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
935
|
+
try {
|
|
936
|
+
let config = {};
|
|
937
|
+
if (existsSync(configPath)) {
|
|
938
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
939
|
+
}
|
|
2153
940
|
|
|
2154
|
-
|
|
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
|
-
|
|
2158
|
-
|
|
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
|
+
}
|