@opichi/smartcode
Version:
Universal code intelligence MCP server - analyze any codebase with TypeScript excellence and multi-language support
226 lines • 7.94 kB
JavaScript
import { QdrantClient } from '@qdrant/js-client-rest';
export class CodeVectorStore {
client;
collectionName = 'code_nodes';
constructor(url = 'http://localhost:6333') {
this.client = new QdrantClient({ url });
}
async initialize() {
try {
// Check if collection exists
const collections = await this.client.getCollections();
const exists = collections.collections.some(c => c.name === this.collectionName);
if (!exists) {
// Create collection with appropriate vector size (384 for sentence transformers)
await this.client.createCollection(this.collectionName, {
vectors: {
size: 384,
distance: 'Cosine'
}
});
console.log(`Created Qdrant collection: ${this.collectionName}`);
}
}
catch (error) {
console.error('Failed to initialize Qdrant:', error);
throw error;
}
}
async indexNode(node) {
if (!node.embedding) {
throw new Error('Node must have embedding before indexing');
}
try {
await this.client.upsert(this.collectionName, {
wait: true,
points: [
{
id: this.hashId(node.id),
vector: node.embedding,
payload: {
id: node.id,
type: node.type,
name: node.name,
filePath: node.filePath,
startLine: node.startLine,
endLine: node.endLine,
content: node.content,
metadata: node.metadata
}
}
]
});
}
catch (error) {
console.error(`Failed to index node ${node.id}:`, error);
throw error;
}
}
async indexNodes(nodes) {
const nodesWithEmbeddings = nodes.filter(node => node.embedding);
if (nodesWithEmbeddings.length === 0) {
return;
}
try {
const points = nodesWithEmbeddings.map(node => ({
id: this.hashId(node.id),
vector: node.embedding,
payload: {
id: node.id,
type: node.type,
name: node.name,
filePath: node.filePath,
startLine: node.startLine,
endLine: node.endLine,
content: node.content,
metadata: node.metadata
}
}));
await this.client.upsert(this.collectionName, {
wait: true,
points
});
console.log(`Indexed ${points.length} code nodes`);
}
catch (error) {
console.error('Failed to index nodes:', error);
throw error;
}
}
async searchSimilar(queryEmbedding, limit = 10, filter) {
try {
const searchResult = await this.client.search(this.collectionName, {
vector: queryEmbedding,
limit,
filter,
with_payload: true,
score_threshold: 0.3 // Only return results with decent similarity
});
return searchResult.map(result => ({
node: {
id: result.payload.id,
type: result.payload.type,
name: result.payload.name,
filePath: result.payload.filePath,
startLine: result.payload.startLine,
endLine: result.payload.endLine,
content: result.payload.content,
metadata: result.payload.metadata
},
score: result.score,
context: this.generateContext(result.payload.content),
relationships: [] // Will be populated by knowledge graph
}));
}
catch (error) {
console.error('Failed to search:', error);
throw error;
}
}
async searchByType(nodeType, limit = 50) {
try {
const searchResult = await this.client.scroll(this.collectionName, {
filter: {
must: [
{
key: 'type',
match: { value: nodeType }
}
]
},
limit,
with_payload: true
});
return searchResult.points.map(point => ({
id: point.payload.id,
type: point.payload.type,
name: point.payload.name,
filePath: point.payload.filePath,
startLine: point.payload.startLine,
endLine: point.payload.endLine,
content: point.payload.content,
metadata: point.payload.metadata
}));
}
catch (error) {
console.error('Failed to search by type:', error);
throw error;
}
}
async searchByFile(filePath) {
try {
const searchResult = await this.client.scroll(this.collectionName, {
filter: {
must: [
{
key: 'filePath',
match: { value: filePath }
}
]
},
limit: 1000,
with_payload: true
});
return searchResult.points.map(point => ({
id: point.payload.id,
type: point.payload.type,
name: point.payload.name,
filePath: point.payload.filePath,
startLine: point.payload.startLine,
endLine: point.payload.endLine,
content: point.payload.content,
metadata: point.payload.metadata
}));
}
catch (error) {
console.error('Failed to search by file:', error);
throw error;
}
}
async deleteByFile(filePath) {
try {
await this.client.delete(this.collectionName, {
filter: {
must: [
{
key: 'filePath',
match: { value: filePath }
}
]
}
});
}
catch (error) {
console.error(`Failed to delete nodes for file ${filePath}:`, error);
throw error;
}
}
async clear() {
try {
await this.client.deleteCollection(this.collectionName);
await this.initialize();
}
catch (error) {
console.error('Failed to clear collection:', error);
throw error;
}
}
hashId(id) {
// Simple hash function to convert string ID to number
let hash = 0;
for (let i = 0; i < id.length; i++) {
const char = id.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32-bit integer
}
return Math.abs(hash);
}
generateContext(content) {
// Generate a brief context snippet
const lines = content.split('\n');
const firstLine = lines[0]?.trim() || '';
const preview = firstLine.length > 100 ? firstLine.substring(0, 100) + '...' : firstLine;
return preview;
}
}
//# sourceMappingURL=qdrant.js.map