cntx-ui 3.0.8 ā 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/cntx-ui.js +89 -0
- package/dist/lib/agent-runtime.js +269 -0
- package/dist/lib/agent-tools.js +162 -0
- package/dist/lib/api-router.js +387 -0
- package/dist/lib/bundle-manager.js +236 -0
- package/dist/lib/configuration-manager.js +230 -0
- package/dist/lib/database-manager.js +277 -0
- package/dist/lib/file-system-manager.js +305 -0
- package/dist/lib/function-level-chunker.js +144 -0
- package/dist/lib/heuristics-manager.js +491 -0
- package/dist/lib/mcp-server.js +159 -0
- package/dist/lib/mcp-transport.js +10 -0
- package/dist/lib/semantic-splitter.js +335 -0
- package/dist/lib/simple-vector-store.js +98 -0
- package/dist/lib/treesitter-semantic-chunker.js +277 -0
- package/dist/lib/websocket-manager.js +268 -0
- package/dist/server.js +413 -0
- package/package.json +17 -8
- package/bin/cntx-ui-mcp.sh +0 -3
- package/bin/cntx-ui.js +0 -123
- package/lib/agent-runtime.js +0 -371
- package/lib/agent-tools.js +0 -370
- package/lib/api-router.js +0 -1026
- package/lib/bundle-manager.js +0 -326
- package/lib/configuration-manager.js +0 -760
- package/lib/database-manager.js +0 -397
- package/lib/file-system-manager.js +0 -489
- package/lib/function-level-chunker.js +0 -406
- package/lib/heuristics-manager.js +0 -529
- package/lib/mcp-server.js +0 -1380
- package/lib/mcp-transport.js +0 -97
- package/lib/semantic-splitter.js +0 -347
- package/lib/simple-vector-store.js +0 -108
- package/lib/treesitter-semantic-chunker.js +0 -1557
- package/lib/websocket-manager.js +0 -470
- package/server.js +0 -687
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, existsSync } from 'fs';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { startServer, initConfig, getStatus, setupMCP, generateBundle } from '../server.js';
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
let packagePath = join(__dirname, '..', 'package.json');
|
|
8
|
+
if (!existsSync(packagePath)) {
|
|
9
|
+
packagePath = join(__dirname, '..', '..', 'package.json');
|
|
10
|
+
}
|
|
11
|
+
const packageJson = JSON.parse(readFileSync(packagePath, 'utf8'));
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
const command = args[0] || 'help';
|
|
14
|
+
const isVerbose = args.includes('--verbose');
|
|
15
|
+
// Graceful shutdown
|
|
16
|
+
process.on('SIGINT', () => {
|
|
17
|
+
console.log('\nš Shutting down cntx-ui...');
|
|
18
|
+
process.exit(0);
|
|
19
|
+
});
|
|
20
|
+
async function main() {
|
|
21
|
+
try {
|
|
22
|
+
const actualCommand = command === 'w' ? 'watch' : command;
|
|
23
|
+
switch (actualCommand) {
|
|
24
|
+
case 'watch':
|
|
25
|
+
const port = parseInt(args[1]) || 3333;
|
|
26
|
+
// Enable MCP status tracking by default for the web dashboard
|
|
27
|
+
const withMcp = !args.includes('--no-mcp');
|
|
28
|
+
await startServer({ port, withMcp, verbose: isVerbose });
|
|
29
|
+
break;
|
|
30
|
+
case 'init':
|
|
31
|
+
console.log('š Initializing cntx-ui...');
|
|
32
|
+
await initConfig();
|
|
33
|
+
break;
|
|
34
|
+
case 'mcp':
|
|
35
|
+
await startServer({ withMcp: true, skipFileWatcher: true, skipBundleGeneration: true });
|
|
36
|
+
break;
|
|
37
|
+
case 'bundle':
|
|
38
|
+
const bundleName = args[1] || 'master';
|
|
39
|
+
try {
|
|
40
|
+
await generateBundle(bundleName);
|
|
41
|
+
console.log(`ā
Bundle '${bundleName}' generated successfully`);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
console.error(`ā Failed to generate bundle '${bundleName}': ${error.message}`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
break;
|
|
48
|
+
case 'status':
|
|
49
|
+
await getStatus();
|
|
50
|
+
break;
|
|
51
|
+
case 'setup-mcp':
|
|
52
|
+
setupMCP();
|
|
53
|
+
break;
|
|
54
|
+
case 'version':
|
|
55
|
+
case '-v':
|
|
56
|
+
case '--version':
|
|
57
|
+
console.log(`v${packageJson.version}`);
|
|
58
|
+
break;
|
|
59
|
+
case 'help':
|
|
60
|
+
default:
|
|
61
|
+
console.log(`
|
|
62
|
+
cntx-ui v${packageJson.version} - Repository Intelligence engine
|
|
63
|
+
|
|
64
|
+
Usage:
|
|
65
|
+
cntx-ui watch [port] Start the visual dashboard and intelligence engine (default: 3333)
|
|
66
|
+
cntx-ui init Initialize cntx-ui configuration in the current directory
|
|
67
|
+
cntx-ui mcp Start the Model Context Protocol (MCP) server on stdio
|
|
68
|
+
cntx-ui bundle [name] Generate specific bundle (default: master)
|
|
69
|
+
cntx-ui status Show current project status
|
|
70
|
+
cntx-ui setup-mcp Add this project to Claude Desktop MCP config
|
|
71
|
+
cntx-ui version Show current version
|
|
72
|
+
cntx-ui help Show this help information
|
|
73
|
+
|
|
74
|
+
Options:
|
|
75
|
+
--verbose Show detailed logging
|
|
76
|
+
--no-mcp Disable MCP server when running watch
|
|
77
|
+
`);
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.error(`ā Error: ${error.message}`);
|
|
83
|
+
if (isVerbose) {
|
|
84
|
+
console.error(error.stack);
|
|
85
|
+
}
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Runtime for Codebase Exploration and Development
|
|
3
|
+
* Now stateful with SQLite-based working memory
|
|
4
|
+
*/
|
|
5
|
+
import AgentTools from './agent-tools.js';
|
|
6
|
+
import crypto from 'crypto';
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
export class AgentRuntime {
|
|
11
|
+
cntxServer;
|
|
12
|
+
db;
|
|
13
|
+
tools;
|
|
14
|
+
currentSessionId;
|
|
15
|
+
constructor(cntxServer) {
|
|
16
|
+
this.cntxServer = cntxServer;
|
|
17
|
+
this.db = cntxServer.databaseManager;
|
|
18
|
+
this.tools = new AgentTools(cntxServer);
|
|
19
|
+
this.currentSessionId = null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Initialize or resume a session
|
|
23
|
+
*/
|
|
24
|
+
async startSession(id = null, title = 'New Exploration') {
|
|
25
|
+
this.currentSessionId = id || crypto.randomUUID();
|
|
26
|
+
this.db.createSession(this.currentSessionId, title);
|
|
27
|
+
// Refresh manifest when a new session starts
|
|
28
|
+
await this.generateAgentManifest();
|
|
29
|
+
return this.currentSessionId;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Generates a .cntx/AGENT.md manifest for machine consumption
|
|
33
|
+
*/
|
|
34
|
+
async generateAgentManifest() {
|
|
35
|
+
const overview = await this.getCodebaseOverview();
|
|
36
|
+
const summary = await this.getSemanticSummary();
|
|
37
|
+
const bundles = await this.analyzeBundles('all');
|
|
38
|
+
// Auto-generate tool reference from MCP server
|
|
39
|
+
let toolsReference = '';
|
|
40
|
+
if (this.cntxServer.mcpServer) {
|
|
41
|
+
const tools = this.cntxServer.mcpServer.getToolDefinitions();
|
|
42
|
+
toolsReference = tools.map(t => {
|
|
43
|
+
let params = [];
|
|
44
|
+
if (t.inputSchema?.properties) {
|
|
45
|
+
params = Object.entries(t.inputSchema.properties).map(([name, prop]) => {
|
|
46
|
+
const isReq = t.inputSchema.required?.includes(name) ? 'required' : 'optional';
|
|
47
|
+
return `\`${name}\` (${prop.type}, ${isReq}): ${prop.description}`;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return `### \`${t.name}\`\n${t.description}\n${params.length > 0 ? '**Parameters:**\n- ' + params.join('\n- ') : '*No parameters required*'}\n`;
|
|
51
|
+
}).join('\n');
|
|
52
|
+
}
|
|
53
|
+
const manifest = `# š¤ Agent Handshake: ${overview.projectPath.split('/').pop()}
|
|
54
|
+
|
|
55
|
+
## Project Overview
|
|
56
|
+
- **Path:** \`${overview.projectPath}\`
|
|
57
|
+
- **Total Files:** ${overview.totalFiles}
|
|
58
|
+
- **Semantic Intelligence:** ${summary.totalChunks} persistent chunks indexed.
|
|
59
|
+
|
|
60
|
+
## Codebase Organization (Bundles)
|
|
61
|
+
${bundles.map(b => `- **${b.name}**: ${b.purpose} (${b.fileCount} files)`).join('\n')}
|
|
62
|
+
|
|
63
|
+
## Intelligence Interface (MCP Tools)
|
|
64
|
+
You have access to a specialized "Repository Intelligence" engine. Use these tools for high-signal exploration:
|
|
65
|
+
|
|
66
|
+
${toolsReference || '*(MCP Server not yet initialized, tools will appear here)*'}
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## š Complete Tool & API Reference
|
|
71
|
+
Refer to the dynamic reference below for full parameter schemas and HTTP fallback endpoints.
|
|
72
|
+
|
|
73
|
+
${fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), '../templates/TOOLS.md'), 'utf8')}
|
|
74
|
+
|
|
75
|
+
## Working Memory
|
|
76
|
+
This agent is **stateful**. All interactions in this directory are logged to a persistent SQLite database (\`.cntx/bundles.db\`), allowing for context retention across sessions.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
*Generated automatically by cntx-ui. Optimized for LLM consumption.*
|
|
80
|
+
`;
|
|
81
|
+
const manifestPath = path.join(this.cntxServer.CNTX_DIR, 'AGENT.md');
|
|
82
|
+
fs.writeFileSync(manifestPath, manifest, 'utf8');
|
|
83
|
+
if (this.cntxServer.verbose)
|
|
84
|
+
console.log('š Agent manifest updated: .cntx/AGENT.md');
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Log an interaction to the agent's memory
|
|
88
|
+
*/
|
|
89
|
+
async logInteraction(role, content, metadata = {}) {
|
|
90
|
+
if (!this.currentSessionId)
|
|
91
|
+
await this.startSession();
|
|
92
|
+
this.db.addMessage(this.currentSessionId, role, content, metadata);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Discovery Mode: "Tell me about this codebase"
|
|
96
|
+
* Now logs the discovery process to memory
|
|
97
|
+
*/
|
|
98
|
+
async discoverCodebase(options = {}) {
|
|
99
|
+
const { scope = 'all', includeDetails = true } = options;
|
|
100
|
+
try {
|
|
101
|
+
await this.logInteraction('agent', `Starting codebase discovery for scope: ${scope}`);
|
|
102
|
+
const discovery = {
|
|
103
|
+
overview: await this.getCodebaseOverview(),
|
|
104
|
+
bundles: await this.analyzeBundles(scope),
|
|
105
|
+
architecture: await this.analyzeArchitecture(),
|
|
106
|
+
patterns: await this.identifyPatterns(),
|
|
107
|
+
recommendations: []
|
|
108
|
+
};
|
|
109
|
+
if (includeDetails) {
|
|
110
|
+
discovery.semanticSummary = await this.getSemanticSummary();
|
|
111
|
+
discovery.fileTypes = await this.analyzeFileTypes();
|
|
112
|
+
discovery.complexity = await this.analyzeComplexity();
|
|
113
|
+
}
|
|
114
|
+
discovery.recommendations = await this.generateDiscoveryRecommendations();
|
|
115
|
+
await this.logInteraction('agent', `Discovery complete. Found ${discovery.overview.totalFiles} files.`, { discovery });
|
|
116
|
+
return discovery;
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
await this.logInteraction('agent', `Discovery failed: ${error.message}`);
|
|
120
|
+
throw new Error(`Discovery failed: ${error.message}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Query Mode: "Where is the user authentication handled?"
|
|
125
|
+
* Now recalls previous context from SQLite
|
|
126
|
+
*/
|
|
127
|
+
async answerQuery(question, options = {}) {
|
|
128
|
+
const { maxResults = 10, includeCode = false } = options;
|
|
129
|
+
try {
|
|
130
|
+
await this.logInteraction('user', question);
|
|
131
|
+
// Perform semantic search via Vector Store
|
|
132
|
+
const combinedResults = await this.cntxServer.vectorStore.search(question, { limit: maxResults });
|
|
133
|
+
// Generate contextual answer
|
|
134
|
+
const answer = await this.generateContextualAnswer(question, { chunks: combinedResults, files: [] }, includeCode);
|
|
135
|
+
const response = {
|
|
136
|
+
question,
|
|
137
|
+
answer: answer.response,
|
|
138
|
+
evidence: answer.evidence,
|
|
139
|
+
confidence: answer.confidence,
|
|
140
|
+
relatedFiles: [...new Set(combinedResults.map(c => c.filePath))].slice(0, 5)
|
|
141
|
+
};
|
|
142
|
+
await this.logInteraction('agent', response.answer, { response });
|
|
143
|
+
return response;
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
throw new Error(`Query failed: ${error.message}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Feature Investigation Mode: Now persists the investigation approach
|
|
151
|
+
*/
|
|
152
|
+
async investigateFeature(featureDescription, options = {}) {
|
|
153
|
+
const { includeRecommendations = true } = options;
|
|
154
|
+
try {
|
|
155
|
+
await this.logInteraction('user', `Investigating feature: ${featureDescription}`);
|
|
156
|
+
const investigation = {
|
|
157
|
+
feature: featureDescription,
|
|
158
|
+
existing: await this.findExistingImplementations(featureDescription),
|
|
159
|
+
related: await this.findRelatedCode(featureDescription),
|
|
160
|
+
integration: await this.findIntegrationPoints(featureDescription)
|
|
161
|
+
};
|
|
162
|
+
if (includeRecommendations) {
|
|
163
|
+
investigation.approach = await this.suggestImplementationApproach(investigation);
|
|
164
|
+
}
|
|
165
|
+
await this.logInteraction('agent', `Investigation complete for ${featureDescription}`, { investigation });
|
|
166
|
+
return investigation;
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
throw new Error(`Feature investigation failed: ${error.message}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// --- Helper Methods ---
|
|
173
|
+
async getCodebaseOverview() {
|
|
174
|
+
const bundles = Array.from(this.cntxServer.bundleManager.getAllBundleInfo());
|
|
175
|
+
const totalFiles = bundles.reduce((sum, b) => sum + b.fileCount, 0);
|
|
176
|
+
const totalSize = bundles.reduce((sum, b) => sum + b.size, 0);
|
|
177
|
+
return {
|
|
178
|
+
projectPath: this.cntxServer.CWD,
|
|
179
|
+
totalBundles: bundles.length,
|
|
180
|
+
totalFiles,
|
|
181
|
+
totalSize,
|
|
182
|
+
bundleNames: bundles.map(b => b.name)
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
async analyzeBundles(scope) {
|
|
186
|
+
const bundles = this.cntxServer.bundleManager.getAllBundleInfo();
|
|
187
|
+
const filtered = scope === 'all' ? bundles : bundles.filter(b => b.name === scope);
|
|
188
|
+
return filtered.map(b => ({
|
|
189
|
+
...b,
|
|
190
|
+
purpose: this.inferBundlePurpose(b.name, b.files || [])
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
inferBundlePurpose(name, files) {
|
|
194
|
+
if (name.includes('component') || name.includes('ui'))
|
|
195
|
+
return 'UI Components';
|
|
196
|
+
if (name.includes('api') || name.includes('server'))
|
|
197
|
+
return 'Backend API';
|
|
198
|
+
return 'General Module';
|
|
199
|
+
}
|
|
200
|
+
async analyzeArchitecture() {
|
|
201
|
+
return {
|
|
202
|
+
type: 'Dynamic Architecture',
|
|
203
|
+
timestamp: new Date().toISOString()
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
async identifyPatterns() {
|
|
207
|
+
return {
|
|
208
|
+
coding: 'Modern Node.js',
|
|
209
|
+
style: 'Functional / Modular'
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
async getSemanticSummary() {
|
|
213
|
+
const chunks = this.db.db.prepare('SELECT COUNT(*) as count FROM semantic_chunks').get();
|
|
214
|
+
return { totalChunks: chunks.count };
|
|
215
|
+
}
|
|
216
|
+
async analyzeFileTypes() {
|
|
217
|
+
const rows = this.db.db.prepare('SELECT file_path FROM semantic_chunks').all();
|
|
218
|
+
const exts = {};
|
|
219
|
+
rows.forEach(r => {
|
|
220
|
+
const ext = r.file_path.split('.').pop() || 'unknown';
|
|
221
|
+
exts[ext] = (exts[ext] || 0) + 1;
|
|
222
|
+
});
|
|
223
|
+
return exts;
|
|
224
|
+
}
|
|
225
|
+
async analyzeComplexity() {
|
|
226
|
+
const rows = this.db.db.prepare('SELECT complexity_score FROM semantic_chunks').all();
|
|
227
|
+
const scores = { low: 0, medium: 0, high: 0 };
|
|
228
|
+
rows.forEach(r => {
|
|
229
|
+
if (r.complexity_score < 5)
|
|
230
|
+
scores.low++;
|
|
231
|
+
else if (r.complexity_score < 15)
|
|
232
|
+
scores.medium++;
|
|
233
|
+
else
|
|
234
|
+
scores.high++;
|
|
235
|
+
});
|
|
236
|
+
return scores;
|
|
237
|
+
}
|
|
238
|
+
async generateDiscoveryRecommendations() {
|
|
239
|
+
return [{ type: 'info', message: 'Continue organizing by semantic purpose.' }];
|
|
240
|
+
}
|
|
241
|
+
async findExistingImplementations(featureDescription) {
|
|
242
|
+
return await this.cntxServer.vectorStore.search(featureDescription, { limit: 5 });
|
|
243
|
+
}
|
|
244
|
+
async findRelatedCode(featureDescription) {
|
|
245
|
+
return [];
|
|
246
|
+
}
|
|
247
|
+
async findIntegrationPoints(featureDescription) {
|
|
248
|
+
return [];
|
|
249
|
+
}
|
|
250
|
+
async suggestImplementationApproach(investigation) {
|
|
251
|
+
return { strategy: 'TBD', description: 'Ready to plan' };
|
|
252
|
+
}
|
|
253
|
+
async generateContextualAnswer(question, results, includeCode) {
|
|
254
|
+
let response = `Based on the codebase analysis:\n\n`;
|
|
255
|
+
if (results.chunks.length > 0) {
|
|
256
|
+
const top = results.chunks[0];
|
|
257
|
+
response += `The most relevant implementation found is \`${top.name}\` in \`${top.filePath}\` (Purpose: ${top.purpose}).\n\n`;
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
response += `No direct semantic matches found. Try refining your query.`;
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
response,
|
|
264
|
+
evidence: results.chunks.slice(0, 3),
|
|
265
|
+
confidence: results.chunks.length > 0 ? 0.8 : 0.2
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
export default AgentRuntime;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Tools for Codebase Exploration
|
|
3
|
+
* Built on top of existing cntx-ui infrastructure
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, statSync } from 'fs';
|
|
6
|
+
import { join, relative } from 'path';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { exec } from 'child_process';
|
|
9
|
+
import { promisify } from 'util';
|
|
10
|
+
const execAsync = promisify(exec);
|
|
11
|
+
export default class AgentTools {
|
|
12
|
+
cntxServer;
|
|
13
|
+
constructor(cntxServer) {
|
|
14
|
+
this.cntxServer = cntxServer;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Execute a shell command within the project context
|
|
18
|
+
*/
|
|
19
|
+
async executeCommand(command) {
|
|
20
|
+
try {
|
|
21
|
+
const { stdout, stderr } = await execAsync(command, { cwd: this.cntxServer.CWD });
|
|
22
|
+
return { stdout, stderr };
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
return { error: error.message, stdout: error.stdout, stderr: error.stderr };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* List files in a directory, respecting ignore patterns
|
|
30
|
+
*/
|
|
31
|
+
async listFiles(dirPath = '.') {
|
|
32
|
+
try {
|
|
33
|
+
const absolutePath = join(this.cntxServer.CWD, dirPath);
|
|
34
|
+
if (!existsSync(absolutePath)) {
|
|
35
|
+
return { error: `Directory not found: ${dirPath}` };
|
|
36
|
+
}
|
|
37
|
+
const files = this.cntxServer.fileSystemManager.getAllFiles(absolutePath);
|
|
38
|
+
return files.map(f => relative(this.cntxServer.CWD, f));
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
return { error: error.message };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Search for code chunks based on semantic similarity or text matching
|
|
46
|
+
*/
|
|
47
|
+
async searchChunks(query, options = {}) {
|
|
48
|
+
const { maxResults = 10 } = options;
|
|
49
|
+
try {
|
|
50
|
+
let chunks = [];
|
|
51
|
+
// Try semantic search if vector store is initialized
|
|
52
|
+
if (this.cntxServer.vectorStoreInitialized) {
|
|
53
|
+
try {
|
|
54
|
+
const searchResults = await this.cntxServer.vectorStore.search(query, { limit: maxResults * 2 });
|
|
55
|
+
const chunkIds = searchResults.map(r => r.id || r.chunkId).filter(Boolean);
|
|
56
|
+
const allChunks = await this.cntxServer.getSemanticAnalysis();
|
|
57
|
+
chunks = allChunks.chunks.filter((c) => chunkIds.includes(c.id));
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.warn('Semantic search failed, falling back to text search:', error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Fallback or combine with text search if needed
|
|
64
|
+
if (chunks.length < maxResults) {
|
|
65
|
+
const allChunks = await this.cntxServer.getSemanticAnalysis();
|
|
66
|
+
const textResults = allChunks.chunks.filter((c) => c.name.toLowerCase().includes(query.toLowerCase()) ||
|
|
67
|
+
c.purpose.toLowerCase().includes(query.toLowerCase()) ||
|
|
68
|
+
c.code.toLowerCase().includes(query.toLowerCase()));
|
|
69
|
+
// Merge and deduplicate
|
|
70
|
+
const seenIds = new Set(chunks.map(c => c.id));
|
|
71
|
+
for (const chunk of textResults) {
|
|
72
|
+
if (!seenIds.has(chunk.id)) {
|
|
73
|
+
chunks.push(chunk);
|
|
74
|
+
if (chunks.length >= maxResults * 2)
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return chunks.slice(0, maxResults);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
return { error: error.message };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get technical metadata for a file
|
|
87
|
+
*/
|
|
88
|
+
async getFileMetadata(filePath) {
|
|
89
|
+
try {
|
|
90
|
+
const fullPath = join(this.cntxServer.CWD, filePath);
|
|
91
|
+
if (!existsSync(fullPath)) {
|
|
92
|
+
return { error: `File not found: ${filePath}` };
|
|
93
|
+
}
|
|
94
|
+
const stats = statSync(fullPath);
|
|
95
|
+
const chunks = this.cntxServer.databaseManager.getChunksByFile(filePath);
|
|
96
|
+
return {
|
|
97
|
+
path: filePath,
|
|
98
|
+
size: stats.size,
|
|
99
|
+
modified: stats.mtime,
|
|
100
|
+
type: this.getFileType(filePath),
|
|
101
|
+
semanticChunks: chunks.length,
|
|
102
|
+
complexity: chunks.reduce((sum, c) => sum + (c.complexity?.score || 0), 0)
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
return { error: error.message };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// --- Helper Methods ---
|
|
110
|
+
getFileSize(filePath) {
|
|
111
|
+
try {
|
|
112
|
+
const fullPath = join(this.cntxServer.CWD, filePath);
|
|
113
|
+
const stats = statSync(fullPath);
|
|
114
|
+
return stats.size;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
getFileType(filePath) {
|
|
121
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
122
|
+
const typeMap = {
|
|
123
|
+
'.js': 'javascript',
|
|
124
|
+
'.jsx': 'javascript',
|
|
125
|
+
'.ts': 'typescript',
|
|
126
|
+
'.tsx': 'typescript',
|
|
127
|
+
'.py': 'python',
|
|
128
|
+
'.rs': 'rust',
|
|
129
|
+
'.go': 'go',
|
|
130
|
+
'.java': 'java',
|
|
131
|
+
'.cpp': 'cpp',
|
|
132
|
+
'.c': 'c',
|
|
133
|
+
'.md': 'markdown',
|
|
134
|
+
'.json': 'json',
|
|
135
|
+
'.yaml': 'yaml',
|
|
136
|
+
'.yml': 'yaml',
|
|
137
|
+
'.toml': 'toml'
|
|
138
|
+
};
|
|
139
|
+
return typeMap[ext] || 'text';
|
|
140
|
+
}
|
|
141
|
+
getMimeType(filePath) {
|
|
142
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
143
|
+
const mimeTypes = {
|
|
144
|
+
'.js': 'application/javascript',
|
|
145
|
+
'.jsx': 'application/javascript',
|
|
146
|
+
'.ts': 'application/typescript',
|
|
147
|
+
'.tsx': 'application/typescript',
|
|
148
|
+
'.json': 'application/json',
|
|
149
|
+
'.md': 'text/markdown',
|
|
150
|
+
'.html': 'text/html',
|
|
151
|
+
'.css': 'text/css',
|
|
152
|
+
'.txt': 'text/plain',
|
|
153
|
+
'.rs': 'text/x-rust'
|
|
154
|
+
};
|
|
155
|
+
return mimeTypes[ext] || 'text/plain';
|
|
156
|
+
}
|
|
157
|
+
truncateContent(content, maxLength = 1000) {
|
|
158
|
+
if (!content || content.length <= maxLength)
|
|
159
|
+
return content;
|
|
160
|
+
return content.substring(0, maxLength) + '...';
|
|
161
|
+
}
|
|
162
|
+
}
|