cntx-ui 2.0.15 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -344
- package/bin/cntx-ui-mcp.sh +3 -0
- package/bin/cntx-ui.js +2 -1
- package/lib/agent-runtime.js +161 -1340
- package/lib/agent-tools.js +9 -7
- package/lib/api-router.js +262 -79
- package/lib/bundle-manager.js +172 -407
- package/lib/configuration-manager.js +94 -59
- package/lib/database-manager.js +397 -0
- package/lib/file-system-manager.js +17 -0
- package/lib/heuristics-manager.js +119 -17
- package/lib/mcp-server.js +125 -55
- package/lib/semantic-splitter.js +222 -481
- package/lib/simple-vector-store.js +69 -300
- package/package.json +18 -31
- package/server.js +151 -73
- package/templates/TOOLS.md +41 -0
- package/templates/activities/activities/create-project-bundles/README.md +4 -3
- package/templates/activities/activities/create-project-bundles/notes.md +15 -19
- package/templates/activities/activities/create-project-bundles/tasks.md +4 -4
- package/templates/activities/activities.json +1 -1
- package/templates/agent-config.yaml +0 -13
- package/templates/agent-instructions.md +22 -6
- package/templates/agent-rules/capabilities/bundle-system.md +1 -1
- package/templates/agent-rules/project-specific/architecture.md +1 -1
- package/web/dist/assets/index-B2OdTzzI.css +1 -0
- package/web/dist/assets/index-D0tBsKiR.js +2016 -0
- package/web/dist/index.html +2 -2
- package/mcp-config-example.json +0 -9
- package/web/dist/assets/heuristics-manager-browser-DfonOP5I.js +0 -1
- package/web/dist/assets/index-dF3qg-y_.js +0 -2486
- package/web/dist/assets/index-h5FGSg_P.css +0 -1
package/lib/bundle-manager.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bundle Manager for cntx-ui
|
|
3
|
-
* Handles
|
|
3
|
+
* Handles Smart Dynamic Bundles and traditional XML generation
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { readFileSync, statSync } from 'fs';
|
|
@@ -10,462 +10,227 @@ export default class BundleManager {
|
|
|
10
10
|
constructor(configManager, fileSystemManager, verbose = false) {
|
|
11
11
|
this.configManager = configManager;
|
|
12
12
|
this.fileSystemManager = fileSystemManager;
|
|
13
|
+
this.db = configManager.dbManager;
|
|
13
14
|
this.verbose = verbose;
|
|
14
15
|
this._isScanning = false;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const progress = `📊 Generating bundles: ${processedBundles}/${totalBundles} (${name})`;
|
|
32
|
-
process.stdout.write(`\r${progress.padEnd(80)}`); // Pad to clear previous longer messages
|
|
33
|
-
}
|
|
34
|
-
await this.generateBundle(name);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (this.verbose) {
|
|
38
|
-
process.stdout.write('\r' + ''.padEnd(80) + '\r'); // Clear the line
|
|
39
|
-
}
|
|
40
|
-
this.configManager.saveBundleStates();
|
|
41
|
-
} finally {
|
|
42
|
-
this._isScanning = false;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async generateBundle(name) {
|
|
47
|
-
const bundles = this.configManager.getBundles();
|
|
48
|
-
const bundle = bundles.get(name);
|
|
49
|
-
|
|
50
|
-
if (!bundle) {
|
|
51
|
-
throw new Error(`Bundle "${name}" not found`);
|
|
52
|
-
}
|
|
18
|
+
/**
|
|
19
|
+
* Get all bundle information, including Smart Dynamic Bundles
|
|
20
|
+
*/
|
|
21
|
+
getAllBundleInfo() {
|
|
22
|
+
if (this.verbose) console.log('📦 Getting all bundle info...');
|
|
23
|
+
const manualBundles = Array.from(this.configManager.getBundles().entries()).map(([name, bundle]) => ({
|
|
24
|
+
name,
|
|
25
|
+
fileCount: bundle.files?.length || 0,
|
|
26
|
+
size: bundle.size || 0,
|
|
27
|
+
generated: bundle.generated,
|
|
28
|
+
changed: bundle.changed,
|
|
29
|
+
patterns: bundle.patterns,
|
|
30
|
+
type: 'manual'
|
|
31
|
+
}));
|
|
53
32
|
|
|
54
|
-
|
|
55
|
-
// This preserves manual file management while allowing pattern-based initialization
|
|
56
|
-
if (!bundle.files || bundle.files.length === 0) {
|
|
57
|
-
// Get all files matching bundle patterns
|
|
58
|
-
const allFiles = this.fileSystemManager.getAllFiles();
|
|
59
|
-
const bundleFiles = allFiles.filter(file =>
|
|
60
|
-
bundle.patterns.some(pattern =>
|
|
61
|
-
this.fileSystemManager.matchesPattern(file, pattern)
|
|
62
|
-
)
|
|
63
|
-
);
|
|
33
|
+
if (this.verbose) console.log(`📦 Found ${manualBundles.length} manual bundles`);
|
|
64
34
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
// If bundle.files already exists, preserve manual file management
|
|
35
|
+
const smartBundles = this.generateSmartBundleDefinitions();
|
|
36
|
+
if (this.verbose) console.log(`📦 Found ${smartBundles.length} smart bundle definitions`);
|
|
69
37
|
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
38
|
+
// Filter out smart bundles that have no files
|
|
39
|
+
const activeSmartBundles = smartBundles.map(b => {
|
|
40
|
+
const files = this.resolveSmartBundle(b.name);
|
|
41
|
+
return {
|
|
42
|
+
...b,
|
|
43
|
+
fileCount: files.length,
|
|
44
|
+
files
|
|
45
|
+
};
|
|
46
|
+
}).filter(b => b.fileCount > 0);
|
|
74
47
|
|
|
75
|
-
|
|
76
|
-
const absoluteFiles = bundle.files.map(file =>
|
|
77
|
-
this.fileSystemManager.absolutePath(file)
|
|
78
|
-
);
|
|
79
|
-
bundle.content = this.generateBundleXML(name, absoluteFiles);
|
|
80
|
-
bundle.size = Buffer.byteLength(bundle.content, 'utf8');
|
|
81
|
-
bundle.generated = new Date().toISOString();
|
|
82
|
-
bundle.changed = false;
|
|
48
|
+
if (this.verbose) console.log(`📦 Active smart bundles: ${activeSmartBundles.length}`);
|
|
83
49
|
|
|
84
|
-
return
|
|
50
|
+
return [...manualBundles, ...activeSmartBundles];
|
|
85
51
|
}
|
|
86
52
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
<description>
|
|
105
|
-
This bundle contains ${files.length} files organized into different categories.
|
|
106
|
-
${bundlePurpose}
|
|
107
|
-
</description>
|
|
108
|
-
<entry_points>
|
|
109
|
-
${entryPoints.map(file => ` <file>${this.escapeXml(file)}</file>`).join('\n')}
|
|
110
|
-
</entry_points>
|
|
111
|
-
</overview>
|
|
112
|
-
|
|
113
|
-
<file_structure>`;
|
|
114
|
-
|
|
115
|
-
// Add categorized files
|
|
116
|
-
Object.entries(categorizedFiles).forEach(([category, categoryFiles]) => {
|
|
117
|
-
if (categoryFiles.length > 0) {
|
|
118
|
-
xml += `
|
|
119
|
-
<group name="${category}" description="${this.getTypeDescription(category)}">`;
|
|
120
|
-
|
|
121
|
-
categoryFiles.forEach(file => {
|
|
122
|
-
xml += `
|
|
123
|
-
${this.generateFileXML(file)}`;
|
|
53
|
+
/**
|
|
54
|
+
* Generate Smart Bundle definitions from indexed semantic data
|
|
55
|
+
*/
|
|
56
|
+
generateSmartBundleDefinitions() {
|
|
57
|
+
const smartBundles = [];
|
|
58
|
+
try {
|
|
59
|
+
// 1. Group by Purpose (Heuristics)
|
|
60
|
+
const purposeRows = this.db.db.prepare('SELECT DISTINCT purpose, COUNT(*) as count FROM semantic_chunks GROUP BY purpose').all();
|
|
61
|
+
purposeRows.forEach(row => {
|
|
62
|
+
if (!row.purpose) return;
|
|
63
|
+
const name = `smart:${row.purpose.toLowerCase().replace(/\s+/g, '-')}`;
|
|
64
|
+
smartBundles.push({
|
|
65
|
+
name,
|
|
66
|
+
purpose: row.purpose,
|
|
67
|
+
fileCount: row.count,
|
|
68
|
+
type: 'smart',
|
|
69
|
+
description: `Automatically grouped by purpose: ${row.purpose}`
|
|
124
70
|
});
|
|
71
|
+
});
|
|
125
72
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
'components': [],
|
|
144
|
-
'hooks': [],
|
|
145
|
-
'utilities': [],
|
|
146
|
-
'types': [],
|
|
147
|
-
'styles': [],
|
|
148
|
-
'tests': [],
|
|
149
|
-
'configuration': [],
|
|
150
|
-
'documentation': [],
|
|
151
|
-
'other': []
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
files.forEach(file => {
|
|
155
|
-
const ext = extname(file).toLowerCase();
|
|
156
|
-
const fileName = basename(file).toLowerCase();
|
|
157
|
-
const filePath = file.toLowerCase();
|
|
158
|
-
|
|
159
|
-
// Entry points
|
|
160
|
-
if (fileName.match(/^(main|index|app)\.(js|jsx|ts|tsx)$/)) {
|
|
161
|
-
categories.entry_points.push(file);
|
|
162
|
-
}
|
|
163
|
-
// Components
|
|
164
|
-
else if (ext.match(/\.(jsx|tsx|vue)$/) || filePath.includes('/components/')) {
|
|
165
|
-
categories.components.push(file);
|
|
166
|
-
}
|
|
167
|
-
// Hooks
|
|
168
|
-
else if (filePath.includes('/hooks/') || fileName.startsWith('use') && ext.match(/\.(js|ts)$/)) {
|
|
169
|
-
categories.hooks.push(file);
|
|
170
|
-
}
|
|
171
|
-
// Utilities
|
|
172
|
-
else if (filePath.includes('/utils/') || filePath.includes('/helpers/') || filePath.includes('/lib/')) {
|
|
173
|
-
categories.utilities.push(file);
|
|
174
|
-
}
|
|
175
|
-
// Types
|
|
176
|
-
else if (fileName.includes('.d.ts') || filePath.includes('/types/') || fileName.includes('types')) {
|
|
177
|
-
categories.types.push(file);
|
|
178
|
-
}
|
|
179
|
-
// Styles
|
|
180
|
-
else if (ext.match(/\.(css|scss|sass|less|styl)$/)) {
|
|
181
|
-
categories.styles.push(file);
|
|
182
|
-
}
|
|
183
|
-
// Tests
|
|
184
|
-
else if (fileName.includes('.test.') || fileName.includes('.spec.') || filePath.includes('/test/') || filePath.includes('/__tests__/')) {
|
|
185
|
-
categories.tests.push(file);
|
|
186
|
-
}
|
|
187
|
-
// Configuration
|
|
188
|
-
else if (ext.match(/\.(json|yaml|yml|toml|ini)$/) || fileName.includes('config')) {
|
|
189
|
-
categories.configuration.push(file);
|
|
190
|
-
}
|
|
191
|
-
// Documentation
|
|
192
|
-
else if (ext.match(/\.(md|txt|rst)$/)) {
|
|
193
|
-
categories.documentation.push(file);
|
|
194
|
-
}
|
|
195
|
-
// Other
|
|
196
|
-
else {
|
|
197
|
-
categories.other.push(file);
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
return categories;
|
|
73
|
+
// 2. Group by Component Types (Subtypes)
|
|
74
|
+
const subtypeRows = this.db.db.prepare('SELECT DISTINCT subtype, COUNT(*) as count FROM semantic_chunks GROUP BY subtype').all();
|
|
75
|
+
subtypeRows.forEach(row => {
|
|
76
|
+
if (!row.subtype) return;
|
|
77
|
+
const name = `smart:type-${row.subtype.toLowerCase().replace(/_/g, '-')}`;
|
|
78
|
+
smartBundles.push({
|
|
79
|
+
name,
|
|
80
|
+
purpose: row.subtype,
|
|
81
|
+
fileCount: row.count,
|
|
82
|
+
type: 'smart',
|
|
83
|
+
description: `All ${row.subtype} elements across the codebase`
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
} catch (e) {
|
|
87
|
+
if (this.verbose) console.warn('Smart bundle discovery failed:', e.message);
|
|
88
|
+
}
|
|
89
|
+
return smartBundles;
|
|
202
90
|
}
|
|
203
91
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Resolve files for a bundle (Manual or Smart)
|
|
94
|
+
*/
|
|
95
|
+
async resolveBundleFiles(bundleName) {
|
|
96
|
+
if (bundleName.startsWith('smart:')) {
|
|
97
|
+
return this.resolveSmartBundle(bundleName);
|
|
98
|
+
}
|
|
211
99
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
100
|
+
const bundle = this.configManager.getBundles().get(bundleName);
|
|
101
|
+
if (!bundle) return [];
|
|
102
|
+
|
|
103
|
+
const allFiles = this.fileSystemManager.getAllFiles();
|
|
104
|
+
return allFiles.filter(file =>
|
|
105
|
+
bundle.patterns.some(pattern => this.fileSystemManager.matchesPattern(file, pattern))
|
|
106
|
+
).map(f => this.fileSystemManager.relativePath(f));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Resolve a Smart Bundle query against SQLite
|
|
111
|
+
*/
|
|
112
|
+
resolveSmartBundle(bundleName) {
|
|
113
|
+
const query = bundleName.replace('smart:', '');
|
|
114
|
+
let rows = [];
|
|
115
|
+
|
|
116
|
+
if (query.startsWith('type-')) {
|
|
117
|
+
const type = query.replace('type-', '').replace(/-/g, '_');
|
|
118
|
+
rows = this.db.db.prepare('SELECT DISTINCT file_path FROM semantic_chunks WHERE LOWER(subtype) = ?').all(type);
|
|
119
|
+
} else {
|
|
120
|
+
const purposeRows = this.db.db.prepare('SELECT DISTINCT purpose FROM semantic_chunks').all();
|
|
121
|
+
const matched = purposeRows.find(r => r.purpose?.toLowerCase().replace(/\s+/g, '-') === query);
|
|
122
|
+
if (matched) {
|
|
123
|
+
rows = this.db.db.prepare('SELECT DISTINCT file_path FROM semantic_chunks WHERE purpose = ?').all(matched.purpose);
|
|
216
124
|
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
return entryPoints;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
getBundlePurpose(bundleName) {
|
|
223
|
-
const purposes = {
|
|
224
|
-
'master': 'Complete codebase overview with all project files',
|
|
225
|
-
'frontend': 'User interface components, styling, and client-side logic',
|
|
226
|
-
'backend': 'Server-side logic, API endpoints, and business logic',
|
|
227
|
-
'components': 'Reusable UI components and their associated styles',
|
|
228
|
-
'utilities': 'Shared utility functions and helper modules',
|
|
229
|
-
'configuration': 'Project configuration files and environment setup',
|
|
230
|
-
'tests': 'Test files and testing utilities',
|
|
231
|
-
'documentation': 'Project documentation and README files',
|
|
232
|
-
'types': 'TypeScript type definitions and interfaces',
|
|
233
|
-
'styles': 'CSS, SCSS, and other styling files'
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
return purposes[bundleName] || `Files matching the ${bundleName} bundle patterns`;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
getTypeDescription(type) {
|
|
240
|
-
const descriptions = {
|
|
241
|
-
'entry_points': 'Main application entry points and bootstrap files',
|
|
242
|
-
'components': 'Reusable UI components and their implementations',
|
|
243
|
-
'hooks': 'Custom React hooks and composable functions',
|
|
244
|
-
'utilities': 'Shared utility functions and helper modules',
|
|
245
|
-
'types': 'TypeScript type definitions and interfaces',
|
|
246
|
-
'styles': 'CSS, SCSS, and other styling files',
|
|
247
|
-
'tests': 'Test files and testing utilities',
|
|
248
|
-
'configuration': 'Configuration files and environment setup',
|
|
249
|
-
'documentation': 'Documentation, README files, and guides',
|
|
250
|
-
'other': 'Other project files not fitting specific categories'
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
return descriptions[type] || 'Project files';
|
|
125
|
+
}
|
|
126
|
+
return rows.map(r => r.file_path);
|
|
254
127
|
}
|
|
255
128
|
|
|
256
|
-
// ===
|
|
129
|
+
// === Bundle Generation ===
|
|
257
130
|
|
|
258
|
-
|
|
131
|
+
async generateAllBundles() {
|
|
132
|
+
this._isScanning = true;
|
|
259
133
|
try {
|
|
260
|
-
const
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
<![CDATA[${content}]]>
|
|
267
|
-
</file>`;
|
|
268
|
-
} catch (error) {
|
|
269
|
-
const relativePath = relative(this.configManager.CWD, file);
|
|
270
|
-
return `<file path="${this.escapeXml(relativePath)}" role="error" error="${this.escapeXml(error.message)}">
|
|
271
|
-
<!-- File could not be read -->
|
|
272
|
-
</file>`;
|
|
134
|
+
const bundles = this.configManager.getBundles();
|
|
135
|
+
for (const [name] of bundles) {
|
|
136
|
+
await this.regenerateBundle(name);
|
|
137
|
+
}
|
|
138
|
+
} finally {
|
|
139
|
+
this._isScanning = false;
|
|
273
140
|
}
|
|
274
141
|
}
|
|
275
142
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const
|
|
143
|
+
async regenerateBundle(bundleName) {
|
|
144
|
+
if (this.verbose) console.log(`🔄 Regenerating bundle: ${bundleName}`);
|
|
145
|
+
|
|
146
|
+
const files = await this.resolveBundleFiles(bundleName);
|
|
147
|
+
const content = await this.generateBundleXML(bundleName, files);
|
|
148
|
+
|
|
149
|
+
const bundleData = {
|
|
150
|
+
files,
|
|
151
|
+
content,
|
|
152
|
+
size: Buffer.byteLength(content, 'utf8'),
|
|
153
|
+
generated: new Date().toISOString(),
|
|
154
|
+
changed: false
|
|
155
|
+
};
|
|
280
156
|
|
|
281
|
-
if (
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
return 'configuration';
|
|
286
|
-
}
|
|
287
|
-
if (fileName.includes('readme') || ext === '.md') {
|
|
288
|
-
return 'documentation';
|
|
157
|
+
if (!bundleName.startsWith('smart:')) {
|
|
158
|
+
const existing = this.configManager.getBundles().get(bundleName);
|
|
159
|
+
this.configManager.getBundles().set(bundleName, { ...existing, ...bundleData });
|
|
160
|
+
this.configManager.saveBundleStates();
|
|
289
161
|
}
|
|
290
162
|
|
|
291
|
-
return
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
getFileStats(filePath) {
|
|
295
|
-
try {
|
|
296
|
-
const stats = statSync(filePath);
|
|
297
|
-
return {
|
|
298
|
-
size: stats.size,
|
|
299
|
-
mtime: stats.mtime,
|
|
300
|
-
ctime: stats.ctime
|
|
301
|
-
};
|
|
302
|
-
} catch (error) {
|
|
303
|
-
return {
|
|
304
|
-
size: 0,
|
|
305
|
-
mtime: new Date(0),
|
|
306
|
-
ctime: new Date(0)
|
|
307
|
-
};
|
|
308
|
-
}
|
|
163
|
+
return bundleData;
|
|
309
164
|
}
|
|
310
165
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
166
|
+
async generateBundleXML(bundleName, relativeFiles) {
|
|
167
|
+
const projectInfo = this.getProjectInfo();
|
|
168
|
+
let xml = `<?xml version="1.0" encoding="UTF-8"?>\n<codebase>\n`;
|
|
169
|
+
xml += ` <project_info>\n <name>${this.escapeXml(projectInfo.name)}</name>\n <bundle>${this.escapeXml(bundleName)}</bundle>\n </project_info>\n`;
|
|
170
|
+
|
|
171
|
+
for (const relPath of relativeFiles) {
|
|
172
|
+
xml += await this.generateFileXML(relPath);
|
|
314
173
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
.replace(/>/g, '>')
|
|
319
|
-
.replace(/"/g, '"')
|
|
320
|
-
.replace(/'/g, ''');
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// === Bundle State Management ===
|
|
324
|
-
|
|
325
|
-
markBundlesChanged(filename) {
|
|
326
|
-
const bundles = this.configManager.getBundles();
|
|
327
|
-
|
|
328
|
-
bundles.forEach((bundle, name) => {
|
|
329
|
-
const matchesBundle = bundle.patterns.some(pattern =>
|
|
330
|
-
this.fileSystemManager.matchesPattern(filename, pattern)
|
|
331
|
-
);
|
|
332
|
-
|
|
333
|
-
if (matchesBundle) {
|
|
334
|
-
bundle.changed = true;
|
|
335
|
-
}
|
|
336
|
-
});
|
|
174
|
+
|
|
175
|
+
xml += `</codebase>`;
|
|
176
|
+
return xml;
|
|
337
177
|
}
|
|
338
178
|
|
|
339
|
-
|
|
179
|
+
async generateFileXML(relativePath) {
|
|
340
180
|
try {
|
|
341
|
-
const
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
181
|
+
const fullPath = this.fileSystemManager.fullPath(relativePath);
|
|
182
|
+
const content = readFileSync(fullPath, 'utf8');
|
|
183
|
+
const chunks = this.db.getChunksByFile(relativePath);
|
|
184
|
+
|
|
185
|
+
let xml = ` <file path="${this.escapeXml(relativePath)}">\n`;
|
|
186
|
+
if (chunks.length > 0) {
|
|
187
|
+
xml += ` <semantic_context>\n`;
|
|
188
|
+
chunks.forEach(c => {
|
|
189
|
+
xml += ` <chunk name="${this.escapeXml(c.name)}" purpose="${this.escapeXml(c.purpose)}" complexity="${c.complexity?.score || 0}" />\n`;
|
|
190
|
+
});
|
|
191
|
+
xml += ` </semantic_context>\n`;
|
|
346
192
|
}
|
|
347
|
-
|
|
348
|
-
return
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
);
|
|
352
|
-
|
|
353
|
-
const isHidden = this.configManager.isFileHidden(file, bundleName);
|
|
354
|
-
const relativePath = relative(this.configManager.CWD, file);
|
|
355
|
-
|
|
356
|
-
return {
|
|
357
|
-
path: relativePath,
|
|
358
|
-
fullPath: file,
|
|
359
|
-
included: matchesBundle && !isHidden,
|
|
360
|
-
hidden: isHidden,
|
|
361
|
-
matchesPattern: matchesBundle
|
|
362
|
-
};
|
|
363
|
-
});
|
|
364
|
-
} catch (error) {
|
|
365
|
-
console.error('Failed to get file list with visibility:', error.message);
|
|
366
|
-
return [];
|
|
193
|
+
xml += ` <content><![CDATA[${content}]]></content>\n </file>\n`;
|
|
194
|
+
return xml;
|
|
195
|
+
} catch (e) {
|
|
196
|
+
return ` <file path="${this.escapeXml(relativePath)}" error="${this.escapeXml(e.message)}" />\n`;
|
|
367
197
|
}
|
|
368
198
|
}
|
|
369
199
|
|
|
370
|
-
// === Project Information ===
|
|
371
|
-
|
|
372
200
|
getProjectInfo() {
|
|
373
201
|
try {
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
version: pkg.version || '1.0.0',
|
|
379
|
-
description: pkg.description || 'No description available'
|
|
380
|
-
};
|
|
381
|
-
} catch (error) {
|
|
382
|
-
return {
|
|
383
|
-
name: 'Unknown Project',
|
|
384
|
-
version: '1.0.0',
|
|
385
|
-
description: 'No description available'
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// === Bundle Operations ===
|
|
391
|
-
|
|
392
|
-
async regenerateBundle(bundleName) {
|
|
393
|
-
// Notify WebSocket clients that sync has started
|
|
394
|
-
if (this.webSocketManager) {
|
|
395
|
-
this.webSocketManager.onBundleSyncStarted(bundleName);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
try {
|
|
399
|
-
const bundle = await this.generateBundle(bundleName);
|
|
400
|
-
this.configManager.saveBundleStates();
|
|
401
|
-
|
|
402
|
-
// Notify WebSocket clients that sync completed successfully
|
|
403
|
-
if (this.webSocketManager) {
|
|
404
|
-
this.webSocketManager.onBundleSyncCompleted(bundleName);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
return bundle;
|
|
408
|
-
} catch (error) {
|
|
409
|
-
// Notify WebSocket clients that sync failed
|
|
410
|
-
if (this.webSocketManager) {
|
|
411
|
-
this.webSocketManager.onBundleSyncFailed(bundleName, error);
|
|
412
|
-
}
|
|
413
|
-
throw error;
|
|
202
|
+
const pkg = JSON.parse(readFileSync(this.fileSystemManager.fullPath('package.json'), 'utf8'));
|
|
203
|
+
return { name: pkg.name || 'Unknown', version: pkg.version || '1.0.0' };
|
|
204
|
+
} catch {
|
|
205
|
+
return { name: 'Unknown', version: '1.0.0' };
|
|
414
206
|
}
|
|
415
207
|
}
|
|
416
208
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
const changedBundles = [];
|
|
420
|
-
|
|
421
|
-
for (const [name, bundle] of bundles) {
|
|
422
|
-
if (bundle.changed) {
|
|
423
|
-
await this.generateBundle(name);
|
|
424
|
-
changedBundles.push(name);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
if (changedBundles.length > 0) {
|
|
429
|
-
this.configManager.saveBundleStates();
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
return changedBundles;
|
|
209
|
+
escapeXml(text) {
|
|
210
|
+
return String(text).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
433
211
|
}
|
|
434
212
|
|
|
435
213
|
getBundleContent(bundleName) {
|
|
214
|
+
if (bundleName.startsWith('smart:')) {
|
|
215
|
+
// For smart bundles, we generate on the fly if not cached
|
|
216
|
+
return this.regenerateBundle(bundleName).then(data => data.content);
|
|
217
|
+
}
|
|
436
218
|
const bundle = this.configManager.getBundles().get(bundleName);
|
|
437
219
|
return bundle ? bundle.content : null;
|
|
438
220
|
}
|
|
439
221
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
fileCount: bundle.files.length,
|
|
447
|
-
size: bundle.size,
|
|
448
|
-
generated: bundle.generated,
|
|
449
|
-
changed: bundle.changed,
|
|
450
|
-
patterns: bundle.patterns
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
getAllBundleInfo() {
|
|
455
|
-
const bundles = this.configManager.getBundles();
|
|
456
|
-
return Array.from(bundles.entries()).map(([name, bundle]) => ({
|
|
457
|
-
name,
|
|
458
|
-
fileCount: bundle.files.length,
|
|
459
|
-
size: bundle.size,
|
|
460
|
-
generated: bundle.generated,
|
|
461
|
-
changed: bundle.changed,
|
|
462
|
-
patterns: bundle.patterns
|
|
463
|
-
}));
|
|
222
|
+
markBundlesChanged(filename) {
|
|
223
|
+
this.configManager.getBundles().forEach((bundle, name) => {
|
|
224
|
+
if (bundle.patterns?.some(p => this.fileSystemManager.matchesPattern(filename, p))) {
|
|
225
|
+
bundle.changed = true;
|
|
226
|
+
}
|
|
227
|
+
});
|
|
464
228
|
}
|
|
465
229
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
230
|
+
getBundleInfo(bundleName) {
|
|
231
|
+
if (bundleName.startsWith('smart:')) {
|
|
232
|
+
return this.generateSmartBundleDefinitions().find(b => b.name === bundleName);
|
|
233
|
+
}
|
|
234
|
+
return this.configManager.getBundles().get(bundleName);
|
|
470
235
|
}
|
|
471
236
|
}
|