UNPKG

claude-flow-multilang

Version:

Revolutionary multilingual AI orchestration framework with cultural awareness and DDD architecture

334 lines (287 loc) 8.73 kB
import * as fs from 'fs/promises'; import * as path from 'path'; import { logger } from '../core/logger.js'; export interface PromptConfig { sourceDirectories: string[]; destinationDirectory: string; defaultOptions: { backup: boolean; verify: boolean; parallel: boolean; maxWorkers: number; conflictResolution: 'skip' | 'overwrite' | 'backup' | 'merge'; includePatterns: string[]; excludePatterns: string[]; }; profiles: Record<string, Partial<PromptConfig['defaultOptions']>>; } export const DEFAULT_CONFIG: PromptConfig = { sourceDirectories: ['.roo', '.claude/commands', 'src/templates', 'templates'], destinationDirectory: './project-prompts', defaultOptions: { backup: true, verify: true, parallel: true, maxWorkers: 4, conflictResolution: 'backup', includePatterns: ['*.md', '*.txt', '*.prompt', '*.prompts', '*.json'], excludePatterns: ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**'], }, profiles: { sparc: { includePatterns: ['*.md', 'rules.md', 'sparc-*.md'], excludePatterns: ['**/README.md', '**/CHANGELOG.md'], }, templates: { includePatterns: ['*.template', '*.tmpl', '*.hbs', '*.mustache'], conflictResolution: 'merge', }, safe: { backup: true, verify: true, conflictResolution: 'skip', parallel: false, }, fast: { backup: false, verify: false, parallel: true, maxWorkers: 8, conflictResolution: 'overwrite', }, }, }; export class PromptConfigManager { private configPath: string; private config: PromptConfig; constructor(configPath?: string) { this.configPath = configPath || path.join(process.cwd(), '.prompt-config.json'); this.config = { ...DEFAULT_CONFIG }; } async loadConfig(): Promise<PromptConfig> { try { const configData = await fs.readFile(this.configPath, 'utf-8'); const userConfig = JSON.parse(configData) as Partial<PromptConfig>; // Merge with defaults this.config = this.mergeConfig(DEFAULT_CONFIG, userConfig); logger.info(`Loaded config from ${this.configPath}`); } catch (error) { logger.info('Using default configuration'); } return this.config; } async saveConfig(config?: Partial<PromptConfig>): Promise<void> { if (config) { this.config = this.mergeConfig(this.config, config); } await fs.writeFile(this.configPath, JSON.stringify(this.config, null, 2)); logger.info(`Saved config to ${this.configPath}`); } getConfig(): PromptConfig { return this.config; } getProfile(profileName: string): PromptConfig['defaultOptions'] { const profile = this.config.profiles[profileName]; if (!profile) { throw new Error(`Profile '${profileName}' not found`); } return { ...this.config.defaultOptions, ...profile }; } listProfiles(): string[] { return Object.keys(this.config.profiles); } private mergeConfig(base: PromptConfig, override: Partial<PromptConfig>): PromptConfig { return { ...base, ...override, defaultOptions: { ...base.defaultOptions, ...override.defaultOptions, }, profiles: { ...base.profiles, ...override.profiles, }, }; } } export class PromptPathResolver { private basePath: string; constructor(basePath: string = process.cwd()) { this.basePath = basePath; } resolvePaths( sourceDirectories: string[], destinationDirectory: string, ): { sources: string[]; destination: string; } { const sources = sourceDirectories .map((dir) => path.resolve(this.basePath, dir)) .filter((dir) => this.directoryExists(dir)); const destination = path.resolve(this.basePath, destinationDirectory); return { sources, destination }; } private directoryExists(dirPath: string): boolean { try { const stats = require('fs').statSync(dirPath); return stats.isDirectory(); } catch { return false; } } // Discover prompt directories automatically async discoverPromptDirectories(): Promise<string[]> { const candidates = [ '.roo', '.claude', 'prompts', 'templates', 'src/prompts', 'src/templates', 'docs/prompts', 'scripts/prompts', ]; const discovered: string[] = []; for (const candidate of candidates) { const fullPath = path.resolve(this.basePath, candidate); if (await this.containsPromptFiles(fullPath)) { discovered.push(fullPath); } } return discovered; } private async containsPromptFiles(dirPath: string): Promise<boolean> { try { const entries = await fs.readdir(dirPath, { withFileTypes: true }); for (const entry of entries) { if (entry.isFile()) { const fileName = entry.name.toLowerCase(); if ( fileName.endsWith('.md') || fileName.endsWith('.txt') || fileName.endsWith('.prompt') || fileName.includes('prompt') || fileName.includes('template') ) { return true; } } else if (entry.isDirectory()) { const subPath = path.join(dirPath, entry.name); if (await this.containsPromptFiles(subPath)) { return true; } } } return false; } catch { return false; } } } export class PromptValidator { static async validatePromptFile(filePath: string): Promise<{ valid: boolean; issues: string[]; metadata?: any; }> { const issues: string[] = []; let metadata: any = {}; try { const content = await fs.readFile(filePath, 'utf-8'); // Check for empty files if (content.trim().length === 0) { issues.push('File is empty'); } // Check for common prompt markers const hasPromptMarkers = [ '# ', '## ', '### ', // Markdown headers 'You are', 'Your task', 'Please', // Common prompt starters '```', '`', // Code blocks '{{', '}}', // Template variables ].some((marker) => content.includes(marker)); if (!hasPromptMarkers) { issues.push('File may not contain valid prompt content'); } // Extract metadata from front matter const frontMatterMatch = content.match(/^---\n([\s\S]*?\n)---/); if (frontMatterMatch) { try { metadata = this.parseFrontMatter(frontMatterMatch[1]); } catch (error) { issues.push('Invalid front matter format'); } } // Check file size (warn if too large) const stats = await fs.stat(filePath); if (stats.size > 100 * 1024) { // 100KB issues.push('File is unusually large for a prompt'); } return { valid: issues.length === 0, issues, metadata, }; } catch (error) { return { valid: false, issues: [`Failed to read file: ${error instanceof Error ? error.message : String(error)}`], }; } } private static parseFrontMatter(frontMatter: string): any { // Simple YAML-like parser for basic key-value pairs const metadata: any = {}; const lines = frontMatter.split('\n'); for (const line of lines) { const match = line.match(/^(\w+):\s*(.+)$/); if (match) { const [, key, value] = match; metadata[key] = value.trim(); } } return metadata; } } export function createProgressBar(total: number): { update: (current: number) => void; complete: () => void; } { const barLength = 40; return { update: (current: number) => { const percentage = Math.round((current / total) * 100); const filledLength = Math.round((current / total) * barLength); const bar = '█'.repeat(filledLength) + '░'.repeat(barLength - filledLength); process.stdout.write(`\r[${bar}] ${percentage}% (${current}/${total})`); }, complete: () => { process.stdout.write('\n'); }, }; } // Utility function to format file sizes export function formatFileSize(bytes: number): string { const units = ['B', 'KB', 'MB', 'GB']; let size = bytes; let unitIndex = 0; while (size >= 1024 && unitIndex < units.length - 1) { size /= 1024; unitIndex++; } return `${size.toFixed(1)} ${units[unitIndex]}`; } // Utility function to format duration export function formatDuration(ms: number): string { if (ms < 1000) return `${ms}ms`; if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`; return `${Math.floor(ms / 60000)}m ${Math.floor((ms % 60000) / 1000)}s`; }