@versatil/sdlc-framework
Version:
🚀 AI-Native SDLC framework with 11-MCP ecosystem, RAG memory, OPERA orchestration, and 6 specialized agents achieving ZERO CONTEXT LOSS. Features complete CI/CD pipeline with 7 GitHub workflows (MCP testing, security scanning, performance benchmarking),
486 lines • 19 kB
JavaScript
/**
* VERSATIL SDLC Framework - Production Supabase Vector Store
*
* Advanced client-side vector store with:
* - Dual embedding support (OpenAI + local Transformers.js)
* - Real-time agent collaboration via Supabase channels
* - Production-ready pattern storage and retrieval
* - Seamless integration with existing Enhanced OPERA agents
*/
import { createClient } from '@supabase/supabase-js';
import { VERSATILLogger } from '../utils/logger.js';
import { EventEmitter } from 'events';
export class SupabaseVectorStore extends EventEmitter {
constructor(config) {
super();
this.isInitialized = false;
this.config = {
useLocalEmbeddings: false,
embeddingModel: 'text-embedding-ada-002',
maxRetries: 3,
retryDelay: 1000,
...config
};
this.logger = new VERSATILLogger();
this.supabase = createClient(config.supabaseUrl, config.supabaseKey);
this.initializeEmbeddingProvider();
}
/**
* Initialize embedding provider based on configuration
*/
async initializeEmbeddingProvider() {
try {
if (this.config.openaiKey && !this.config.useLocalEmbeddings) {
// Use OpenAI for production (better quality)
const { OpenAI } = await import('openai');
this.openai = new OpenAI({ apiKey: this.config.openaiKey });
this.logger.info('OpenAI embeddings initialized', { model: this.config.embeddingModel }, 'supabase-vector-store');
}
else {
// Use local Transformers.js for edge/privacy
await this.initializeLocalEmbeddings();
}
this.isInitialized = true;
this.emit('initialized', { provider: this.openai ? 'openai' : 'local' });
}
catch (error) {
this.logger.error('Failed to initialize embedding provider', { error }, 'supabase-vector-store');
// Fallback to local embeddings
if (this.openai) {
this.logger.warn('Falling back to local embeddings', {}, 'supabase-vector-store');
await this.initializeLocalEmbeddings();
this.isInitialized = true;
}
}
}
/**
* Initialize local Transformers.js embeddings
*/
async initializeLocalEmbeddings() {
try {
const { pipeline } = await import('@xenova/transformers');
this.embeddingModel = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2', { device: 'cpu' } // Ensure CPU usage for broader compatibility
);
this.logger.info('Local embeddings initialized', { model: 'all-MiniLM-L6-v2' }, 'supabase-vector-store');
}
catch (error) {
this.logger.error('Failed to initialize local embeddings', { error }, 'supabase-vector-store');
throw new Error('No embedding provider available');
}
}
/**
* Generate embeddings for text
*/
async generateEmbedding(text) {
if (!this.isInitialized) {
await this.initializeEmbeddingProvider();
}
let retries = 0;
const maxRetries = this.config.maxRetries || 3;
while (retries < maxRetries) {
try {
if (this.openai) {
// Use OpenAI embeddings
const response = await this.openai.embeddings.create({
model: this.config.embeddingModel || 'text-embedding-ada-002',
input: text.slice(0, 8000), // Limit input length
});
return response.data[0].embedding;
}
else if (this.embeddingModel) {
// Use local embeddings
const output = await this.embeddingModel(text.slice(0, 512), {
pooling: 'mean',
normalize: true
});
return Array.from(output.data);
}
else {
throw new Error('No embedding provider available');
}
}
catch (error) {
retries++;
if (retries >= maxRetries) {
this.logger.error('Failed to generate embedding after retries', {
error,
retries,
text: text.slice(0, 100)
}, 'supabase-vector-store');
throw error;
}
// Wait before retry
await new Promise(resolve => setTimeout(resolve, this.config.retryDelay * retries));
}
}
throw new Error('Failed to generate embedding');
}
/**
* Add a code pattern to the vector store
*/
async addPattern(pattern) {
try {
const embedding = await this.generateEmbedding(`${pattern.type} ${pattern.language || ''} ${pattern.framework || ''} ${pattern.code}`);
const { data, error } = await this.supabase
.from('agent_code_patterns')
.insert({
agent_name: pattern.agent,
pattern_type: pattern.type,
code_content: pattern.code,
file_path: pattern.filePath,
language: pattern.language,
framework: pattern.framework,
quality_score: pattern.score,
embedding: embedding,
metadata: pattern.metadata,
tags: this.extractTags(pattern)
})
.select()
.single();
if (error) {
this.logger.error('Failed to add pattern', { error, pattern: pattern.type }, 'supabase-vector-store');
throw error;
}
this.logger.info('Pattern added successfully', {
id: data.id,
agent: pattern.agent,
type: pattern.type
}, 'supabase-vector-store');
// Notify other agents of new pattern
await this.notifyPatternAdded(data);
return { ...pattern, id: data.id, embedding, created_at: data.created_at };
}
catch (error) {
this.logger.error('Error adding pattern', { error }, 'supabase-vector-store');
throw error;
}
}
/**
* Retrieve similar patterns using vector similarity
*/
async retrieveSimilarPatterns(query, options = {}) {
try {
const { agentName, patternType, language, framework, minSimilarity = 0.7, limit = 5 } = options;
const embedding = await this.generateEmbedding(query);
// Use the appropriate search function based on agent
let rpcFunction = 'search_backend_patterns'; // default
if (agentName === 'enhanced-maria') {
rpcFunction = 'search_qa_patterns';
}
else if (agentName === 'enhanced-james') {
rpcFunction = 'search_frontend_patterns';
}
const { data, error } = await this.supabase.rpc(rpcFunction, {
query_embedding: embedding,
match_threshold: minSimilarity,
match_count: limit,
language_filter: language || null,
framework_filter: framework || null
});
if (error) {
this.logger.error('Failed to retrieve similar patterns', { error, query }, 'supabase-vector-store');
throw error;
}
const patterns = data.map((row) => ({
id: row.id,
agent: row.agent_name || agentName || 'unknown',
type: row.pattern_type,
code: row.code_content,
filePath: row.file_path || '',
language: row.language,
framework: row.framework,
score: row.quality_score,
similarity: row.similarity,
metadata: row.metadata || {},
embedding: row.embedding,
created_at: row.created_at
}));
this.logger.info('Retrieved similar patterns', {
count: patterns.length,
query: query.slice(0, 50)
}, 'supabase-vector-store');
return patterns;
}
catch (error) {
this.logger.error('Error retrieving similar patterns', { error }, 'supabase-vector-store');
return [];
}
}
/**
* Learn from agent interaction and store successful solutions
*/
async learnFromInteraction(interaction) {
try {
const embedding = await this.generateEmbedding(`${interaction.problem} ${interaction.solution}`);
const { data, error } = await this.supabase
.from('agent_solutions')
.insert({
agent_name: interaction.agent,
problem_type: interaction.problemType,
problem_description: interaction.problem,
solution_code: interaction.solution,
solution_explanation: interaction.explanation,
effectiveness_score: interaction.score,
embedding: embedding,
context: interaction.context,
success_cases: interaction.score > 0.8 ? 1 : 0,
failure_cases: interaction.score < 0.5 ? 1 : 0
})
.select()
.single();
if (error) {
this.logger.error('Failed to store learning interaction', { error }, 'supabase-vector-store');
throw error;
}
this.logger.info('Learning interaction stored', {
id: data.id,
agent: interaction.agent,
score: interaction.score
}, 'supabase-vector-store');
// Broadcast learning to other agents
await this.broadcastLearning(interaction);
return {
id: data.id,
agent: interaction.agent,
problemType: interaction.problemType,
problem: interaction.problem,
solution: interaction.solution,
explanation: interaction.explanation,
score: interaction.score,
context: interaction.context,
embedding,
effectiveness_score: data.effectiveness_score,
created_at: data.created_at
};
}
catch (error) {
this.logger.error('Error storing learning interaction', { error }, 'supabase-vector-store');
throw error;
}
}
/**
* Find solutions for similar problems
*/
async findSimilarSolutions(problem, agentName, limit = 3) {
try {
const embedding = await this.generateEmbedding(problem);
const { data, error } = await this.supabase.rpc('search_agent_solutions', {
query_embedding: embedding,
agent_filter: agentName || null,
match_threshold: 0.7,
match_count: limit
});
if (error) {
this.logger.error('Failed to find similar solutions', { error, problem }, 'supabase-vector-store');
throw error;
}
return data.map((row) => ({
id: row.id,
agent: row.agent_name,
problemType: row.problem_type,
problem: row.problem_description,
solution: row.solution_code,
explanation: row.solution_explanation,
score: row.similarity,
effectiveness_score: row.effectiveness_score,
context: row.context || {},
created_at: row.created_at
}));
}
catch (error) {
this.logger.error('Error finding similar solutions', { error }, 'supabase-vector-store');
return [];
}
}
/**
* Setup real-time collaboration between agents
*/
setupRealtimeCollaboration() {
if (this.learningChannel) {
return this.learningChannel;
}
this.learningChannel = this.supabase.channel('agent-learning')
.on('broadcast', { event: 'new-pattern' }, (payload) => {
this.emit('new-pattern', payload.payload);
})
.on('broadcast', { event: 'new-solution' }, (payload) => {
this.emit('new-solution', payload.payload);
})
.on('broadcast', { event: 'agent-insight' }, (payload) => {
this.emit('agent-insight', payload.payload);
})
.subscribe();
this.logger.info('Real-time collaboration setup complete', {}, 'supabase-vector-store');
return this.learningChannel;
}
/**
* Subscribe to learning events from other agents
*/
subscribeToLearning(callback) {
this.setupRealtimeCollaboration();
this.on('new-pattern', callback);
this.on('new-solution', callback);
this.on('agent-insight', callback);
return () => {
this.off('new-pattern', callback);
this.off('new-solution', callback);
this.off('agent-insight', callback);
};
}
/**
* Get performance metrics for the vector store
*/
async getPerformanceMetrics() {
try {
const { data: patterns, error: patternsError } = await this.supabase
.from('agent_code_patterns')
.select('agent_name, quality_score, created_at')
.gte('created_at', new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString());
const { data: solutions, error: solutionsError } = await this.supabase
.from('agent_solutions')
.select('agent_name, effectiveness_score, created_at')
.gte('created_at', new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString());
if (patternsError || solutionsError) {
throw patternsError || solutionsError;
}
const metrics = {
patterns: {
total: patterns?.length || 0,
avgQuality: patterns?.reduce((sum, p) => sum + p.quality_score, 0) / (patterns?.length || 1),
byAgent: this.groupByAgent(patterns, 'quality_score')
},
solutions: {
total: solutions?.length || 0,
avgEffectiveness: solutions?.reduce((sum, s) => sum + s.effectiveness_score, 0) / (solutions?.length || 1),
byAgent: this.groupByAgent(solutions, 'effectiveness_score')
},
embeddingProvider: this.openai ? 'openai' : 'local',
isInitialized: this.isInitialized
};
return metrics;
}
catch (error) {
this.logger.error('Error getting performance metrics', { error }, 'supabase-vector-store');
return null;
}
}
/**
* Cleanup resources
*/
async cleanup() {
if (this.learningChannel) {
await this.supabase.removeChannel(this.learningChannel);
}
this.removeAllListeners();
this.logger.info('Supabase vector store cleanup complete', {}, 'supabase-vector-store');
}
// Private helper methods
extractTags(pattern) {
const tags = [];
if (pattern.language)
tags.push(pattern.language);
if (pattern.framework)
tags.push(pattern.framework);
if (pattern.type)
tags.push(pattern.type);
if (pattern.agent)
tags.push(pattern.agent);
return tags;
}
async notifyPatternAdded(pattern) {
if (this.learningChannel) {
await this.learningChannel.send({
type: 'broadcast',
event: 'new-pattern',
payload: {
id: pattern.id,
agent: pattern.agent_name,
type: pattern.pattern_type,
language: pattern.language,
framework: pattern.framework
}
});
}
}
async broadcastLearning(interaction) {
if (this.learningChannel) {
await this.learningChannel.send({
type: 'broadcast',
event: 'new-solution',
payload: {
agent: interaction.agent,
problemType: interaction.problemType,
score: interaction.score
}
});
}
}
groupByAgent(items, scoreField) {
const grouped = {};
items?.forEach(item => {
const agent = item.agent_name;
if (!grouped[agent]) {
grouped[agent] = { count: 0, totalScore: 0, avgScore: 0 };
}
grouped[agent].count++;
grouped[agent].totalScore += item[scoreField];
grouped[agent].avgScore = grouped[agent].totalScore / grouped[agent].count;
});
return grouped;
}
// Migration support methods
async hasExistingData() {
try {
const { count } = await this.supabase
.from('code_patterns')
.select('*', { count: 'exact', head: true });
return (count || 0) > 0;
}
catch {
return false;
}
}
async initialize() {
// Already initialized in constructor
return Promise.resolve();
}
getEmbeddingProvider() {
return this.config.openaiKey ? 'openai' : 'local';
}
getFeatures() {
return ['patterns', 'solutions', 'interactions', 'cross-agent-learning'];
}
async storeSolution(solution) {
try {
const { error } = await this.supabase
.from('agent_solutions')
.insert([solution]);
if (error)
throw error;
this.emit('solution_stored', { solution });
}
catch (error) {
this.logger.error('Failed to store solution', { error }, 'supabase-vector-store');
throw error;
}
}
async addSolution(solution) {
await this.storeSolution(solution);
}
async getPatternCount() {
try {
const { count } = await this.supabase
.from('code_patterns')
.select('*', { count: 'exact', head: true });
return count || 0;
}
catch {
return 0;
}
}
}
// Export singleton instance factory
export function createSupabaseVectorStore(config) {
return new SupabaseVectorStore(config);
}
//# sourceMappingURL=supabase-vector-store.js.map