UNPKG

@sethdouglasford/claude-flow

Version:

Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology

273 lines 9.21 kB
import * as fs from "fs/promises"; import * as fsSync from "fs"; import * as path from "path"; import { Logger } from "../core/logger.js"; const logger = Logger.getInstance(); export const DEFAULT_CONFIG = { 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", "**/*.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 { configPath; config; constructor(configPath) { this.configPath = configPath ?? path.join(process.cwd(), ".prompt-config.json"); this.config = { ...DEFAULT_CONFIG }; } async loadConfig() { try { const configData = await fs.readFile(this.configPath, "utf-8"); const userConfig = JSON.parse(configData); // 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) { 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() { return this.config; } getProfile(profileName) { const profile = this.config.profiles[profileName]; if (!profile) { throw new Error(`Profile '${profileName}' not found`); } return { ...this.config.defaultOptions, ...profile }; } listProfiles() { return Object.keys(this.config.profiles); } mergeConfig(base, override) { return { ...base, ...override, defaultOptions: { ...base.defaultOptions, ...override.defaultOptions, }, profiles: { ...base.profiles, ...override.profiles, }, }; } } export class PromptPathResolver { basePath; constructor(basePath = process.cwd()) { this.basePath = basePath; } resolvePaths(sourceDirectories, destinationDirectory) { const sources = sourceDirectories .map(dir => { // Handle both absolute and relative paths const resolvedPath = path.isAbsolute(dir) ? dir : path.resolve(this.basePath, dir); return resolvedPath; }) .filter(dir => this.directoryExists(dir)); const destination = path.isAbsolute(destinationDirectory) ? destinationDirectory : path.resolve(this.basePath, destinationDirectory); return { sources, destination }; } directoryExists(dirPath) { try { const stats = fsSync.statSync(dirPath); return stats.isDirectory(); } catch (error) { return false; } } // Discover prompt directories automatically async discoverPromptDirectories() { const candidates = [ ".roo", ".claude", "prompts", "templates", "src/prompts", "src/templates", "docs/prompts", "scripts/prompts", ]; const discovered = []; for (const candidate of candidates) { const fullPath = path.resolve(this.basePath, candidate); if (await this.containsPromptFiles(fullPath)) { discovered.push(fullPath); } } return discovered; } async containsPromptFiles(dirPath) { 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) { const issues = []; let metadata = {}; 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)}`], }; } } static parseFrontMatter(frontMatter) { // Simple YAML-like parser for basic key-value pairs const metadata = {}; 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) { const barLength = 40; return { update: (current) => { 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) { 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) { 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`; } //# sourceMappingURL=prompt-utils.js.map