@sparkleideas/plugins 3.0.0-alpha.10
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/README.md +401 -0
- package/__tests__/collection-manager.test.ts +332 -0
- package/__tests__/dependency-graph.test.ts +434 -0
- package/__tests__/enhanced-plugin-registry.test.ts +488 -0
- package/__tests__/plugin-registry.test.ts +368 -0
- package/__tests__/ruvector-bridge.test.ts +2429 -0
- package/__tests__/ruvector-integration.test.ts +1602 -0
- package/__tests__/ruvector-migrations.test.ts +1099 -0
- package/__tests__/ruvector-quantization.test.ts +846 -0
- package/__tests__/ruvector-streaming.test.ts +1088 -0
- package/__tests__/sdk.test.ts +325 -0
- package/__tests__/security.test.ts +348 -0
- package/__tests__/utils/ruvector-test-utils.ts +860 -0
- package/examples/plugin-creator/index.ts +636 -0
- package/examples/plugin-creator/plugin-creator.test.ts +312 -0
- package/examples/ruvector/README.md +288 -0
- package/examples/ruvector/attention-patterns.ts +394 -0
- package/examples/ruvector/basic-usage.ts +288 -0
- package/examples/ruvector/docker-compose.yml +75 -0
- package/examples/ruvector/gnn-analysis.ts +501 -0
- package/examples/ruvector/hyperbolic-hierarchies.ts +557 -0
- package/examples/ruvector/init-db.sql +119 -0
- package/examples/ruvector/quantization.ts +680 -0
- package/examples/ruvector/self-learning.ts +447 -0
- package/examples/ruvector/semantic-search.ts +576 -0
- package/examples/ruvector/streaming-large-data.ts +507 -0
- package/examples/ruvector/transactions.ts +594 -0
- package/examples/ruvector-plugins/hook-pattern-library.ts +486 -0
- package/examples/ruvector-plugins/index.ts +79 -0
- package/examples/ruvector-plugins/intent-router.ts +354 -0
- package/examples/ruvector-plugins/mcp-tool-optimizer.ts +424 -0
- package/examples/ruvector-plugins/reasoning-bank.ts +657 -0
- package/examples/ruvector-plugins/ruvector-plugins.test.ts +518 -0
- package/examples/ruvector-plugins/semantic-code-search.ts +498 -0
- package/examples/ruvector-plugins/shared/index.ts +20 -0
- package/examples/ruvector-plugins/shared/vector-utils.ts +257 -0
- package/examples/ruvector-plugins/sona-learning.ts +445 -0
- package/package.json +97 -0
- package/src/collections/collection-manager.ts +661 -0
- package/src/collections/index.ts +56 -0
- package/src/collections/official/index.ts +1040 -0
- package/src/core/base-plugin.ts +416 -0
- package/src/core/plugin-interface.ts +215 -0
- package/src/hooks/index.ts +685 -0
- package/src/index.ts +378 -0
- package/src/integrations/agentic-flow.ts +743 -0
- package/src/integrations/index.ts +88 -0
- package/src/integrations/ruvector/ARCHITECTURE.md +1245 -0
- package/src/integrations/ruvector/attention-advanced.ts +1040 -0
- package/src/integrations/ruvector/attention-executor.ts +782 -0
- package/src/integrations/ruvector/attention-mechanisms.ts +757 -0
- package/src/integrations/ruvector/attention.ts +1063 -0
- package/src/integrations/ruvector/gnn.ts +3050 -0
- package/src/integrations/ruvector/hyperbolic.ts +1948 -0
- package/src/integrations/ruvector/index.ts +394 -0
- package/src/integrations/ruvector/migrations/001_create_extension.sql +135 -0
- package/src/integrations/ruvector/migrations/002_create_vector_tables.sql +259 -0
- package/src/integrations/ruvector/migrations/003_create_indices.sql +328 -0
- package/src/integrations/ruvector/migrations/004_create_functions.sql +598 -0
- package/src/integrations/ruvector/migrations/005_create_attention_functions.sql +654 -0
- package/src/integrations/ruvector/migrations/006_create_gnn_functions.sql +728 -0
- package/src/integrations/ruvector/migrations/007_create_hyperbolic_functions.sql +762 -0
- package/src/integrations/ruvector/migrations/index.ts +35 -0
- package/src/integrations/ruvector/migrations/migrations.ts +647 -0
- package/src/integrations/ruvector/quantization.ts +2036 -0
- package/src/integrations/ruvector/ruvector-bridge.ts +2000 -0
- package/src/integrations/ruvector/self-learning.ts +2376 -0
- package/src/integrations/ruvector/streaming.ts +1737 -0
- package/src/integrations/ruvector/types.ts +1945 -0
- package/src/providers/index.ts +643 -0
- package/src/registry/dependency-graph.ts +568 -0
- package/src/registry/enhanced-plugin-registry.ts +994 -0
- package/src/registry/plugin-registry.ts +604 -0
- package/src/sdk/index.ts +563 -0
- package/src/security/index.ts +594 -0
- package/src/types/index.ts +446 -0
- package/src/workers/index.ts +700 -0
- package/tmp.json +0 -0
- package/tsconfig.json +25 -0
- package/vitest.config.ts +23 -0
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RuVector PostgreSQL Bridge - Semantic Code Search Example
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates:
|
|
5
|
+
* - Embedding code snippets for semantic search
|
|
6
|
+
* - Natural language code queries
|
|
7
|
+
* - Ranking and relevance scoring
|
|
8
|
+
* - Hybrid search (semantic + keyword)
|
|
9
|
+
*
|
|
10
|
+
* Run with: npx ts-node examples/ruvector/semantic-search.ts
|
|
11
|
+
*
|
|
12
|
+
* @module @sparkleideas/plugins/examples/ruvector/semantic-search
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
createRuVectorBridge,
|
|
17
|
+
type RuVectorBridge,
|
|
18
|
+
type VectorRecord,
|
|
19
|
+
type VectorSearchOptions,
|
|
20
|
+
} from '../../src/integrations/ruvector/index.js';
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Configuration
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
const config = {
|
|
27
|
+
connection: {
|
|
28
|
+
host: process.env.POSTGRES_HOST || 'localhost',
|
|
29
|
+
port: parseInt(process.env.POSTGRES_PORT || '5432', 10),
|
|
30
|
+
database: process.env.POSTGRES_DB || 'vectors',
|
|
31
|
+
user: process.env.POSTGRES_USER || 'postgres',
|
|
32
|
+
password: process.env.POSTGRES_PASSWORD || 'postgres',
|
|
33
|
+
},
|
|
34
|
+
dimensions: 768, // Code embedding dimension (e.g., CodeBERT, StarCoder)
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// Simulated Embedding Model
|
|
39
|
+
// ============================================================================
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Simulated code embedding function.
|
|
43
|
+
* In production, use a proper code embedding model like:
|
|
44
|
+
* - CodeBERT
|
|
45
|
+
* - StarCoder
|
|
46
|
+
* - Code-LLaMA embeddings
|
|
47
|
+
* - OpenAI text-embedding-3-small
|
|
48
|
+
*/
|
|
49
|
+
class CodeEmbedder {
|
|
50
|
+
private dimension: number;
|
|
51
|
+
private vocabulary: Map<string, number[]> = new Map();
|
|
52
|
+
|
|
53
|
+
constructor(dimension: number) {
|
|
54
|
+
this.dimension = dimension;
|
|
55
|
+
this.initializeVocabulary();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Initialize semantic vocabulary for code concepts.
|
|
60
|
+
* This is a simplified simulation - real models use neural networks.
|
|
61
|
+
*/
|
|
62
|
+
private initializeVocabulary(): void {
|
|
63
|
+
const concepts = [
|
|
64
|
+
'function', 'class', 'variable', 'loop', 'condition',
|
|
65
|
+
'array', 'object', 'string', 'number', 'boolean',
|
|
66
|
+
'async', 'await', 'promise', 'callback', 'error',
|
|
67
|
+
'import', 'export', 'module', 'interface', 'type',
|
|
68
|
+
'auth', 'user', 'password', 'token', 'login',
|
|
69
|
+
'database', 'query', 'sql', 'insert', 'select',
|
|
70
|
+
'api', 'http', 'request', 'response', 'endpoint',
|
|
71
|
+
'test', 'expect', 'mock', 'assert', 'describe',
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
concepts.forEach((concept, idx) => {
|
|
75
|
+
const embedding = this.createConceptEmbedding(idx, concepts.length);
|
|
76
|
+
this.vocabulary.set(concept, embedding);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Create a deterministic embedding for a concept.
|
|
82
|
+
*/
|
|
83
|
+
private createConceptEmbedding(idx: number, total: number): number[] {
|
|
84
|
+
const embedding = new Array(this.dimension).fill(0);
|
|
85
|
+
const base = (idx / total) * Math.PI * 2;
|
|
86
|
+
|
|
87
|
+
for (let i = 0; i < this.dimension; i++) {
|
|
88
|
+
embedding[i] = Math.sin(base + (i / this.dimension) * Math.PI) * 0.5;
|
|
89
|
+
if (i % 2 === 0) {
|
|
90
|
+
embedding[i] += Math.cos(base * 2 + i * 0.01) * 0.3;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return this.normalize(embedding);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Embed code snippet.
|
|
99
|
+
*/
|
|
100
|
+
embed(code: string): number[] {
|
|
101
|
+
const tokens = this.tokenize(code);
|
|
102
|
+
const embedding = new Array(this.dimension).fill(0);
|
|
103
|
+
|
|
104
|
+
// Accumulate embeddings from vocabulary matches
|
|
105
|
+
let matchCount = 0;
|
|
106
|
+
for (const token of tokens) {
|
|
107
|
+
const tokenLower = token.toLowerCase();
|
|
108
|
+
if (this.vocabulary.has(tokenLower)) {
|
|
109
|
+
const tokenEmbedding = this.vocabulary.get(tokenLower)!;
|
|
110
|
+
for (let i = 0; i < this.dimension; i++) {
|
|
111
|
+
embedding[i] += tokenEmbedding[i];
|
|
112
|
+
}
|
|
113
|
+
matchCount++;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Add noise for tokens not in vocabulary
|
|
118
|
+
const unknownRatio = (tokens.length - matchCount) / tokens.length;
|
|
119
|
+
for (let i = 0; i < this.dimension; i++) {
|
|
120
|
+
embedding[i] += (Math.random() * 2 - 1) * unknownRatio * 0.1;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return this.normalize(embedding);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Embed a natural language query.
|
|
128
|
+
*/
|
|
129
|
+
embedQuery(query: string): number[] {
|
|
130
|
+
return this.embed(query);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Tokenize code into meaningful tokens.
|
|
135
|
+
*/
|
|
136
|
+
private tokenize(code: string): string[] {
|
|
137
|
+
return code
|
|
138
|
+
.replace(/[^\w\s]/g, ' ')
|
|
139
|
+
.split(/\s+/)
|
|
140
|
+
.filter(t => t.length > 1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Normalize vector to unit length.
|
|
145
|
+
*/
|
|
146
|
+
private normalize(vec: number[]): number[] {
|
|
147
|
+
const magnitude = Math.sqrt(vec.reduce((sum, v) => sum + v * v, 0));
|
|
148
|
+
if (magnitude === 0) return vec;
|
|
149
|
+
return vec.map(v => v / magnitude);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ============================================================================
|
|
154
|
+
// Sample Code Repository
|
|
155
|
+
// ============================================================================
|
|
156
|
+
|
|
157
|
+
const codeSnippets = [
|
|
158
|
+
{
|
|
159
|
+
id: 'auth-login',
|
|
160
|
+
language: 'typescript',
|
|
161
|
+
filename: 'auth/login.ts',
|
|
162
|
+
code: `
|
|
163
|
+
async function login(username: string, password: string): Promise<AuthToken> {
|
|
164
|
+
const user = await findUserByUsername(username);
|
|
165
|
+
if (!user) {
|
|
166
|
+
throw new AuthenticationError('User not found');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const isValid = await verifyPassword(password, user.passwordHash);
|
|
170
|
+
if (!isValid) {
|
|
171
|
+
throw new AuthenticationError('Invalid password');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const token = generateJWT({ userId: user.id, role: user.role });
|
|
175
|
+
await saveSession(user.id, token);
|
|
176
|
+
|
|
177
|
+
return { token, expiresIn: 3600 };
|
|
178
|
+
}`,
|
|
179
|
+
tags: ['authentication', 'login', 'jwt', 'security'],
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
id: 'auth-middleware',
|
|
183
|
+
language: 'typescript',
|
|
184
|
+
filename: 'middleware/auth.ts',
|
|
185
|
+
code: `
|
|
186
|
+
export function authMiddleware(req: Request, res: Response, next: NextFunction) {
|
|
187
|
+
const token = req.headers.authorization?.replace('Bearer ', '');
|
|
188
|
+
|
|
189
|
+
if (!token) {
|
|
190
|
+
return res.status(401).json({ error: 'No token provided' });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
const decoded = verifyJWT(token);
|
|
195
|
+
req.user = decoded;
|
|
196
|
+
next();
|
|
197
|
+
} catch (error) {
|
|
198
|
+
return res.status(403).json({ error: 'Invalid token' });
|
|
199
|
+
}
|
|
200
|
+
}`,
|
|
201
|
+
tags: ['middleware', 'authentication', 'jwt', 'express'],
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
id: 'db-query',
|
|
205
|
+
language: 'typescript',
|
|
206
|
+
filename: 'database/query.ts',
|
|
207
|
+
code: `
|
|
208
|
+
async function executeQuery<T>(sql: string, params: unknown[]): Promise<T[]> {
|
|
209
|
+
const client = await pool.connect();
|
|
210
|
+
try {
|
|
211
|
+
const result = await client.query(sql, params);
|
|
212
|
+
return result.rows;
|
|
213
|
+
} catch (error) {
|
|
214
|
+
logger.error('Database query failed', { sql, error });
|
|
215
|
+
throw new DatabaseError('Query execution failed');
|
|
216
|
+
} finally {
|
|
217
|
+
client.release();
|
|
218
|
+
}
|
|
219
|
+
}`,
|
|
220
|
+
tags: ['database', 'postgresql', 'query', 'async'],
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: 'api-users',
|
|
224
|
+
language: 'typescript',
|
|
225
|
+
filename: 'api/users.ts',
|
|
226
|
+
code: `
|
|
227
|
+
router.get('/users/:id', async (req: Request, res: Response) => {
|
|
228
|
+
const { id } = req.params;
|
|
229
|
+
|
|
230
|
+
const user = await userService.findById(id);
|
|
231
|
+
if (!user) {
|
|
232
|
+
return res.status(404).json({ error: 'User not found' });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
res.json({
|
|
236
|
+
id: user.id,
|
|
237
|
+
username: user.username,
|
|
238
|
+
email: user.email,
|
|
239
|
+
createdAt: user.createdAt,
|
|
240
|
+
});
|
|
241
|
+
});`,
|
|
242
|
+
tags: ['api', 'rest', 'users', 'express'],
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
id: 'test-auth',
|
|
246
|
+
language: 'typescript',
|
|
247
|
+
filename: 'tests/auth.test.ts',
|
|
248
|
+
code: `
|
|
249
|
+
describe('Authentication', () => {
|
|
250
|
+
it('should login with valid credentials', async () => {
|
|
251
|
+
const result = await login('testuser', 'password123');
|
|
252
|
+
|
|
253
|
+
expect(result.token).toBeDefined();
|
|
254
|
+
expect(result.expiresIn).toBe(3600);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('should reject invalid password', async () => {
|
|
258
|
+
await expect(login('testuser', 'wrong'))
|
|
259
|
+
.rejects
|
|
260
|
+
.toThrow('Invalid password');
|
|
261
|
+
});
|
|
262
|
+
});`,
|
|
263
|
+
tags: ['test', 'jest', 'authentication', 'unit-test'],
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
id: 'array-utils',
|
|
267
|
+
language: 'typescript',
|
|
268
|
+
filename: 'utils/array.ts',
|
|
269
|
+
code: `
|
|
270
|
+
export function chunk<T>(array: T[], size: number): T[][] {
|
|
271
|
+
const chunks: T[][] = [];
|
|
272
|
+
for (let i = 0; i < array.length; i += size) {
|
|
273
|
+
chunks.push(array.slice(i, i + size));
|
|
274
|
+
}
|
|
275
|
+
return chunks;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function unique<T>(array: T[]): T[] {
|
|
279
|
+
return [...new Set(array)];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function groupBy<T>(array: T[], key: keyof T): Record<string, T[]> {
|
|
283
|
+
return array.reduce((acc, item) => {
|
|
284
|
+
const groupKey = String(item[key]);
|
|
285
|
+
acc[groupKey] = acc[groupKey] || [];
|
|
286
|
+
acc[groupKey].push(item);
|
|
287
|
+
return acc;
|
|
288
|
+
}, {} as Record<string, T[]>);
|
|
289
|
+
}`,
|
|
290
|
+
tags: ['utility', 'array', 'functional'],
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
id: 'error-handler',
|
|
294
|
+
language: 'typescript',
|
|
295
|
+
filename: 'middleware/error.ts',
|
|
296
|
+
code: `
|
|
297
|
+
export function errorHandler(
|
|
298
|
+
error: Error,
|
|
299
|
+
req: Request,
|
|
300
|
+
res: Response,
|
|
301
|
+
next: NextFunction
|
|
302
|
+
) {
|
|
303
|
+
logger.error('Unhandled error', { error, path: req.path });
|
|
304
|
+
|
|
305
|
+
if (error instanceof ValidationError) {
|
|
306
|
+
return res.status(400).json({ error: error.message });
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (error instanceof AuthenticationError) {
|
|
310
|
+
return res.status(401).json({ error: error.message });
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
314
|
+
}`,
|
|
315
|
+
tags: ['error-handling', 'middleware', 'express'],
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
id: 'cache-service',
|
|
319
|
+
language: 'typescript',
|
|
320
|
+
filename: 'services/cache.ts',
|
|
321
|
+
code: `
|
|
322
|
+
class CacheService {
|
|
323
|
+
private cache: Map<string, { value: unknown; expiry: number }> = new Map();
|
|
324
|
+
|
|
325
|
+
async get<T>(key: string): Promise<T | null> {
|
|
326
|
+
const entry = this.cache.get(key);
|
|
327
|
+
if (!entry) return null;
|
|
328
|
+
|
|
329
|
+
if (Date.now() > entry.expiry) {
|
|
330
|
+
this.cache.delete(key);
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return entry.value as T;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async set(key: string, value: unknown, ttlSeconds: number): Promise<void> {
|
|
338
|
+
this.cache.set(key, {
|
|
339
|
+
value,
|
|
340
|
+
expiry: Date.now() + ttlSeconds * 1000,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}`,
|
|
344
|
+
tags: ['cache', 'memory', 'service'],
|
|
345
|
+
},
|
|
346
|
+
];
|
|
347
|
+
|
|
348
|
+
// ============================================================================
|
|
349
|
+
// Main Example
|
|
350
|
+
// ============================================================================
|
|
351
|
+
|
|
352
|
+
async function main(): Promise<void> {
|
|
353
|
+
console.log('RuVector PostgreSQL Bridge - Semantic Code Search Example');
|
|
354
|
+
console.log('=========================================================\n');
|
|
355
|
+
|
|
356
|
+
const bridge: RuVectorBridge = createRuVectorBridge({
|
|
357
|
+
connectionString: `postgresql://${config.connection.user}:${config.connection.password}@${config.connection.host}:${config.connection.port}/${config.connection.database}`,
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
const embedder = new CodeEmbedder(config.dimensions);
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
await bridge.connect();
|
|
364
|
+
console.log('Connected to PostgreSQL\n');
|
|
365
|
+
|
|
366
|
+
// ========================================================================
|
|
367
|
+
// 1. Create Code Collection
|
|
368
|
+
// ========================================================================
|
|
369
|
+
console.log('1. Creating code collection...');
|
|
370
|
+
await bridge.createCollection('code_snippets', {
|
|
371
|
+
dimensions: config.dimensions,
|
|
372
|
+
distanceMetric: 'cosine',
|
|
373
|
+
indexType: 'hnsw',
|
|
374
|
+
indexParams: { m: 16, efConstruction: 100 },
|
|
375
|
+
});
|
|
376
|
+
console.log(' Collection created!\n');
|
|
377
|
+
|
|
378
|
+
// ========================================================================
|
|
379
|
+
// 2. Index Code Snippets
|
|
380
|
+
// ========================================================================
|
|
381
|
+
console.log('2. Indexing code snippets...');
|
|
382
|
+
|
|
383
|
+
for (const snippet of codeSnippets) {
|
|
384
|
+
const embedding = embedder.embed(snippet.code);
|
|
385
|
+
await bridge.insert('code_snippets', {
|
|
386
|
+
id: snippet.id,
|
|
387
|
+
embedding,
|
|
388
|
+
metadata: {
|
|
389
|
+
language: snippet.language,
|
|
390
|
+
filename: snippet.filename,
|
|
391
|
+
tags: snippet.tags,
|
|
392
|
+
codePreview: snippet.code.slice(0, 100) + '...',
|
|
393
|
+
},
|
|
394
|
+
});
|
|
395
|
+
console.log(` Indexed: ${snippet.filename}`);
|
|
396
|
+
}
|
|
397
|
+
console.log(' All snippets indexed!\n');
|
|
398
|
+
|
|
399
|
+
// ========================================================================
|
|
400
|
+
// 3. Natural Language Code Search
|
|
401
|
+
// ========================================================================
|
|
402
|
+
console.log('3. Natural language search examples:\n');
|
|
403
|
+
|
|
404
|
+
const queries = [
|
|
405
|
+
'How do I authenticate a user with username and password?',
|
|
406
|
+
'Show me how to handle errors in Express middleware',
|
|
407
|
+
'I need to query a PostgreSQL database',
|
|
408
|
+
'Unit test example for login functionality',
|
|
409
|
+
'How to implement caching with expiration?',
|
|
410
|
+
];
|
|
411
|
+
|
|
412
|
+
for (const query of queries) {
|
|
413
|
+
console.log(` Query: "${query}"`);
|
|
414
|
+
|
|
415
|
+
const queryEmbedding = embedder.embedQuery(query);
|
|
416
|
+
const results = await bridge.search('code_snippets', queryEmbedding, {
|
|
417
|
+
k: 2,
|
|
418
|
+
includeMetadata: true,
|
|
419
|
+
includeDistance: true,
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
console.log(' Results:');
|
|
423
|
+
results.forEach((result, i) => {
|
|
424
|
+
const similarity = 1 - (result.distance ?? 0);
|
|
425
|
+
console.log(` ${i + 1}. ${result.metadata?.filename} (similarity: ${(similarity * 100).toFixed(1)}%)`);
|
|
426
|
+
console.log(` Tags: ${(result.metadata?.tags as string[])?.join(', ')}`);
|
|
427
|
+
});
|
|
428
|
+
console.log();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// ========================================================================
|
|
432
|
+
// 4. Tag-Filtered Search
|
|
433
|
+
// ========================================================================
|
|
434
|
+
console.log('4. Tag-filtered search (authentication + middleware)...\n');
|
|
435
|
+
|
|
436
|
+
const authQuery = 'verify user credentials and protect routes';
|
|
437
|
+
const authEmbedding = embedder.embedQuery(authQuery);
|
|
438
|
+
|
|
439
|
+
const authResults = await bridge.search('code_snippets', authEmbedding, {
|
|
440
|
+
k: 5,
|
|
441
|
+
includeMetadata: true,
|
|
442
|
+
includeDistance: true,
|
|
443
|
+
filter: {
|
|
444
|
+
tags: { $contains: 'authentication' },
|
|
445
|
+
},
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
console.log(` Query: "${authQuery}"`);
|
|
449
|
+
console.log(' Filtered to: authentication tag');
|
|
450
|
+
console.log(' Results:');
|
|
451
|
+
authResults.forEach((result, i) => {
|
|
452
|
+
const similarity = 1 - (result.distance ?? 0);
|
|
453
|
+
console.log(` ${i + 1}. ${result.metadata?.filename} (similarity: ${(similarity * 100).toFixed(1)}%)`);
|
|
454
|
+
});
|
|
455
|
+
console.log();
|
|
456
|
+
|
|
457
|
+
// ========================================================================
|
|
458
|
+
// 5. Hybrid Search (Semantic + Keyword)
|
|
459
|
+
// ========================================================================
|
|
460
|
+
console.log('5. Hybrid search (semantic + keyword matching)...\n');
|
|
461
|
+
|
|
462
|
+
const hybridQuery = 'async function database';
|
|
463
|
+
const hybridEmbedding = embedder.embedQuery(hybridQuery);
|
|
464
|
+
|
|
465
|
+
// First, get semantic results
|
|
466
|
+
const semanticResults = await bridge.search('code_snippets', hybridEmbedding, {
|
|
467
|
+
k: 10,
|
|
468
|
+
includeMetadata: true,
|
|
469
|
+
includeDistance: true,
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// Then, boost results that contain the keyword
|
|
473
|
+
const keyword = 'async';
|
|
474
|
+
const hybridResults = semanticResults
|
|
475
|
+
.map(result => {
|
|
476
|
+
const code = codeSnippets.find(s => s.id === result.id)?.code ?? '';
|
|
477
|
+
const keywordMatches = (code.match(new RegExp(keyword, 'gi')) || []).length;
|
|
478
|
+
const semanticScore = 1 - (result.distance ?? 0);
|
|
479
|
+
const keywordBoost = Math.min(keywordMatches * 0.05, 0.2);
|
|
480
|
+
const hybridScore = semanticScore * 0.7 + keywordBoost + 0.1;
|
|
481
|
+
|
|
482
|
+
return { ...result, hybridScore };
|
|
483
|
+
})
|
|
484
|
+
.sort((a, b) => b.hybridScore - a.hybridScore)
|
|
485
|
+
.slice(0, 3);
|
|
486
|
+
|
|
487
|
+
console.log(` Query: "${hybridQuery}"`);
|
|
488
|
+
console.log(' Results (semantic 70% + keyword boost 30%):');
|
|
489
|
+
hybridResults.forEach((result, i) => {
|
|
490
|
+
console.log(` ${i + 1}. ${result.metadata?.filename} (hybrid score: ${(result.hybridScore * 100).toFixed(1)}%)`);
|
|
491
|
+
});
|
|
492
|
+
console.log();
|
|
493
|
+
|
|
494
|
+
// ========================================================================
|
|
495
|
+
// 6. Similar Code Detection
|
|
496
|
+
// ========================================================================
|
|
497
|
+
console.log('6. Finding similar code to a reference snippet...\n');
|
|
498
|
+
|
|
499
|
+
const referenceId = 'auth-login';
|
|
500
|
+
const reference = await bridge.get('code_snippets', referenceId);
|
|
501
|
+
|
|
502
|
+
if (reference) {
|
|
503
|
+
const similarResults = await bridge.search('code_snippets', reference.embedding, {
|
|
504
|
+
k: 4,
|
|
505
|
+
includeMetadata: true,
|
|
506
|
+
includeDistance: true,
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
console.log(` Reference: ${reference.metadata?.filename}`);
|
|
510
|
+
console.log(' Similar code:');
|
|
511
|
+
similarResults
|
|
512
|
+
.filter(r => r.id !== referenceId) // Exclude self
|
|
513
|
+
.forEach((result, i) => {
|
|
514
|
+
const similarity = 1 - (result.distance ?? 0);
|
|
515
|
+
console.log(` ${i + 1}. ${result.metadata?.filename} (similarity: ${(similarity * 100).toFixed(1)}%)`);
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
console.log();
|
|
519
|
+
|
|
520
|
+
// ========================================================================
|
|
521
|
+
// 7. Relevance Feedback (Re-ranking)
|
|
522
|
+
// ========================================================================
|
|
523
|
+
console.log('7. Relevance feedback / re-ranking demo...\n');
|
|
524
|
+
|
|
525
|
+
// User marks 'auth-middleware' as relevant for authentication queries
|
|
526
|
+
const relevantId = 'auth-middleware';
|
|
527
|
+
const relevantDoc = await bridge.get('code_snippets', relevantId);
|
|
528
|
+
|
|
529
|
+
if (relevantDoc) {
|
|
530
|
+
// Combine original query with relevant document embedding
|
|
531
|
+
const originalQuery = 'protect API endpoints';
|
|
532
|
+
const originalEmbedding = embedder.embedQuery(originalQuery);
|
|
533
|
+
|
|
534
|
+
// Rocchio algorithm: query' = alpha * query + beta * relevant
|
|
535
|
+
const alpha = 0.7;
|
|
536
|
+
const beta = 0.3;
|
|
537
|
+
const expandedEmbedding = originalEmbedding.map(
|
|
538
|
+
(v, i) => alpha * v + beta * relevantDoc.embedding[i]
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
// Normalize
|
|
542
|
+
const magnitude = Math.sqrt(expandedEmbedding.reduce((s, v) => s + v * v, 0));
|
|
543
|
+
const normalizedEmbedding = expandedEmbedding.map(v => v / magnitude);
|
|
544
|
+
|
|
545
|
+
const rerankedResults = await bridge.search('code_snippets', normalizedEmbedding, {
|
|
546
|
+
k: 3,
|
|
547
|
+
includeMetadata: true,
|
|
548
|
+
includeDistance: true,
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
console.log(` Original query: "${originalQuery}"`);
|
|
552
|
+
console.log(` Relevant feedback: ${relevantDoc.metadata?.filename}`);
|
|
553
|
+
console.log(' Re-ranked results:');
|
|
554
|
+
rerankedResults.forEach((result, i) => {
|
|
555
|
+
const similarity = 1 - (result.distance ?? 0);
|
|
556
|
+
console.log(` ${i + 1}. ${result.metadata?.filename} (similarity: ${(similarity * 100).toFixed(1)}%)`);
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// ========================================================================
|
|
561
|
+
// Cleanup
|
|
562
|
+
// ========================================================================
|
|
563
|
+
console.log('\n' + '='.repeat(60));
|
|
564
|
+
console.log('Semantic code search example completed!');
|
|
565
|
+
console.log('='.repeat(60));
|
|
566
|
+
|
|
567
|
+
} catch (error) {
|
|
568
|
+
console.error('Error:', error);
|
|
569
|
+
throw error;
|
|
570
|
+
} finally {
|
|
571
|
+
await bridge.disconnect();
|
|
572
|
+
console.log('\nDisconnected from PostgreSQL.');
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
main().catch(console.error);
|