UNPKG

claude-flow-novice

Version:

Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes Local RuVector Accelerator and all CFN skills for complete functionality.

447 lines (446 loc) 17.7 kB
/** * Artifact Registry TypeScript API * Provides centralized artifact management with TTL-based cleanup * Version: 1.0.0 */ import Database from 'better-sqlite3'; import { createHash } from 'crypto'; import { readFileSync, existsSync, mkdirSync, statSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; // ESM-compatible __dirname const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // ============================================================================ // Error Classes // ============================================================================ export class ArtifactRegistryError extends Error { code; details; constructor(message, code, details){ super(message), this.code = code, this.details = details; this.name = 'ArtifactRegistryError'; } } export class ArtifactNotFoundError extends ArtifactRegistryError { constructor(id){ super(`Artifact not found: ${id}`, 'ARTIFACT_NOT_FOUND', { id }); this.name = 'ArtifactNotFoundError'; } } export class ArtifactValidationError extends ArtifactRegistryError { constructor(message, details){ super(message, 'VALIDATION_ERROR', details); this.name = 'ArtifactValidationError'; } } export class ArtifactDatabaseError extends ArtifactRegistryError { constructor(message, details){ super(message, 'DATABASE_ERROR', details); this.name = 'ArtifactDatabaseError'; } } // ============================================================================ // Artifact Registry Class // ============================================================================ export class ArtifactRegistry { db; static instance = null; constructor(dbPath){ try { // Ensure database directory exists const dbDir = dirname(dbPath); if (!existsSync(dbDir)) { mkdirSync(dbDir, { recursive: true }); } this.db = new Database(dbPath); this.db.pragma('journal_mode = WAL'); // Write-Ahead Logging for concurrency this.db.pragma('foreign_keys = ON'); this.db.pragma('synchronous = NORMAL'); this.initializeSchema(); } catch (error) { throw new ArtifactDatabaseError(`Failed to initialize database: ${error instanceof Error ? error.message : String(error)}`, { dbPath, error }); } } /** * Get singleton instance (optional pattern) */ static getInstance(dbPath) { if (!ArtifactRegistry.instance) { if (!dbPath) { throw new ArtifactDatabaseError('Database path required for first initialization', {}); } ArtifactRegistry.instance = new ArtifactRegistry(dbPath); } return ArtifactRegistry.instance; } /** * Initialize database schema */ initializeSchema() { try { const schemaPath = join(__dirname, '../database/artifact-registry-schema.sql'); if (!existsSync(schemaPath)) { throw new Error(`Schema file not found: ${schemaPath}`); } const schema = readFileSync(schemaPath, 'utf-8'); // Step 1: Remove multi-line comments FIRST (they can contain semicolons) const withoutMultiLineComments = schema.replace(/\/\*[\s\S]*?\*\//g, ''); // Step 2: Remove single-line comments const cleanedSchema = withoutMultiLineComments.split('\n').filter((line)=>{ const trimmed = line.trim(); return trimmed.length > 0 && !trimmed.startsWith('--'); }).join('\n'); // Step 3: Execute entire schema as one block // SQLite's exec() can handle multiple statements including triggers with internal semicolons this.db.exec(cleanedSchema); } catch (error) { throw new ArtifactDatabaseError(`Failed to initialize schema: ${error instanceof Error ? error.message : String(error)}`, { error }); } } /** * Generate unique artifact ID */ generateId() { const timestamp = Date.now(); const random = Math.random().toString(36).substring(2, 15); return `artifact-${timestamp}-${random}`; } /** * Calculate checksum for file */ calculateChecksum(filePath) { try { const content = readFileSync(filePath); return createHash('sha256').update(content).digest('hex'); } catch (error) { throw new ArtifactValidationError(`Failed to calculate checksum: ${error instanceof Error ? error.message : String(error)}`, { filePath, error }); } } /** * Validate artifact metadata */ validateMetadata(metadata) { if (!metadata.name || metadata.name.trim().length === 0) { throw new ArtifactValidationError('Artifact name is required'); } if (!metadata.type) { throw new ArtifactValidationError('Artifact type is required'); } const validTypes = [ 'code', 'documentation', 'test', 'config', 'binary', 'data', 'model', 'report', 'other' ]; if (!validTypes.includes(metadata.type)) { throw new ArtifactValidationError(`Invalid artifact type: ${metadata.type}`, { validTypes }); } if (!metadata.storage_location || metadata.storage_location.trim().length === 0) { throw new ArtifactValidationError('Storage location is required'); } if (metadata.acl_level !== undefined && (metadata.acl_level < 1 || metadata.acl_level > 5)) { throw new ArtifactValidationError('ACL level must be between 1 and 5', { acl_level: metadata.acl_level }); } if (metadata.retention_policy) { const validPolicies = [ 'ephemeral', 'standard', 'permanent', 'custom' ]; if (!validPolicies.includes(metadata.retention_policy)) { throw new ArtifactValidationError(`Invalid retention policy: ${metadata.retention_policy}`, { validPolicies }); } } } /** * Create new artifact */ createArtifact(metadata) { try { // Validate metadata this.validateMetadata(metadata); // Generate ID const id = this.generateId(); // Calculate file-based metadata if storage location exists let size_bytes; let checksum; let content_hash; if (existsSync(metadata.storage_location)) { const stats = statSync(metadata.storage_location); size_bytes = stats.size; checksum = this.calculateChecksum(metadata.storage_location); if (metadata.content) { content_hash = createHash('sha256').update(metadata.content).digest('hex'); } } // Set defaults const retention_days = metadata.retention_days ?? (metadata.retention_policy === 'ephemeral' ? 7 : metadata.retention_policy === 'permanent' ? 0 : 30 // standard ); const retention_policy = metadata.retention_policy ?? 'standard'; // Prepare insert statement const stmt = this.db.prepare(` INSERT INTO artifacts ( id, name, type, format, content, content_hash, size_bytes, storage_location, checksum, is_compressed, compression_type, swarm_id, agent_id, task_id, version, parent_artifact_id, artifact_chain, tags, metadata, acl_level, retention_days, retention_policy, status ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) `); // Execute insert stmt.run(id, metadata.name, metadata.type, metadata.format ?? null, metadata.content ?? null, content_hash ?? null, size_bytes ?? null, metadata.storage_location, checksum ?? null, metadata.is_compressed ? 1 : 0, metadata.compression_type ?? null, metadata.swarm_id ?? null, metadata.agent_id ?? null, metadata.task_id ?? null, metadata.version ?? 1, metadata.parent_artifact_id ?? null, metadata.artifact_chain ? JSON.stringify(metadata.artifact_chain) : null, metadata.tags ? JSON.stringify(metadata.tags) : null, metadata.metadata ? JSON.stringify(metadata.metadata) : null, metadata.acl_level ?? 2, retention_days, retention_policy, 'active'); // Retrieve and return created artifact const artifact = this.getArtifact(id); if (!artifact) { throw new ArtifactDatabaseError('Failed to retrieve created artifact', { id }); } return artifact; } catch (error) { if (error instanceof ArtifactRegistryError) { throw error; } throw new ArtifactDatabaseError(`Failed to create artifact: ${error instanceof Error ? error.message : String(error)}`, { metadata, error }); } } /** * Get artifact by ID */ getArtifact(id) { try { const stmt = this.db.prepare('SELECT * FROM artifacts WHERE id = ?'); const row = stmt.get(id); return row ?? null; } catch (error) { throw new ArtifactDatabaseError(`Failed to get artifact: ${error instanceof Error ? error.message : String(error)}`, { id, error }); } } /** * List artifacts with optional filters */ listArtifacts(filters) { try { let query = 'SELECT * FROM artifacts WHERE 1=1'; const params = []; if (filters) { if (filters.type) { query += ' AND type = ?'; params.push(filters.type); } if (filters.status) { query += ' AND status = ?'; params.push(filters.status); } if (filters.retention_policy) { query += ' AND retention_policy = ?'; params.push(filters.retention_policy); } if (filters.swarm_id) { query += ' AND swarm_id = ?'; params.push(filters.swarm_id); } if (filters.agent_id) { query += ' AND agent_id = ?'; params.push(filters.agent_id); } if (filters.task_id) { query += ' AND task_id = ?'; params.push(filters.task_id); } if (filters.cleanup_eligible !== undefined) { query += ' AND cleanup_eligible = ?'; params.push(filters.cleanup_eligible ? 1 : 0); } if (filters.created_after) { query += ' AND created_at >= ?'; params.push(filters.created_after.toISOString()); } if (filters.created_before) { query += ' AND created_at <= ?'; params.push(filters.created_before.toISOString()); } if (filters.expires_before) { query += ' AND expires_at IS NOT NULL AND expires_at <= ?'; params.push(filters.expires_before.toISOString()); } if (filters.tags && filters.tags.length > 0) { // JSON search for tags (SQLite JSON support) for (const tag of filters.tags){ query += ` AND tags LIKE ?`; params.push(`%"${tag}"%`); } } } query += ' ORDER BY created_at DESC'; if (filters?.limit) { query += ' LIMIT ?'; params.push(filters.limit); } if (filters?.offset) { query += ' OFFSET ?'; params.push(filters.offset); } const stmt = this.db.prepare(query); return stmt.all(...params); } catch (error) { throw new ArtifactDatabaseError(`Failed to list artifacts: ${error instanceof Error ? error.message : String(error)}`, { filters, error }); } } /** * Archive artifact (mark as archived) */ archiveArtifact(id) { try { const artifact = this.getArtifact(id); if (!artifact) { throw new ArtifactNotFoundError(id); } if (artifact.status === 'deleted') { throw new ArtifactValidationError('Cannot archive deleted artifact', { id, status: artifact.status }); } const stmt = this.db.prepare(` UPDATE artifacts SET status = 'archived', archived_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP WHERE id = ? `); stmt.run(id); const updated = this.getArtifact(id); if (!updated) { throw new ArtifactDatabaseError('Failed to retrieve archived artifact', { id }); } return updated; } catch (error) { if (error instanceof ArtifactRegistryError) { throw error; } throw new ArtifactDatabaseError(`Failed to archive artifact: ${error instanceof Error ? error.message : String(error)}`, { id, error }); } } /** * Delete artifact (soft delete - mark as deleted) */ deleteArtifact(id) { try { const artifact = this.getArtifact(id); if (!artifact) { throw new ArtifactNotFoundError(id); } const stmt = this.db.prepare(` UPDATE artifacts SET status = 'deleted', deleted_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP WHERE id = ? `); stmt.run(id); const updated = this.getArtifact(id); if (!updated) { throw new ArtifactDatabaseError('Failed to retrieve deleted artifact', { id }); } return updated; } catch (error) { if (error instanceof ArtifactRegistryError) { throw error; } throw new ArtifactDatabaseError(`Failed to delete artifact: ${error instanceof Error ? error.message : String(error)}`, { id, error }); } } /** * Get artifact statistics by retention policy */ getStatsByRetentionPolicy() { try { const stmt = this.db.prepare(` SELECT retention_policy, COUNT(*) as total, SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) as active, SUM(CASE WHEN status = 'archived' THEN 1 ELSE 0 END) as archived, SUM(CASE WHEN status = 'deleted' THEN 1 ELSE 0 END) as deleted, SUM(CASE WHEN cleanup_eligible = 1 THEN 1 ELSE 0 END) as cleanup_eligible, COALESCE(SUM(size_bytes), 0) as total_size_bytes FROM artifacts GROUP BY retention_policy `); const rows = stmt.all(); const stats = {}; for (const row of rows){ const { retention_policy, ...statsData } = row; stats[retention_policy] = statsData; } return stats; } catch (error) { throw new ArtifactDatabaseError(`Failed to get statistics: ${error instanceof Error ? error.message : String(error)}`, { error }); } } /** * Find expired artifacts eligible for cleanup */ findExpiredArtifacts() { try { const stmt = this.db.prepare(` SELECT * FROM artifacts WHERE status = 'active' AND expires_at IS NOT NULL AND datetime('now') >= expires_at ORDER BY created_at ASC `); return stmt.all(); } catch (error) { throw new ArtifactDatabaseError(`Failed to find expired artifacts: ${error instanceof Error ? error.message : String(error)}`, { error }); } } /** * Close database connection */ close() { this.db.close(); } } //# sourceMappingURL=artifact-registry.js.map