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