UNPKG

claude-flow-novice

Version:

Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes CodeSearch (hybrid SQLite + pgvector), mem0/memgraph specialists, and all CFN skills.

240 lines (196 loc) 6.31 kB
/** * Database Adapter for SQLite operations * Supports parameterized queries to prevent SQL injection */ import { spawn } from 'child_process'; import type { DatabaseAdapter } from './types'; export class SQLiteDatabaseAdapter implements DatabaseAdapter { private databasePath: string; constructor(databasePath: string) { this.databasePath = databasePath; } /** * Execute raw SQL query and return results */ async query(sql: string, params?: any[]): Promise<any> { const results = await this.selectAll(sql, params); return results.length > 0 ? results[0] : null; } /** * Select single row */ async selectOne(sql: string, params?: any[]): Promise<any> { const results = await this.selectAll(sql, params); return results.length > 0 ? results[0] : null; } /** * Select all rows */ async selectAll(sql: string, params?: any[]): Promise<any[]> { const output = await this.executeSqlite3(sql, params); if (!output.trim()) { return []; } // Parse pipe-delimited output const lines = output.trim().split('\n'); return lines.map(line => this.parsePipeDelimitedRow(line)); } /** * Execute SQL statement (INSERT, UPDATE, DELETE) */ async execute(sql: string, params?: any[]): Promise<void> { await this.executeSqlite3(sql, params); } /** * Get affected agents using a skill */ async getAffectedAgents(skillId: number): Promise<string[]> { const results = await this.selectAll( 'SELECT DISTINCT agent_type FROM agent_skill_mappings WHERE skill_id = ?1', [skillId] ); return results.map(row => row.agent_type).filter(Boolean); } /** * Close database connection (no-op for SQLite) */ async close(): Promise<void> { // SQLite doesn't require explicit connection closing } /** * Execute sqlite3 command with parameter binding */ private async executeSqlite3(sql: string, params?: any[]): Promise<string> { return new Promise((resolve, reject) => { const sqlite3Args = [ '-header', '-separator', '|', this.databasePath, ]; const sqlWithParams = this.bindParameters(sql, params); const process = spawn('sqlite3', sqlite3Args, { stdio: ['pipe', 'pipe', 'pipe'], }); let stdout = ''; let stderr = ''; process.stdout?.on('data', (data) => { stdout += data.toString(); }); process.stderr?.on('data', (data) => { stderr += data.toString(); }); process.on('close', (code) => { if (code === 0) { resolve(stdout); } else { reject(new Error(`SQLite error: ${stderr || 'unknown error'}`)); } }); process.stdin?.write(sqlWithParams); process.stdin?.end(); }); } /** * Bind parameters to SQL query * Simple implementation - in production would use proper binding */ private bindParameters(sql: string, params?: any[]): string { if (!params || params.length === 0) { return sql; } let result = sql; params.forEach((param, index) => { const placeholder = `?${index + 1}`; const value = this.escapeValue(param); result = result.replace(placeholder, value); }); return result; } /** * Escape value for SQL safety */ private escapeValue(value: any): string { if (value === null || value === undefined) { return 'NULL'; } if (typeof value === 'number') { return value.toString(); } if (typeof value === 'boolean') { return value ? '1' : '0'; } if (typeof value === 'string') { // Escape single quotes by doubling them return `'${value.replace(/'/g, "''")}'`; } throw new Error(`Unsupported parameter type: ${typeof value}`); } /** * Parse pipe-delimited row into object */ private parsePipeDelimitedRow(line: string): Record<string, any> { // For simplicity, return as array of values // In production, would need column names from query const values = line.split('|'); return { value: values.length === 1 ? values[0] : values, }; } } /** * Mock database adapter for testing */ export class MockDatabaseAdapter implements DatabaseAdapter { private data: Map<string, any[]> = new Map(); setTableData(table: string, rows: any[]): void { this.data.set(table, rows); } async query(sql: string, params?: any[]): Promise<any> { const results = await this.selectAll(sql, params); return results.length > 0 ? results[0] : null; } async selectOne(sql: string, params?: any[]): Promise<any> { const results = await this.selectAll(sql, params); return results.length > 0 ? results[0] : null; } async selectAll(sql: string, params?: any[]): Promise<any[]> { // Mock implementation - parse SQL to determine which table to query if (sql.includes('FROM skills')) { const skills = this.data.get('skills') || []; // Handle WHERE name = ?1 if (params && params.length > 0 && sql.includes('WHERE name =')) { return skills.filter(s => s.name === params[0]); } // Handle COUNT(*) if (sql.includes('COUNT(*)')) { return [{ count: skills.length, value: skills.length }]; } return skills; } // Default: return empty array or result table return this.data.get('result') || []; } async execute(sql: string, params?: any[]): Promise<void> { // eslint-disable-next-line @typescript-eslint/no-unused-vars void sql; void params; // Mock implementation } async getAffectedAgents(skillId: number): Promise<string[]> { const mappings = this.data.get('agent_skill_mappings') || []; const agents = new Set<string>(); for (const mapping of mappings) { if (mapping.skill_id === skillId) { agents.add(mapping.agent_type); } } return Array.from(agents); } async close(): Promise<void> { this.data.clear(); } clear(): void { this.data.clear(); } }