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.
Files changed (44) hide show
  1. package/bin/cntx-ui.js +137 -55
  2. package/lib/agent-runtime.js +1480 -0
  3. package/lib/agent-tools.js +368 -0
  4. package/lib/api-router.js +978 -0
  5. package/lib/bundle-manager.js +471 -0
  6. package/lib/configuration-manager.js +725 -0
  7. package/lib/file-system-manager.js +472 -0
  8. package/lib/heuristics-manager.js +425 -0
  9. package/lib/mcp-server.js +1054 -1
  10. package/lib/semantic-splitter.js +7 -14
  11. package/lib/simple-vector-store.js +329 -0
  12. package/lib/websocket-manager.js +470 -0
  13. package/package.json +10 -3
  14. package/server.js +662 -1933
  15. package/templates/activities/README.md +67 -0
  16. package/templates/activities/activities/create-project-bundles/README.md +83 -0
  17. package/templates/activities/activities/create-project-bundles/notes.md +102 -0
  18. package/templates/activities/activities/create-project-bundles/progress.md +63 -0
  19. package/templates/activities/activities/create-project-bundles/tasks.md +39 -0
  20. package/templates/activities/activities.json +219 -0
  21. package/templates/activities/lib/.markdownlint.jsonc +18 -0
  22. package/templates/activities/lib/create-activity.mdc +63 -0
  23. package/templates/activities/lib/generate-tasks.mdc +64 -0
  24. package/templates/activities/lib/process-task-list.mdc +52 -0
  25. package/templates/agent-config.yaml +78 -0
  26. package/templates/agent-instructions.md +218 -0
  27. package/templates/agent-rules/capabilities/activities-system.md +147 -0
  28. package/templates/agent-rules/capabilities/bundle-system.md +131 -0
  29. package/templates/agent-rules/capabilities/vector-search.md +135 -0
  30. package/templates/agent-rules/core/codebase-navigation.md +91 -0
  31. package/templates/agent-rules/core/performance-hierarchy.md +48 -0
  32. package/templates/agent-rules/core/response-formatting.md +120 -0
  33. package/templates/agent-rules/project-specific/architecture.md +145 -0
  34. package/templates/config.json +76 -0
  35. package/templates/hidden-files.json +14 -0
  36. package/web/dist/assets/heuristics-manager-browser-DfonOP5I.js +1 -0
  37. package/web/dist/assets/index-dF3qg-y_.js +2486 -0
  38. package/web/dist/assets/index-h5FGSg_P.css +1 -0
  39. package/web/dist/cntx-ui.svg +18 -0
  40. package/web/dist/index.html +25 -8
  41. package/lib/semantic-integration.js +0 -441
  42. package/web/dist/assets/index-Ci1Q-YrQ.js +0 -611
  43. package/web/dist/assets/index-IUp4q_fr.css +0 -1
  44. package/web/dist/vite.svg +0 -21
@@ -0,0 +1,368 @@
1
+ /**
2
+ * Agent Tools for Codebase Exploration
3
+ * Built on top of existing cntx-ui infrastructure
4
+ */
5
+
6
+ import { readFileSync, existsSync } from 'fs';
7
+ import { join, relative } from 'path';
8
+ import { exec } from 'child_process';
9
+ import { promisify } from 'util';
10
+
11
+ const execAsync = promisify(exec);
12
+
13
+ export class AgentTools {
14
+ constructor(cntxServer) {
15
+ this.cntxServer = cntxServer;
16
+ }
17
+
18
+ /**
19
+ * Read file contents with bundle context
20
+ */
21
+ async readFile(filePath, options = {}) {
22
+ try {
23
+ const fullPath = join(this.cntxServer.CWD, filePath);
24
+
25
+ if (!existsSync(fullPath)) {
26
+ throw new Error(`File not found: ${filePath}`);
27
+ }
28
+
29
+ const content = readFileSync(fullPath, 'utf8');
30
+ const bundles = this.getFileBundles(filePath);
31
+
32
+ return {
33
+ path: filePath,
34
+ content: options.truncate ? this.truncateContent(content, options.maxLength) : content,
35
+ size: content.length,
36
+ lines: content.split('\n').length,
37
+ bundles,
38
+ mimeType: this.getMimeType(filePath)
39
+ };
40
+ } catch (error) {
41
+ throw new Error(`Failed to read file ${filePath}: ${error.message}`);
42
+ }
43
+ }
44
+
45
+ /**
46
+ * List files with bundle awareness and filtering
47
+ */
48
+ async listFiles(options = {}) {
49
+ const { bundle, pattern, type, limit = 100 } = options;
50
+
51
+ try {
52
+ let files = [];
53
+
54
+ if (bundle) {
55
+ const bundleObj = this.cntxServer.bundles.get(bundle);
56
+ if (!bundleObj) {
57
+ throw new Error(`Bundle '${bundle}' not found`);
58
+ }
59
+ files = bundleObj.files.map(f => ({
60
+ path: f,
61
+ bundle,
62
+ size: this.getFileSize(f),
63
+ type: this.getFileType(f)
64
+ }));
65
+ } else {
66
+ // Get all files across bundles
67
+ const allFiles = this.cntxServer.getAllFiles();
68
+ files = allFiles.map(f => ({
69
+ path: f,
70
+ bundles: this.getFileBundles(f),
71
+ size: this.getFileSize(f),
72
+ type: this.getFileType(f)
73
+ }));
74
+ }
75
+
76
+ // Apply filters
77
+ if (pattern) {
78
+ const regex = new RegExp(pattern, 'i');
79
+ files = files.filter(f => regex.test(f.path));
80
+ }
81
+
82
+ if (type) {
83
+ files = files.filter(f => f.type === type);
84
+ }
85
+
86
+ // Limit results
87
+ return files.slice(0, limit);
88
+ } catch (error) {
89
+ throw new Error(`Failed to list files: ${error.message}`);
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Search semantic chunks using existing vector search
95
+ */
96
+ async searchSemanticChunks(query, options = {}) {
97
+ try {
98
+ const analysis = await this.cntxServer.getSemanticAnalysis();
99
+ if (!analysis || !analysis.chunks) {
100
+ return { chunks: [], message: 'No semantic analysis available' };
101
+ }
102
+
103
+ let chunks = analysis.chunks;
104
+ const { bundle, type, complexity, maxResults = 10 } = options;
105
+
106
+ // Apply semantic search if vector store is available
107
+ if (this.cntxServer.vectorStoreInitialized) {
108
+ try {
109
+ const searchResults = await this.cntxServer.vectorStore.search(query, maxResults * 2);
110
+ const chunkIds = searchResults.map(r => r.metadata?.chunkId).filter(Boolean);
111
+ chunks = chunks.filter(c => chunkIds.includes(c.id));
112
+ } catch (error) {
113
+ // Fall back to text-based search
114
+ chunks = chunks.filter(c =>
115
+ c.purpose?.toLowerCase().includes(query.toLowerCase()) ||
116
+ c.name?.toLowerCase().includes(query.toLowerCase()) ||
117
+ c.code?.toLowerCase().includes(query.toLowerCase())
118
+ );
119
+ }
120
+ } else {
121
+ // Text-based semantic search
122
+ chunks = chunks.filter(c =>
123
+ c.purpose?.toLowerCase().includes(query.toLowerCase()) ||
124
+ c.name?.toLowerCase().includes(query.toLowerCase()) ||
125
+ c.code?.toLowerCase().includes(query.toLowerCase())
126
+ );
127
+ }
128
+
129
+ // Apply filters
130
+ if (bundle) {
131
+ chunks = chunks.filter(c => c.bundles && c.bundles.includes(bundle));
132
+ }
133
+
134
+ if (type) {
135
+ chunks = chunks.filter(c => c.subtype === type);
136
+ }
137
+
138
+ if (complexity) {
139
+ chunks = chunks.filter(c => c.complexity?.level === complexity);
140
+ }
141
+
142
+ // Clean and limit results
143
+ const cleanChunks = chunks.slice(0, maxResults).map(chunk => ({
144
+ ...chunk,
145
+ code: this.truncateContent(chunk.code, 500),
146
+ bundles: chunk.bundles || [],
147
+ relevanceScore: chunk.score || 0
148
+ }));
149
+
150
+ return {
151
+ query,
152
+ chunks: cleanChunks,
153
+ totalResults: chunks.length,
154
+ hasMore: chunks.length > maxResults
155
+ };
156
+ } catch (error) {
157
+ throw new Error(`Semantic search failed: ${error.message}`);
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Get bundle information
163
+ */
164
+ async getBundle(bundleName) {
165
+ try {
166
+ const bundle = this.cntxServer.bundles.get(bundleName);
167
+ if (!bundle) {
168
+ throw new Error(`Bundle '${bundleName}' not found`);
169
+ }
170
+
171
+ return {
172
+ name: bundleName,
173
+ patterns: bundle.patterns,
174
+ files: bundle.files,
175
+ fileCount: bundle.files.length,
176
+ size: bundle.size,
177
+ lastGenerated: bundle.lastGenerated,
178
+ changed: bundle.changed,
179
+ content: bundle.content ? this.truncateContent(bundle.content, 2000) : null
180
+ };
181
+ } catch (error) {
182
+ throw new Error(`Failed to get bundle: ${error.message}`);
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Parse AST using existing tree-sitter infrastructure
188
+ */
189
+ async parseAST(filePath, options = {}) {
190
+ try {
191
+ const fullPath = join(this.cntxServer.CWD, filePath);
192
+
193
+ if (!existsSync(fullPath)) {
194
+ throw new Error(`File not found: ${filePath}`);
195
+ }
196
+
197
+ // Use existing semantic analysis if available
198
+ const analysis = await this.cntxServer.getSemanticAnalysis();
199
+ const fileChunks = analysis?.chunks?.filter(c => c.filePath === filePath) || [];
200
+
201
+ if (fileChunks.length > 0) {
202
+ return {
203
+ file: filePath,
204
+ chunks: fileChunks.map(chunk => ({
205
+ name: chunk.name,
206
+ type: chunk.subtype,
207
+ purpose: chunk.purpose,
208
+ startLine: chunk.startLine,
209
+ endLine: chunk.endLine,
210
+ complexity: chunk.complexity,
211
+ isExported: chunk.isExported,
212
+ isAsync: chunk.isAsync,
213
+ imports: chunk.includes?.imports || [],
214
+ dependencies: chunk.dependencies || []
215
+ }))
216
+ };
217
+ }
218
+
219
+ // Fallback: basic file info
220
+ const content = readFileSync(fullPath, 'utf8');
221
+ return {
222
+ file: filePath,
223
+ lines: content.split('\n').length,
224
+ size: content.length,
225
+ type: this.getFileType(filePath),
226
+ message: 'AST parsing requires semantic analysis to be run first'
227
+ };
228
+ } catch (error) {
229
+ throw new Error(`AST parsing failed: ${error.message}`);
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Execute safe CLI commands (restricted set)
235
+ */
236
+ async runCommand(command, options = {}) {
237
+ const { timeout = 10000, cwd } = options;
238
+
239
+ // Whitelist of safe commands
240
+ const safeCommands = [
241
+ 'ls', 'find', 'grep', 'wc', 'head', 'tail',
242
+ 'git status', 'git log', 'git diff', 'git branch',
243
+ 'npm list', 'npm outdated', 'npm audit',
244
+ 'node --version', 'npm --version'
245
+ ];
246
+
247
+ const isCommandSafe = safeCommands.some(safe => command.startsWith(safe));
248
+
249
+ if (!isCommandSafe) {
250
+ throw new Error(`Command not allowed: ${command}. Only safe read-only commands are permitted.`);
251
+ }
252
+
253
+ try {
254
+ const { stdout, stderr } = await execAsync(command, {
255
+ cwd: cwd || this.cntxServer.CWD,
256
+ timeout,
257
+ maxBuffer: 1024 * 1024 // 1MB limit
258
+ });
259
+
260
+ return {
261
+ command,
262
+ stdout: stdout.trim(),
263
+ stderr: stderr.trim(),
264
+ success: true
265
+ };
266
+ } catch (error) {
267
+ return {
268
+ command,
269
+ stdout: error.stdout || '',
270
+ stderr: error.stderr || error.message,
271
+ success: false,
272
+ error: error.message
273
+ };
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Get full semantic analysis
279
+ */
280
+ async getSemanticAnalysis(options = {}) {
281
+ try {
282
+ const analysis = await this.cntxServer.getSemanticAnalysis();
283
+
284
+ if (!analysis) {
285
+ return { message: 'No semantic analysis available. Run semantic analysis first.' };
286
+ }
287
+
288
+ const { includeCode = false, maxChunks = 50 } = options;
289
+
290
+ return {
291
+ timestamp: analysis.timestamp,
292
+ summary: analysis.summary,
293
+ chunks: analysis.chunks.slice(0, maxChunks).map(chunk => ({
294
+ ...chunk,
295
+ code: includeCode ? chunk.code : this.truncateContent(chunk.code, 200),
296
+ bundles: chunk.bundles || []
297
+ })),
298
+ totalChunks: analysis.chunks?.length || 0,
299
+ truncated: analysis.chunks?.length > maxChunks
300
+ };
301
+ } catch (error) {
302
+ throw new Error(`Failed to get semantic analysis: ${error.message}`);
303
+ }
304
+ }
305
+
306
+ // Helper methods
307
+
308
+ getFileBundles(filePath) {
309
+ const bundles = [];
310
+ for (const [bundleName, bundle] of this.cntxServer.bundles) {
311
+ if (bundle.files.includes(filePath)) {
312
+ bundles.push(bundleName);
313
+ }
314
+ }
315
+ return bundles;
316
+ }
317
+
318
+ getFileSize(filePath) {
319
+ try {
320
+ const fullPath = join(this.cntxServer.CWD, filePath);
321
+ const stats = require('fs').statSync(fullPath);
322
+ return stats.size;
323
+ } catch {
324
+ return 0;
325
+ }
326
+ }
327
+
328
+ getFileType(filePath) {
329
+ const ext = require('path').extname(filePath).toLowerCase();
330
+ const typeMap = {
331
+ '.js': 'javascript',
332
+ '.jsx': 'javascript',
333
+ '.ts': 'typescript',
334
+ '.tsx': 'typescript',
335
+ '.json': 'json',
336
+ '.md': 'markdown',
337
+ '.css': 'css',
338
+ '.html': 'html',
339
+ '.py': 'python',
340
+ '.java': 'java',
341
+ '.go': 'go',
342
+ '.rs': 'rust'
343
+ };
344
+ return typeMap[ext] || 'text';
345
+ }
346
+
347
+ getMimeType(filePath) {
348
+ const ext = require('path').extname(filePath).toLowerCase();
349
+ const mimeTypes = {
350
+ '.js': 'application/javascript',
351
+ '.jsx': 'application/javascript',
352
+ '.ts': 'application/typescript',
353
+ '.tsx': 'application/typescript',
354
+ '.json': 'application/json',
355
+ '.md': 'text/markdown',
356
+ '.css': 'text/css',
357
+ '.html': 'text/html'
358
+ };
359
+ return mimeTypes[ext] || 'text/plain';
360
+ }
361
+
362
+ truncateContent(content, maxLength = 1000) {
363
+ if (!content || content.length <= maxLength) return content;
364
+ return content.substring(0, maxLength) + '...';
365
+ }
366
+ }
367
+
368
+ export default AgentTools;