cntx-ui 3.0.7 → 3.0.9

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 (36) hide show
  1. package/dist/bin/cntx-ui.js +70 -0
  2. package/dist/lib/agent-runtime.js +269 -0
  3. package/dist/lib/agent-tools.js +162 -0
  4. package/dist/lib/api-router.js +387 -0
  5. package/dist/lib/bundle-manager.js +236 -0
  6. package/dist/lib/configuration-manager.js +230 -0
  7. package/dist/lib/database-manager.js +277 -0
  8. package/dist/lib/file-system-manager.js +305 -0
  9. package/dist/lib/function-level-chunker.js +144 -0
  10. package/dist/lib/heuristics-manager.js +491 -0
  11. package/dist/lib/mcp-server.js +159 -0
  12. package/dist/lib/mcp-transport.js +10 -0
  13. package/dist/lib/semantic-splitter.js +335 -0
  14. package/dist/lib/simple-vector-store.js +98 -0
  15. package/dist/lib/treesitter-semantic-chunker.js +277 -0
  16. package/dist/lib/websocket-manager.js +268 -0
  17. package/dist/server.js +225 -0
  18. package/package.json +18 -8
  19. package/bin/cntx-ui-mcp.sh +0 -3
  20. package/bin/cntx-ui.js +0 -123
  21. package/lib/agent-runtime.js +0 -371
  22. package/lib/agent-tools.js +0 -370
  23. package/lib/api-router.js +0 -1026
  24. package/lib/bundle-manager.js +0 -326
  25. package/lib/configuration-manager.js +0 -760
  26. package/lib/database-manager.js +0 -397
  27. package/lib/file-system-manager.js +0 -489
  28. package/lib/function-level-chunker.js +0 -406
  29. package/lib/heuristics-manager.js +0 -529
  30. package/lib/mcp-server.js +0 -1380
  31. package/lib/mcp-transport.js +0 -97
  32. package/lib/semantic-splitter.js +0 -304
  33. package/lib/simple-vector-store.js +0 -108
  34. package/lib/treesitter-semantic-chunker.js +0 -1485
  35. package/lib/websocket-manager.js +0 -470
  36. package/server.js +0 -687
@@ -0,0 +1,491 @@
1
+ /**
2
+ * Heuristics Manager - Centralized service for loading and applying heuristics
3
+ * Manages configuration-based code categorization logic
4
+ */
5
+ import { readFileSync, writeFileSync, existsSync, watchFile } from 'fs';
6
+ export default class HeuristicsManager {
7
+ configPath;
8
+ config;
9
+ cache;
10
+ isWatching;
11
+ lastLoaded;
12
+ constructor(configPath = './heuristics-config.json') {
13
+ this.configPath = configPath;
14
+ this.config = null;
15
+ this.cache = new Map();
16
+ this.isWatching = false;
17
+ this.lastLoaded = null;
18
+ }
19
+ /**
20
+ * Load heuristics configuration from file
21
+ */
22
+ loadConfig() {
23
+ try {
24
+ if (!existsSync(this.configPath)) {
25
+ console.warn(`Heuristics config not found at ${this.configPath}, using fallback`);
26
+ this.config = this.getFallbackConfig();
27
+ return;
28
+ }
29
+ const configContent = readFileSync(this.configPath, 'utf8');
30
+ const newConfig = JSON.parse(configContent);
31
+ // Validate config structure
32
+ this.validateConfig(newConfig);
33
+ this.config = newConfig;
34
+ this.lastLoaded = Date.now();
35
+ this.cache.clear(); // Clear cache when config changes
36
+ // Set up file watching if not already watching
37
+ if (!this.isWatching) {
38
+ this.setupFileWatcher();
39
+ }
40
+ console.log('✅ Heuristics configuration loaded successfully');
41
+ }
42
+ catch (error) {
43
+ console.error('❌ Failed to load heuristics config:', error.message);
44
+ console.log('📦 Falling back to hardcoded heuristics');
45
+ this.config = this.getFallbackConfig();
46
+ }
47
+ }
48
+ /**
49
+ * Validate heuristics configuration structure
50
+ */
51
+ validateConfig(config) {
52
+ const required = ['purposeHeuristics', 'bundleHeuristics', 'semanticTypeMapping'];
53
+ for (const field of required) {
54
+ if (!config[field]) {
55
+ throw new Error(`Missing required field: ${field}`);
56
+ }
57
+ }
58
+ // Validate purpose heuristics structure
59
+ if (!config.purposeHeuristics.patterns || !config.purposeHeuristics.fallback) {
60
+ throw new Error('Invalid purposeHeuristics structure');
61
+ }
62
+ // Validate bundle heuristics structure
63
+ if (!config.bundleHeuristics.patterns || !config.bundleHeuristics.fallback) {
64
+ throw new Error('Invalid bundleHeuristics structure');
65
+ }
66
+ }
67
+ /**
68
+ * Set up file watcher for config changes
69
+ */
70
+ setupFileWatcher() {
71
+ if (!existsSync(this.configPath))
72
+ return;
73
+ watchFile(this.configPath, (curr, prev) => {
74
+ if (curr.mtime !== prev.mtime) {
75
+ console.log('📝 Heuristics config file changed, reloading...');
76
+ this.loadConfig();
77
+ }
78
+ });
79
+ this.isWatching = true;
80
+ }
81
+ /**
82
+ * Get configuration, loading if necessary
83
+ */
84
+ getConfig() {
85
+ if (!this.config) {
86
+ this.loadConfig();
87
+ }
88
+ return this.config;
89
+ }
90
+ /**
91
+ * Determine function purpose using configured heuristics
92
+ */
93
+ determinePurpose(func) {
94
+ const config = this.getConfig();
95
+ const name = (func.name || '').toLowerCase();
96
+ const pathParts = func.pathParts || [];
97
+ // Check each purpose pattern
98
+ for (const [patternName, pattern] of Object.entries(config.purposeHeuristics.patterns)) {
99
+ if (this.evaluateConditions(pattern.conditions, { func, name, pathParts })) {
100
+ return pattern.purpose;
101
+ }
102
+ }
103
+ // Return fallback
104
+ return config.purposeHeuristics.fallback.purpose;
105
+ }
106
+ /**
107
+ * Infer business domains (e.g. auth, editing, file-mgmt)
108
+ */
109
+ inferBusinessDomains(func) {
110
+ const domains = new Set();
111
+ const name = (func.name || '').toLowerCase();
112
+ const path = (func.pathParts || []).join('/').toLowerCase();
113
+ const imports = (func.includes?.imports || []).filter((i) => typeof i === 'string');
114
+ // Path-based domains
115
+ if (path.includes('auth'))
116
+ domains.add('authentication');
117
+ if (path.includes('component') || path.includes('ui'))
118
+ domains.add('ui-layer');
119
+ if (path.includes('service') || path.includes('api'))
120
+ domains.add('api-integration');
121
+ if (path.includes('test') || path.includes('spec'))
122
+ domains.add('testing');
123
+ // Import-based domains
124
+ if (imports.some(i => i.includes('tauri')))
125
+ domains.add('desktop-runtime');
126
+ if (imports.some(i => i.includes('tiptap') || i.includes('prosemirror')))
127
+ domains.add('text-editing');
128
+ if (imports.some(i => i.includes('react')))
129
+ domains.add('frontend-ui');
130
+ // Name-based domains
131
+ if (/file|save|export|read|write/i.test(name))
132
+ domains.add('file-management');
133
+ if (/login|user|session/i.test(name))
134
+ domains.add('authentication');
135
+ return Array.from(domains);
136
+ }
137
+ /**
138
+ * Infer technical patterns (e.g. hooks, async-io, event-handlers)
139
+ */
140
+ inferTechnicalPatterns(func) {
141
+ const patterns = new Set();
142
+ const name = (func.name || '').toLowerCase();
143
+ const code = func.code || '';
144
+ if (name.startsWith('use'))
145
+ patterns.add('react-hooks');
146
+ if (code.includes('async') || code.includes('await'))
147
+ patterns.add('async-io');
148
+ if (code.includes('on(') || code.includes('addListener') || name.startsWith('handle'))
149
+ patterns.add('event-driven');
150
+ if (code.includes('new ') || code.includes('class '))
151
+ patterns.add('object-oriented');
152
+ if (func.isExported)
153
+ patterns.add('public-api');
154
+ return Array.from(patterns);
155
+ }
156
+ /**
157
+ * Suggest bundles for file using configured heuristics
158
+ */
159
+ suggestBundlesForFile(filePath) {
160
+ const config = this.getConfig();
161
+ const fileName = filePath.toLowerCase();
162
+ const pathParts = fileName.split('/');
163
+ const suggestions = [];
164
+ // Check each bundle pattern
165
+ for (const [patternName, pattern] of Object.entries(config.bundleHeuristics.patterns)) {
166
+ if (this.evaluateConditions(pattern.conditions, { fileName, filePath, pathParts })) {
167
+ suggestions.push(pattern.bundle);
168
+ // Check sub-patterns
169
+ if (pattern.subPatterns) {
170
+ for (const [subName, subPattern] of Object.entries(pattern.subPatterns)) {
171
+ if (this.evaluateConditions(subPattern.conditions, { fileName, filePath, pathParts })) {
172
+ suggestions.push(subPattern.bundle);
173
+ }
174
+ }
175
+ }
176
+ }
177
+ }
178
+ // Apply fallback logic if no suggestions
179
+ if (suggestions.length === 0) {
180
+ const fallback = config.bundleHeuristics.fallback;
181
+ if (fallback.webFallback && this.evaluateConditions(fallback.webFallback.conditions, { fileName, filePath, pathParts })) {
182
+ suggestions.push(fallback.webFallback.bundle);
183
+ }
184
+ else if (fallback.defaultFallback) {
185
+ suggestions.push(...fallback.defaultFallback.bundles);
186
+ }
187
+ }
188
+ return [...new Set(suggestions)]; // Remove duplicates
189
+ }
190
+ /**
191
+ * Get semantic type cluster mapping
192
+ */
193
+ getSemanticTypeMapping() {
194
+ const config = this.getConfig();
195
+ const mapping = {};
196
+ for (const [clusterName, cluster] of Object.entries(config.semanticTypeMapping.clusters)) {
197
+ for (const type of cluster.types) {
198
+ mapping[type] = cluster.clusterId;
199
+ }
200
+ }
201
+ return mapping;
202
+ }
203
+ /**
204
+ * Evaluate condition strings against context
205
+ */
206
+ evaluateConditions(conditions, context) {
207
+ const conds = Array.isArray(conditions) ? conditions : [conditions];
208
+ // For purpose heuristics, we generally want high precision, so use AND logic
209
+ // if there are multiple conditions
210
+ const needsAndLogic = conds.length > 1 || this.requiresAndLogic(conds);
211
+ if (needsAndLogic) {
212
+ return conds.every(condition => {
213
+ try {
214
+ return this.evaluateCondition(condition, context);
215
+ }
216
+ catch (error) {
217
+ console.warn(`Failed to evaluate condition: ${condition}`, error);
218
+ return false;
219
+ }
220
+ });
221
+ }
222
+ else {
223
+ return conds.some(condition => {
224
+ try {
225
+ return this.evaluateCondition(condition, context);
226
+ }
227
+ catch (error) {
228
+ console.warn(`Failed to evaluate condition: ${condition}`, error);
229
+ return false;
230
+ }
231
+ });
232
+ }
233
+ }
234
+ /**
235
+ * Determine if conditions require AND logic vs OR logic
236
+ */
237
+ requiresAndLogic(conditions) {
238
+ // React hook pattern specifically needs AND logic
239
+ if (conditions.length === 2 &&
240
+ conditions.some(c => c.includes('name.startsWith')) &&
241
+ conditions.some(c => c.includes('func.type'))) {
242
+ return true;
243
+ }
244
+ // Frontend pattern needs AND logic for web + src
245
+ if (conditions.length === 2 &&
246
+ conditions.some(c => c.includes("pathParts.includes('web')")) &&
247
+ conditions.some(c => c.includes("pathParts.includes('src')"))) {
248
+ return true;
249
+ }
250
+ // Default to OR logic for other patterns
251
+ return false;
252
+ }
253
+ /**
254
+ * Evaluate a single condition
255
+ */
256
+ evaluateCondition(condition, context) {
257
+ const { func, name, fileName, filePath, pathParts } = context;
258
+ // Handle function type conditions
259
+ if (condition.includes('func.type ===')) {
260
+ const typeMatch = condition.match(/func\.type === ['"]([^'"]+)['"]/);
261
+ if (typeMatch && func) {
262
+ return func.type === typeMatch[1];
263
+ }
264
+ }
265
+ // Handle name-based conditions
266
+ if (condition.includes('name.startsWith(')) {
267
+ const prefixMatch = condition.match(/name\.startsWith\(['"]([^'"]+)['"]\)/);
268
+ if (prefixMatch && name) {
269
+ return name.startsWith(prefixMatch[1]);
270
+ }
271
+ }
272
+ if (condition.includes('name.includes(')) {
273
+ const includesMatch = condition.match(/name\.includes\(['"]([^'"]+)['"]\)/);
274
+ if (includesMatch && name) {
275
+ return name.includes(includesMatch[1]);
276
+ }
277
+ }
278
+ // Handle fileName conditions
279
+ if (condition.includes('fileName.includes(')) {
280
+ const includesMatch = condition.match(/fileName\.includes\(['"]([^'"]+)['"]\)/);
281
+ if (includesMatch && fileName) {
282
+ return fileName.includes(includesMatch[1]);
283
+ }
284
+ }
285
+ if (condition.includes('fileName.endsWith(')) {
286
+ const endsWithMatch = condition.match(/fileName\.endsWith\(['"]([^'"]+)['"]\)/);
287
+ if (endsWithMatch && fileName) {
288
+ return fileName.endsWith(endsWithMatch[1]);
289
+ }
290
+ }
291
+ // Handle pathParts conditions
292
+ if (condition.includes('pathParts.includes(')) {
293
+ const includesMatch = condition.match(/pathParts\.includes\(['"]([^'"]+)['"]\)/);
294
+ if (includesMatch && pathParts) {
295
+ return pathParts.includes(includesMatch[1]);
296
+ }
297
+ }
298
+ // New: Check imports in the chunk context
299
+ if (condition.includes('chunk.imports.includes(')) {
300
+ const importMatch = condition.match(/chunk\.imports\.includes\(['"]([^'"]+)['"]\)/);
301
+ if (importMatch && func?.includes?.imports) {
302
+ return func.includes.imports
303
+ .filter((i) => typeof i === 'string')
304
+ .some((imp) => imp.includes(importMatch[1]));
305
+ }
306
+ }
307
+ // New: Check for specific naming patterns (case-insensitive)
308
+ if (condition.includes('name.matches(')) {
309
+ const regexMatch = condition.match(/name\.matches\(['"]([^'"]+)['"]\)/);
310
+ if (regexMatch && name) {
311
+ return new RegExp(regexMatch[1], 'i').test(name);
312
+ }
313
+ }
314
+ return false;
315
+ }
316
+ /**
317
+ * Fallback configuration for when config file is unavailable
318
+ */
319
+ getFallbackConfig() {
320
+ return {
321
+ version: "1.0.0",
322
+ purposeHeuristics: {
323
+ patterns: {
324
+ reactComponent: {
325
+ conditions: ["func.type === 'react_component'"],
326
+ purpose: "React component",
327
+ confidence: 0.95
328
+ },
329
+ reactHook: {
330
+ conditions: ["name.startsWith('use')", "func.type === 'function'"],
331
+ purpose: "React hook",
332
+ confidence: 0.9
333
+ },
334
+ serviceLayer: {
335
+ conditions: ["pathParts.includes('services')"],
336
+ purpose: "Service layer logic",
337
+ confidence: 0.7
338
+ },
339
+ componentLayer: {
340
+ conditions: ["pathParts.includes('components')"],
341
+ purpose: "UI component logic",
342
+ confidence: 0.7
343
+ },
344
+ tauriCommand: {
345
+ conditions: ["name.matches('command')", "chunk.imports.includes('tauri')"],
346
+ purpose: "Tauri backend command",
347
+ confidence: 0.9
348
+ },
349
+ textEditing: {
350
+ conditions: ["chunk.imports.includes('tiptap')", "chunk.imports.includes('prosemirror')"],
351
+ purpose: "Rich text editing logic",
352
+ confidence: 0.95
353
+ },
354
+ fileManagement: {
355
+ conditions: ["pathParts.includes('services')", "name.matches('file|export|save')"],
356
+ purpose: "File system service",
357
+ confidence: 0.85
358
+ },
359
+ uiComponent: {
360
+ conditions: ["pathParts.includes('components')", "name.matches('modal|button|menu|bar')"],
361
+ purpose: "UI component logic",
362
+ confidence: 0.85
363
+ },
364
+ apiHandler: {
365
+ conditions: ["name.includes('api')", "name.includes('endpoint')"],
366
+ purpose: "API handler",
367
+ confidence: 0.85
368
+ },
369
+ dataRetrieval: {
370
+ conditions: ["name.includes('get')", "name.includes('fetch')"],
371
+ purpose: "Data retrieval",
372
+ confidence: 0.8
373
+ },
374
+ dataCreation: {
375
+ conditions: ["name.includes('create')", "name.includes('add')"],
376
+ purpose: "Data creation",
377
+ confidence: 0.8
378
+ },
379
+ dataModification: {
380
+ conditions: ["name.includes('update')", "name.includes('edit')"],
381
+ purpose: "Data modification",
382
+ confidence: 0.8
383
+ },
384
+ dataDeletion: {
385
+ conditions: ["name.includes('delete')", "name.includes('remove')"],
386
+ purpose: "Data deletion",
387
+ confidence: 0.8
388
+ },
389
+ validation: {
390
+ conditions: ["name.includes('validate')", "name.includes('check')"],
391
+ purpose: "Validation",
392
+ confidence: 0.75
393
+ },
394
+ dataProcessing: {
395
+ conditions: ["name.includes('parse')", "name.includes('format')"],
396
+ purpose: "Data processing",
397
+ confidence: 0.75
398
+ }
399
+ },
400
+ fallback: {
401
+ purpose: "Utility function",
402
+ confidence: 0.5
403
+ }
404
+ },
405
+ bundleHeuristics: {
406
+ patterns: {
407
+ frontend: {
408
+ conditions: ["pathParts.includes('web')", "pathParts.includes('src')"],
409
+ bundle: "frontend",
410
+ confidence: 0.8,
411
+ subPatterns: {
412
+ uiComponents: {
413
+ conditions: ["pathParts.includes('components')"],
414
+ bundle: "ui-components",
415
+ confidence: 0.9
416
+ }
417
+ }
418
+ },
419
+ server: {
420
+ conditions: ["fileName.includes('server')", "fileName.includes('api')", "pathParts.includes('bin')"],
421
+ bundle: "server",
422
+ confidence: 0.85
423
+ },
424
+ configuration: {
425
+ conditions: ["fileName.includes('config')", "fileName.includes('setup')", "fileName.endsWith('.json')", "fileName.endsWith('.sh')", "fileName.includes('package')"],
426
+ bundle: "config",
427
+ confidence: 0.9
428
+ },
429
+ documentation: {
430
+ conditions: ["fileName.endsWith('.md')", "fileName.includes('doc')", "fileName.includes('readme')"],
431
+ bundle: "docs",
432
+ confidence: 0.95
433
+ }
434
+ },
435
+ fallback: {
436
+ webFallback: {
437
+ conditions: ["pathParts.includes('web')"],
438
+ bundle: "frontend",
439
+ confidence: 0.6
440
+ },
441
+ defaultFallback: {
442
+ bundles: ["server", "config"],
443
+ confidence: 0.4
444
+ }
445
+ }
446
+ },
447
+ semanticTypeMapping: {
448
+ clusters: {
449
+ businessLogic: { types: ["business_logic", "algorithm"], clusterId: 0 },
450
+ dataLayer: { types: ["data_processing", "database"], clusterId: 1 },
451
+ apiLayer: { types: ["api_integration", "middleware", "routing"], clusterId: 2 },
452
+ uiLayer: { types: ["ui_component", "page_component", "layout_component", "hook"], clusterId: 3 },
453
+ utilities: { types: ["utility", "configuration", "function", "type_definition"], clusterId: 4 },
454
+ testing: { types: ["testing", "documentation", "monitoring"], clusterId: 5 },
455
+ infrastructure: { types: ["error_handling", "performance", "security"], clusterId: 6 },
456
+ unknown: { types: ["unknown"], clusterId: 7 }
457
+ }
458
+ }
459
+ };
460
+ }
461
+ /**
462
+ * Update configuration (for API endpoints) and persist to disk
463
+ */
464
+ async updateConfig(newConfig) {
465
+ try {
466
+ this.validateConfig(newConfig);
467
+ // Write to disk
468
+ writeFileSync(this.configPath, JSON.stringify(newConfig, null, 2), 'utf8');
469
+ this.config = newConfig;
470
+ this.cache.clear();
471
+ this.lastLoaded = Date.now();
472
+ console.log('📝 Heuristics configuration updated and saved to disk');
473
+ return true;
474
+ }
475
+ catch (error) {
476
+ console.error('❌ Failed to update heuristics config:', error.message);
477
+ throw error;
478
+ }
479
+ }
480
+ /**
481
+ * Get performance metrics
482
+ */
483
+ getPerformanceMetrics() {
484
+ // TODO: Implement performance tracking
485
+ return {
486
+ totalEvaluations: 0,
487
+ accuracyScore: 0.0,
488
+ lastUpdated: this.lastLoaded
489
+ };
490
+ }
491
+ }
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Model Context Protocol (MCP) Server Implementation
3
+ * Exposes repository intelligence tools to AI agents
4
+ */
5
+ import { readFileSync } from 'fs';
6
+ import { join } from 'path';
7
+ export class MCPServer {
8
+ cntxServer;
9
+ constructor(cntxServer) {
10
+ this.cntxServer = cntxServer;
11
+ // Listen for MCP requests on stdin
12
+ process.stdin.on('data', (data) => {
13
+ this.handleInput(data.toString());
14
+ });
15
+ }
16
+ handleInput(input) {
17
+ try {
18
+ const lines = input.split('\n').filter(l => l.trim());
19
+ for (const line of lines) {
20
+ const request = JSON.parse(line);
21
+ this.routeRequest(request);
22
+ }
23
+ }
24
+ catch (e) {
25
+ // Ignore invalid JSON
26
+ }
27
+ }
28
+ async routeRequest(request) {
29
+ const { method, params, id } = request;
30
+ switch (method) {
31
+ case 'initialize':
32
+ return this.sendResponse(this.createSuccessResponse(id, {
33
+ protocolVersion: '2024-11-05',
34
+ capabilities: {
35
+ tools: {},
36
+ resources: {},
37
+ prompts: {}
38
+ },
39
+ serverInfo: { name: 'cntx-ui', version: '3.0.0' }
40
+ }));
41
+ case 'tools/list':
42
+ return this.sendResponse(this.handleListTools(id));
43
+ case 'tools/call':
44
+ return this.sendResponse(await this.handleCallTool(params, id));
45
+ case 'resources/list':
46
+ return this.sendResponse(this.handleListResources(id));
47
+ case 'prompts/list':
48
+ return this.sendResponse(this.handleListPrompts(id));
49
+ case 'prompts/get':
50
+ return this.sendResponse(this.handleGetPrompt(params, id));
51
+ default:
52
+ return this.sendResponse(this.createErrorResponse(id, -32601, `Method not found: ${method}`));
53
+ }
54
+ }
55
+ sendResponse(response) {
56
+ process.stdout.write(JSON.stringify(response) + '\n');
57
+ }
58
+ createSuccessResponse(id, result) {
59
+ return { jsonrpc: '2.0', id, result };
60
+ }
61
+ createErrorResponse(id, code, message, data = null) {
62
+ const error = { code, message };
63
+ if (data)
64
+ error.data = data;
65
+ return { jsonrpc: '2.0', id, error };
66
+ }
67
+ // --- Handlers ---
68
+ handleListTools(id) {
69
+ const tools = this.getToolDefinitions();
70
+ return this.createSuccessResponse(id, { tools });
71
+ }
72
+ async handleCallTool(params, id) {
73
+ const { name, arguments: args } = params;
74
+ try {
75
+ if (name.startsWith('agent/')) {
76
+ const mode = name.split('/')[1];
77
+ let result;
78
+ switch (mode) {
79
+ case 'discover':
80
+ result = await this.cntxServer.agentRuntime.discoverCodebase(args);
81
+ break;
82
+ case 'query':
83
+ result = await this.cntxServer.agentRuntime.answerQuery(args.question, args);
84
+ break;
85
+ case 'investigate':
86
+ result = await this.cntxServer.agentRuntime.investigateFeature(args.feature, args);
87
+ break;
88
+ default:
89
+ return this.createErrorResponse(id, -32602, `Unknown agent tool: ${name}`);
90
+ }
91
+ return this.createSuccessResponse(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
92
+ }
93
+ // Legacy tools mapping
94
+ switch (name) {
95
+ case 'list_bundles':
96
+ const bundles = this.cntxServer.bundleManager.getAllBundleInfo();
97
+ return this.createSuccessResponse(id, { content: [{ type: 'text', text: JSON.stringify(bundles, null, 2) }] });
98
+ case 'read_file':
99
+ return await this.toolReadFile(args, id);
100
+ default:
101
+ return this.createErrorResponse(id, -32602, `Tool not found: ${name}`);
102
+ }
103
+ }
104
+ catch (e) {
105
+ return this.createErrorResponse(id, -32603, e.message);
106
+ }
107
+ }
108
+ async toolReadFile(args, id) {
109
+ const { path: filePath } = args;
110
+ try {
111
+ const fullPath = join(this.cntxServer.CWD, filePath);
112
+ const content = readFileSync(fullPath, 'utf8');
113
+ return this.createSuccessResponse(id, {
114
+ content: [{ type: 'text', text: content }]
115
+ });
116
+ }
117
+ catch (e) {
118
+ return this.createErrorResponse(id, -32603, e.message);
119
+ }
120
+ }
121
+ handleListResources(id) {
122
+ return this.createSuccessResponse(id, { resources: [] });
123
+ }
124
+ handleListPrompts(id) {
125
+ return this.createSuccessResponse(id, { prompts: [] });
126
+ }
127
+ handleGetPrompt(params, id) {
128
+ return this.createErrorResponse(id, -32602, 'Prompt not found');
129
+ }
130
+ getToolDefinitions() {
131
+ return [
132
+ {
133
+ name: 'agent/discover',
134
+ description: 'Discovery Mode: Comprehensive architectural overview.',
135
+ inputSchema: { type: 'object', properties: { scope: { type: 'string' } } }
136
+ },
137
+ {
138
+ name: 'agent/query',
139
+ description: 'Query Mode: Answer technical questions.',
140
+ inputSchema: { type: 'object', properties: { question: { type: 'string' } }, required: ['question'] }
141
+ },
142
+ {
143
+ name: 'agent/investigate',
144
+ description: 'Investigation Mode: Suggest integration points.',
145
+ inputSchema: { type: 'object', properties: { feature: { type: 'string' } }, required: ['feature'] }
146
+ },
147
+ {
148
+ name: 'list_bundles',
149
+ description: 'List all project bundles.',
150
+ inputSchema: { type: 'object', properties: {} }
151
+ },
152
+ {
153
+ name: 'read_file',
154
+ description: 'Read a file.',
155
+ inputSchema: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] }
156
+ }
157
+ ];
158
+ }
159
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * MCP Stdio Transport
3
+ * Handles the low-level communication for the Model Context Protocol
4
+ */
5
+ import { MCPServer } from './mcp-server.js';
6
+ export function startMCPTransport(cntxServer) {
7
+ // The MCPServer constructor already sets up stdin listener
8
+ const mcpServer = new MCPServer(cntxServer);
9
+ return mcpServer;
10
+ }