Files
wehub-resource-sync 23f7624596
ADR-166 MCP Bridge Security Lock / Static-source security lock (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / Compose default binds loopback + Mongo has auth (push) Failing after 2s
CodeQL Advanced / Analyze (rust) (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / plugin-agent-federation bindHost default (push) Failing after 1s
ADR-166 MCP Bridge Security Lock / Runtime behavior — 401 + terminal gate + fail-closed (push) Failing after 4s
business-pods-smoke / smoke (push) Failing after 1s
all-plugins-smoke / smoke-all (push) Failing after 2s
CI/CD Pipeline / Security & Code Quality (push) Failing after 1s
CI/CD Pipeline / Test Suite (ubuntu-latest) (push) Failing after 1s
CI/CD Pipeline / Build & Package (macos-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (ubuntu-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (windows-latest) (push) Has been skipped
CI/CD Pipeline / Documentation & Examples (push) Failing after 1s
Clone Tracker (14-day rolling) / Snapshot clones for ruflo ecosystem (push) Failing after 1s
CodeQL Advanced / Analyze (actions) (push) Failing after 1s
CodeQL Advanced / Analyze (javascript-typescript) (push) Failing after 1s
federation-peer-rust / stable-noop (push) Failing after 1s
metaharness-ci / score (push) Failing after 1s
metaharness-ci / router-compat (push) Failing after 0s
metaharness-ci / similarity-tests (push) Failing after 0s
no-agentbbs-smoke / smoke-without-agentbbs (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (windows-latest) (push) Has been skipped
codex-integration-audit / Codex integration audit (push) Failing after 1s
helpers-manifest-guard / guard (push) Failing after 1s
🔗 Cross-Agent Integration Tests / 🤝 Agent Coordination Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🧠 Memory Sharing Integration (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🛡️ Fault Tolerance Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / ⚡ Performance Integration Tests (push) Has been skipped
metaharness-ci / mcp-scan (push) Failing after 1s
metaharness-ci / eject-dryrun (push) Failing after 1s
metaharness-ci / metaharness-real-data (push) Failing after 0s
no-cli-optdep-bloat-2561 / guard (push) Failing after 1s
no-metaharness-smoke / smoke-without-metaharness (push) Failing after 1s
no-phantom-agentic-flow-subpath / guard (push) Failing after 1s
🔄 Automated Rollback Manager / 🚨 Failure Detection (push) Failing after 1s
V3 CI/CD Pipeline / Plugin hooks smoke / ubuntu-latest / Node 22 (push) Failing after 1s
V3 CI/CD Pipeline / ruflo-graph-intelligence build + test smoke (#2044, ADR-123) (push) Failing after 1s
CVE Audit Gate / Audit root (critical-blocking) (push) Failing after 2s
cost-tracker-smoke / smoke (push) Failing after 3s
oia-audit-weekly / audit (push) Failing after 2s
ruflo-agent-smoke / ruflo-agent structural smoke (push) Failing after 1s
📊 Status Badges Update / 📊 Update Status Badges (push) Failing after 1s
V3 CI/CD Pipeline / Static regression guards (#2267 YAML + (push) Failing after 1s
V3 CI/CD Pipeline / Test V3 Packages (push) Failing after 0s
V3 CI/CD Pipeline / agent_execute provider routing smoke (#2042) (push) Failing after 0s
CVE Audit Gate / Audit v3 (critical-blocking) (push) Failing after 1s
federation-peer-rust / stable-native (push) Failing after 2s
🔗 Cross-Agent Integration Tests / 🚀 Integration Test Setup (push) Failing after 2s
neural-trader-smoke / runtime-smoke (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (macos-latest) (push) Has been skipped
V3 CI/CD Pipeline / Build V3 (ubuntu-latest) (push) Has been skipped
V3 CI/CD Pipeline / Type Check V3 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 24 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 22 (push) Failing after 2s
V3 CI/CD Pipeline / browser rvf create flag smoke (#2015) (push) Failing after 0s
V3 CI/CD Pipeline / Dependency review (#2046) (push) Has been skipped
V3 CI/CD Pipeline / Supply-chain audit (#2046) (push) Failing after 0s
V3 CI/CD Pipeline / witness marker drift smoke (#2021) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader portfolio CG smoke (#2068, ADR-126 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader backtest signing smoke (#2068, ADR-126 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / kg-extract type-import classification smoke (#2049) (push) Failing after 0s
V3 CI/CD Pipeline / witness verify precondition smoke (#1880) (push) Failing after 2s
V3 CI/CD Pipeline / neural-trader pipeline risk-gate smoke (#2068, ADR-126 Phase 5) (push) Failing after 0s
V3 CI/CD Pipeline / neural-trader feature attribution smoke (#2068, ADR-126 Phase 6) (push) Failing after 0s
V3 CI/CD Pipeline / plugin-registry signature verification smoke (#1922, CWE-347) (push) Failing after 4s
V3 CI/CD Pipeline / memory stats legacy-DB smoke (#2120) (push) Failing after 4s
V3 CI/CD Pipeline / github deprecated actions smoke (#2089, ADR-127 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / graph query + pathfinder smoke (ADR-130 P2+P5) (push) Has been skipped
V3 CI/CD Pipeline / graph trajectory hooks smoke (ADR-130 P3) (push) Has been skipped
V3 CI/CD Pipeline / graph plugin adapter smoke (ADR-130 P4) (push) Has been skipped
V3 CI/CD Pipeline / graph benchmark (ADR-130 P6) (push) Has been skipped
V3 CI/CD Pipeline / statusline generator delegation smoke (#2195) (push) Failing after 1s
V3 CI/CD Pipeline / wizard init regression guard (#2206 (push) Failing after 1s
V3 CI/CD Pipeline / memory no-stray-db smoke (ADR-125 P7) (push) Failing after 1s
V3 CI/CD Pipeline / github-safe injection smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github actions pin smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github attribution opt-in smoke (#2089, ADR-127 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / pre-bash hook safety smoke (#2017) (push) Failing after 1s
V3 CI/CD Pipeline / Memory import smoke / ubuntu-latest (push) Failing after 0s
V3 CI/CD Pipeline / MCP protocol smoke / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / ruvllm WASM auto-init smoke (#2086) (push) Failing after 4s
V3 CI/CD Pipeline / MCP paired-tool round-trip smoke (#1889) (push) Failing after 1s
V3 CI/CD Pipeline / Plugin package install-safety (#1902/#1903/#1904) (push) Failing after 1s
V3 CI/CD Pipeline / Tool description discoverability (ADR-112) (push) Failing after 3s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (22) (push) Failing after 1s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (24) (push) Failing after 1s
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Vector-index dimension audit (#1947) (push) Failing after 0s
V3 CI/CD Pipeline / Hook-command install safety (#1921) (push) Failing after 1s
V3 CI/CD Pipeline / ToolOutputGuardrail smoke (ADR-131, (push) Failing after 1s
V3 CI/CD Pipeline / init-bundle invariants smoke (#2095, ADR-128 Phase 5) (push) Failing after 1s
V3 CI/CD Pipeline / wasm provider bridge smoke (ADR-129 P1) (push) Failing after 2s
V3 CI/CD Pipeline / wasm gallery CRUD smoke (ADR-129 P3) (push) Failing after 1s
V3 CI/CD Pipeline / wasm plugin bridge smoke (ADR-129 P4) (push) Failing after 0s
V3 CI/CD Pipeline / wasm compose smoke (ADR-129 P2) (push) Failing after 4s
V3 CI/CD Pipeline / graph schema smoke (ADR-130 P1) (push) Failing after 0s
Validate Marketplace / validate (push) Failing after 1s
🔍 Verification Pipeline / 🚀 Setup Verification (push) Failing after 1s
🔍 Verification Pipeline / 🛡️ Security Verification (push) Has been skipped
🔍 Verification Pipeline / 📝 Code Quality (push) Has been skipped
🔍 Verification Pipeline / 🧪 Test Verification (${{ matrix.os }}, Node ${{ matrix.node }}) (push) Has been skipped
🔍 Verification Pipeline / 🏗️ Build Verification (push) Has been skipped
🔍 Verification Pipeline / 📚 Documentation Verification (push) Has been skipped
CVE Audit Gate / High-severity report (warn only) (push) Has been cancelled
🔄 Automated Rollback Manager / 🔄 Execute Rollback (push) Has been cancelled
🔄 Automated Rollback Manager / ✅ Post-Rollback Verification (push) Has been cancelled
🔄 Automated Rollback Manager / 📊 Rollback Monitoring (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / windows-latest (push) Has been cancelled
🔄 Automated Rollback Manager / ⏳ Manual Rollback Approval (push) Has been cancelled
V3 CI/CD Pipeline / MCP protocol smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Memory import smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / ubuntu-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Publish to npm (alpha) (push) Has been cancelled
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / macos-latest / Node 22 (push) Has been cancelled
V3 CI/CD Pipeline / Plugin hooks smoke / macos-latest / Node 22 (push) Has been cancelled
CI/CD Pipeline / Deploy & Release (push) Has been cancelled
CI/CD Pipeline / CI Status (push) Has been cancelled
🔗 Cross-Agent Integration Tests / 📊 Integration Test Report (push) Has been cancelled
🔄 Automated Rollback Manager / 🔍 Pre-Rollback Validation (push) Has been cancelled
🔍 Verification Pipeline / ⚡ Performance Verification (push) Has been cancelled
🔍 Verification Pipeline / 📊 Verification Report (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:02:19 +08:00

499 lines
18 KiB
TypeScript

/**
* Semantic Code Search Plugin
*
* Index and search code semantically using @ruvector/wasm vector database.
* Enables natural language queries like "find functions that handle authentication".
*
* Features:
* - Index code chunks with embeddings
* - Natural language code search (<1ms with HNSW)
* - Symbol-aware chunking
* - Language-specific tokenization
* - Incremental indexing
*
* @example
* ```typescript
* import { semanticCodeSearchPlugin } from '@claude-flow/plugins/examples/ruvector-plugins';
* await getDefaultRegistry().register(semanticCodeSearchPlugin);
* ```
*/
import {
PluginBuilder,
MCPToolBuilder,
HookBuilder,
HookEvent,
HookPriority,
Security,
} from '../../src/index.js';
// Import shared vector utilities (consolidated from all plugins)
import {
IVectorDB,
createVectorDB,
generateHashEmbedding,
} from './shared/vector-utils.js';
// ============================================================================
// Types
// ============================================================================
export interface CodeChunk {
id: string;
filePath: string;
content: string;
language: string;
startLine: number;
endLine: number;
symbolType?: 'function' | 'class' | 'method' | 'variable' | 'import' | 'type' | 'interface';
symbolName?: string;
embedding?: Float32Array;
metadata: {
size: number;
hash: string;
indexedAt: Date;
dependencies?: string[];
};
}
export interface CodeSearchResult {
chunk: CodeChunk;
similarity: number;
relevance: number;
highlights?: string[];
}
export interface CodeSearchOptions {
k?: number;
minSimilarity?: number;
languages?: string[];
symbolTypes?: CodeChunk['symbolType'][];
filePaths?: string[];
includeContent?: boolean;
}
// ============================================================================
// Semantic Code Search Core
// ============================================================================
export class SemanticCodeSearch {
private vectorDb: IVectorDB | null = null;
private chunks = new Map<string, CodeChunk>();
private fileIndex = new Map<string, Set<string>>();
private dimensions: number;
private nextId = 1;
private initPromise: Promise<void> | null = null;
constructor(dimensions: number = 768) {
this.dimensions = dimensions;
}
async initialize(): Promise<void> {
if (this.vectorDb) return;
if (this.initPromise) return this.initPromise;
this.initPromise = (async () => {
this.vectorDb = await createVectorDB(this.dimensions);
})();
return this.initPromise;
}
private async ensureInitialized(): Promise<IVectorDB> {
await this.initialize();
return this.vectorDb!;
}
/**
* Index a code file by chunking and embedding.
*/
async indexFile(filePath: string, content: string, language: string): Promise<string[]> {
const db = await this.ensureInitialized();
const safePath = Security.validateString(filePath, { maxLength: 500 });
const safeContent = Security.validateString(content, { maxLength: 1_000_000 });
const safeLang = Security.validateString(language, { maxLength: 50 });
await this.removeFile(safePath);
const chunks = this.chunkCode(safeContent, safeLang, safePath);
const chunkIds: string[] = [];
for (const chunk of chunks) {
const id = `code-${this.nextId++}`;
const embedding = this.generateCodeEmbedding(chunk.content, chunk.symbolName, chunk.symbolType);
const fullChunk: CodeChunk = { ...chunk, id, embedding };
db.insert(embedding, id, {
filePath: chunk.filePath,
language: chunk.language,
symbolType: chunk.symbolType,
symbolName: chunk.symbolName,
startLine: chunk.startLine,
endLine: chunk.endLine,
});
this.chunks.set(id, fullChunk);
if (!this.fileIndex.has(safePath)) {
this.fileIndex.set(safePath, new Set());
}
this.fileIndex.get(safePath)!.add(id);
chunkIds.push(id);
}
return chunkIds;
}
/**
* Search code using natural language query (<1ms with HNSW).
*/
async search(query: string, options?: CodeSearchOptions): Promise<CodeSearchResult[]> {
const db = await this.ensureInitialized();
const safeQuery = Security.validateString(query, { maxLength: 1000 });
const k = options?.k ?? 10;
const minSimilarity = options?.minSimilarity ?? 0.3;
const queryEmbedding = this.generateQueryEmbedding(safeQuery);
const searchResults = db.search(queryEmbedding, k * 2);
const results: CodeSearchResult[] = [];
for (const result of searchResults) {
if (result.score < minSimilarity) continue;
const chunk = this.chunks.get(result.id);
if (!chunk) continue;
if (options?.languages && !options.languages.includes(chunk.language)) continue;
if (options?.symbolTypes && chunk.symbolType && !options.symbolTypes.includes(chunk.symbolType)) continue;
if (options?.filePaths) {
const matchesPath = options.filePaths.some(p => chunk.filePath.includes(p));
if (!matchesPath) continue;
}
const relevance = this.calculateRelevance(chunk, safeQuery);
const highlights = this.findHighlights(chunk.content, safeQuery);
results.push({
chunk: options?.includeContent === false ? { ...chunk, content: '' } : chunk,
similarity: result.score,
relevance,
highlights,
});
if (results.length >= k) break;
}
return results.sort((a, b) => (b.similarity * b.relevance) - (a.similarity * a.relevance));
}
/**
* Find similar code to a given snippet.
*/
async findSimilar(code: string, k: number = 5): Promise<CodeSearchResult[]> {
const db = await this.ensureInitialized();
const safeCode = Security.validateString(code, { maxLength: 10000 });
const embedding = this.generateCodeEmbedding(safeCode);
const searchResults = db.search(embedding, k);
return searchResults.map(r => {
const chunk = this.chunks.get(r.id)!;
return { chunk, similarity: r.score, relevance: 1 };
}).filter(r => r.chunk);
}
/**
* Remove all chunks for a file.
*/
async removeFile(filePath: string): Promise<number> {
const db = await this.ensureInitialized();
const chunkIds = this.fileIndex.get(filePath);
if (!chunkIds) return 0;
let removed = 0;
for (const id of chunkIds) {
db.delete(id);
this.chunks.delete(id);
removed++;
}
this.fileIndex.delete(filePath);
return removed;
}
/**
* Get index statistics.
*/
getStats(): {
totalChunks: number;
totalFiles: number;
byLanguage: Record<string, number>;
bySymbolType: Record<string, number>;
} {
const byLanguage: Record<string, number> = {};
const bySymbolType: Record<string, number> = {};
for (const chunk of this.chunks.values()) {
byLanguage[chunk.language] = (byLanguage[chunk.language] ?? 0) + 1;
if (chunk.symbolType) {
bySymbolType[chunk.symbolType] = (bySymbolType[chunk.symbolType] ?? 0) + 1;
}
}
return {
totalChunks: this.chunks.size,
totalFiles: this.fileIndex.size,
byLanguage,
bySymbolType,
};
}
// =========================================================================
// Private Helpers
// =========================================================================
private chunkCode(content: string, language: string, filePath: string): Omit<CodeChunk, 'id' | 'embedding'>[] {
const chunks: Omit<CodeChunk, 'id' | 'embedding'>[] = [];
const lines = content.split('\n');
const patterns = this.getLanguagePatterns(language);
let currentChunk: string[] = [];
let chunkStartLine = 1;
let currentSymbol: { type: CodeChunk['symbolType']; name: string } | null = null;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const lineNum = i + 1;
const symbol = this.detectSymbol(line, patterns);
if (symbol && currentChunk.length > 0) {
chunks.push(this.createChunk(currentChunk.join('\n'), filePath, language, chunkStartLine, lineNum - 1, currentSymbol));
currentChunk = [];
chunkStartLine = lineNum;
}
if (symbol) currentSymbol = symbol;
currentChunk.push(line);
if (currentChunk.length >= 50 && !this.isInsideBlock(currentChunk)) {
chunks.push(this.createChunk(currentChunk.join('\n'), filePath, language, chunkStartLine, lineNum, currentSymbol));
currentChunk = [];
chunkStartLine = lineNum + 1;
currentSymbol = null;
}
}
if (currentChunk.length > 0) {
chunks.push(this.createChunk(currentChunk.join('\n'), filePath, language, chunkStartLine, lines.length, currentSymbol));
}
return chunks;
}
private createChunk(
content: string, filePath: string, language: string, startLine: number, endLine: number,
symbol: { type: CodeChunk['symbolType']; name: string } | null
): Omit<CodeChunk, 'id' | 'embedding'> {
return {
filePath, content, language, startLine, endLine,
symbolType: symbol?.type, symbolName: symbol?.name,
metadata: { size: content.length, hash: this.hashString(content), indexedAt: new Date() },
};
}
private getLanguagePatterns(language: string): RegExp[] {
const patterns: Record<string, RegExp[]> = {
typescript: [/^\s*(export\s+)?(async\s+)?function\s+(\w+)/, /^\s*(export\s+)?class\s+(\w+)/, /^\s*(export\s+)?interface\s+(\w+)/, /^\s*(export\s+)?type\s+(\w+)/],
javascript: [/^\s*(export\s+)?(async\s+)?function\s+(\w+)/, /^\s*(export\s+)?class\s+(\w+)/],
python: [/^\s*def\s+(\w+)/, /^\s*class\s+(\w+)/],
rust: [/^\s*(pub\s+)?fn\s+(\w+)/, /^\s*(pub\s+)?struct\s+(\w+)/, /^\s*(pub\s+)?enum\s+(\w+)/],
go: [/^\s*func\s+(\w+)/, /^\s*type\s+(\w+)\s+struct/],
};
return patterns[language] ?? patterns.typescript;
}
private detectSymbol(line: string, patterns: RegExp[]): { type: CodeChunk['symbolType']; name: string } | null {
for (const pattern of patterns) {
const match = line.match(pattern);
if (match) {
const name = match[match.length - 1] || match[2] || match[1];
let type: CodeChunk['symbolType'] = 'function';
if (line.includes('class ')) type = 'class';
else if (line.includes('interface ')) type = 'interface';
else if (line.includes('type ')) type = 'type';
return { type, name };
}
}
return null;
}
private isInsideBlock(lines: string[]): boolean {
let braceCount = 0;
for (const line of lines) {
braceCount += (line.match(/{/g) || []).length - (line.match(/}/g) || []).length;
}
return braceCount > 0;
}
private generateCodeEmbedding(code: string, symbolName?: string, symbolType?: string): Float32Array {
const text = `${symbolType || ''} ${symbolName || ''} ${code}`.toLowerCase();
const embedding = new Float32Array(this.dimensions);
let hash = 0;
for (let i = 0; i < text.length; i++) { hash = ((hash << 5) - hash) + text.charCodeAt(i); hash = hash & hash; }
for (let i = 0; i < this.dimensions; i++) { embedding[i] = Math.sin(hash * (i + 1) * 0.001) * 0.5 + 0.5; }
let norm = 0;
for (let i = 0; i < this.dimensions; i++) norm += embedding[i] * embedding[i];
norm = Math.sqrt(norm);
for (let i = 0; i < this.dimensions; i++) embedding[i] /= norm;
return embedding;
}
private generateQueryEmbedding(query: string): Float32Array {
return this.generateCodeEmbedding(query);
}
private calculateRelevance(chunk: CodeChunk, query: string): number {
let relevance = 0.5;
const queryLower = query.toLowerCase();
const symbolLower = chunk.symbolName?.toLowerCase() ?? '';
if (symbolLower && queryLower.includes(symbolLower)) relevance += 0.3;
if (symbolLower && symbolLower.includes(queryLower.split(' ')[0])) relevance += 0.2;
return Math.min(1, relevance);
}
private findHighlights(content: string, query: string): string[] {
const highlights: string[] = [];
const keywords = query.toLowerCase().split(/\s+/).filter(k => k.length > 2);
const lines = content.split('\n');
for (const line of lines) {
for (const keyword of keywords) {
if (line.toLowerCase().includes(keyword)) { highlights.push(line.trim()); break; }
}
if (highlights.length >= 3) break;
}
return highlights;
}
private hashString(str: string): string {
let hash = 0;
for (let i = 0; i < str.length; i++) { hash = ((hash << 5) - hash) + str.charCodeAt(i); hash = hash & hash; }
return hash.toString(16);
}
}
// ============================================================================
// Plugin Definition
// ============================================================================
let searchInstance: SemanticCodeSearch | null = null;
async function getCodeSearch(): Promise<SemanticCodeSearch> {
if (!searchInstance) {
searchInstance = new SemanticCodeSearch(768);
await searchInstance.initialize();
}
return searchInstance;
}
export const semanticCodeSearchPlugin = new PluginBuilder('semantic-code-search', '1.0.0')
.withDescription('Semantic code search using @ruvector/wasm HNSW indexing (<1ms search)')
.withAuthor('Claude Flow Team')
.withTags(['search', 'code', 'semantic', 'ruvector', 'hnsw'])
.withMCPTools([
new MCPToolBuilder('code-index')
.withDescription('Index a code file for semantic search')
.addStringParam('filePath', 'Path to the file', { required: true })
.addStringParam('content', 'File content', { required: true })
.addStringParam('language', 'Programming language', { required: true })
.withHandler(async (params) => {
try {
const search = await getCodeSearch();
const ids = await search.indexFile(params.filePath as string, params.content as string, params.language as string);
return { content: [{ type: 'text', text: `✅ Indexed ${params.filePath}\nCreated ${ids.length} searchable chunks` }] };
} catch (error) {
return { content: [{ type: 'text', text: `❌ Error: ${error instanceof Error ? error.message : String(error)}` }], isError: true };
}
})
.build(),
new MCPToolBuilder('code-search')
.withDescription('Search code using natural language (<1ms with HNSW)')
.addStringParam('query', 'Natural language search query', { required: true })
.addNumberParam('k', 'Number of results', { default: 5 })
.addNumberParam('minSimilarity', 'Minimum similarity', { default: 0.3 })
.withHandler(async (params) => {
try {
const search = await getCodeSearch();
const results = await search.search(params.query as string, { k: params.k as number, minSimilarity: params.minSimilarity as number });
if (results.length === 0) return { content: [{ type: 'text', text: '🔍 No matching code found.' }] };
const output = results.map((r, i) => `**${i + 1}. ${r.chunk.filePath}:${r.chunk.startLine}** (${(r.similarity * 100).toFixed(1)}%)\n ${r.chunk.symbolType ? `[${r.chunk.symbolType}] ` : ''}${r.chunk.symbolName || ''}`).join('\n\n');
return { content: [{ type: 'text', text: `🔍 **Found ${results.length} matches:**\n\n${output}` }] };
} catch (error) {
return { content: [{ type: 'text', text: `❌ Error: ${error instanceof Error ? error.message : String(error)}` }], isError: true };
}
})
.build(),
new MCPToolBuilder('code-similar')
.withDescription('Find code similar to a given snippet')
.addStringParam('code', 'Code snippet', { required: true })
.addNumberParam('k', 'Number of results', { default: 5 })
.withHandler(async (params) => {
try {
const search = await getCodeSearch();
const results = await search.findSimilar(params.code as string, params.k as number);
if (results.length === 0) return { content: [{ type: 'text', text: '🔍 No similar code found.' }] };
const output = results.map((r, i) => `**${i + 1}. ${r.chunk.filePath}:${r.chunk.startLine}** (${(r.similarity * 100).toFixed(1)}% similar)`).join('\n');
return { content: [{ type: 'text', text: `🔍 **Similar code:**\n\n${output}` }] };
} catch (error) {
return { content: [{ type: 'text', text: `❌ Error: ${error instanceof Error ? error.message : String(error)}` }], isError: true };
}
})
.build(),
new MCPToolBuilder('code-stats')
.withDescription('Get code index statistics')
.withHandler(async () => {
const search = await getCodeSearch();
const stats = search.getStats();
return { content: [{ type: 'text', text: `📊 **Code Index:**\n\n**Chunks:** ${stats.totalChunks}\n**Files:** ${stats.totalFiles}\n**Backend:** @ruvector/wasm HNSW` }] };
})
.build(),
])
.withHooks([
new HookBuilder(HookEvent.PostFileWrite)
.withName('code-auto-index')
.withDescription('Auto-index code files on write')
.withPriority(HookPriority.Low)
.when((ctx) => {
const data = ctx.data as { filePath?: string } | undefined;
if (!data?.filePath) return false;
const ext = data.filePath.split('.').pop()?.toLowerCase();
return ['ts', 'js', 'tsx', 'jsx', 'py', 'rs', 'go'].includes(ext || '');
})
.handle(async (ctx) => {
const data = ctx.data as { filePath: string; content?: string };
if (!data.content) return { success: true };
const langMap: Record<string, string> = { ts: 'typescript', tsx: 'typescript', js: 'javascript', jsx: 'javascript', py: 'python', rs: 'rust', go: 'go' };
const ext = data.filePath.split('.').pop()?.toLowerCase() || '';
try {
const search = await getCodeSearch();
await search.indexFile(data.filePath, data.content, langMap[ext] || 'typescript');
} catch { /* silent */ }
return { success: true };
})
.build(),
])
.onInitialize(async (ctx) => {
ctx.logger.info('Semantic Code Search initializing with @ruvector/wasm...');
await getCodeSearch();
ctx.logger.info('Semantic Code Search ready - HNSW indexing enabled');
})
.build();
export default semanticCodeSearchPlugin;