@sethdouglasford/claude-flow
Version:
Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology
286 lines • 11.4 kB
JavaScript
import * as path from "path";
import { EventEmitter } from "events";
import { copyPromptsEnhanced } from "./prompt-copier-enhanced";
import { PromptConfigManager, PromptPathResolver, PromptValidator, } from "./prompt-utils";
import { Logger } from "../core/logger.js";
const logger = Logger.getInstance();
export class PromptManager extends EventEmitter {
configManager;
pathResolver;
options;
constructor(options = {}) {
super();
this.options = {
configPath: options.configPath ?? ".prompt-config.json",
basePath: options.basePath ?? process.cwd(),
autoDiscovery: options.autoDiscovery ?? true,
defaultProfile: options.defaultProfile ?? "sparc",
};
this.configManager = new PromptConfigManager(path.resolve(this.options.basePath, this.options.configPath));
this.pathResolver = new PromptPathResolver(this.options.basePath);
}
async initialize() {
logger.info("Initializing PromptManager...");
// Load configuration
await this.configManager.loadConfig();
// Auto-discover prompt directories if enabled
if (this.options.autoDiscovery) {
const discovered = await this.pathResolver.discoverPromptDirectories();
if (discovered.length > 0) {
logger.info(`Auto-discovered ${discovered.length} prompt directories`);
// Update config with discovered directories
const config = this.configManager.getConfig();
const uniqueDirs = Array.from(new Set([
...config.sourceDirectories,
...discovered.map(dir => path.relative(this.options.basePath, dir)),
]));
await this.configManager.saveConfig({
sourceDirectories: uniqueDirs,
});
}
}
this.emit("initialized");
}
async copyPrompts(options = {}) {
const config = this.configManager.getConfig();
const profile = this.options.defaultProfile;
// Resolve paths
const resolved = this.pathResolver.resolvePaths(config.sourceDirectories, config.destinationDirectory);
if (resolved.sources.length === 0) {
logger.warn("No valid source directories found");
const emptyResult = {
success: false,
totalFiles: 0,
copiedFiles: 0,
failedFiles: 0,
skippedFiles: 0,
errors: [{
file: "source",
error: "No valid source directories found",
phase: "read",
}],
duration: 0,
};
this.emit("copyError", new Error("No valid source directories found"));
return emptyResult;
}
// Build copy options
const copyOptions = {
source: resolved.sources[0], // Use first available source
destination: resolved.destination,
...this.configManager.getProfile(profile),
...options,
};
logger.info("Starting prompt copy operation", {
source: copyOptions.source,
destination: copyOptions.destination,
profile,
});
this.emit("copyStart", copyOptions);
try {
const result = await copyPromptsEnhanced(copyOptions);
this.emit("copyComplete", result);
return result;
}
catch (error) {
this.emit("copyError", error);
throw error;
}
}
async copyFromMultipleSources(options = {}) {
const config = this.configManager.getConfig();
const resolved = this.pathResolver.resolvePaths(config.sourceDirectories, config.destinationDirectory);
const results = [];
for (const source of resolved.sources) {
try {
const copyOptions = {
source,
destination: resolved.destination,
...this.configManager.getProfile(this.options.defaultProfile),
...options,
};
logger.info(`Copying from source: ${source}`);
const result = await copyPromptsEnhanced(copyOptions);
results.push(result);
this.emit("sourceComplete", { source, result });
}
catch (error) {
logger.error(`Failed to copy from ${source}:`, error);
this.emit("sourceError", { source, error });
// Add error result
results.push({
success: false,
totalFiles: 0,
copiedFiles: 0,
failedFiles: 0,
skippedFiles: 0,
errors: [{ file: source, error: error instanceof Error ? error.message : String(error), phase: "read" }],
duration: 0,
});
}
}
return results;
}
async validatePrompts(sourcePath) {
const config = this.configManager.getConfig();
const sources = sourcePath ? [sourcePath] : config.sourceDirectories;
const resolved = this.pathResolver.resolvePaths(sources, config.destinationDirectory);
let totalFiles = 0;
let validFiles = 0;
let invalidFiles = 0;
const issues = [];
for (const source of resolved.sources) {
await this.validateDirectory(source, issues);
}
totalFiles = issues.length;
validFiles = issues.filter(issue => issue.issues.length === 0).length;
invalidFiles = totalFiles - validFiles;
const report = {
totalFiles,
validFiles,
invalidFiles,
issues: issues.filter(issue => issue.issues.length > 0), // Only include files with issues
};
this.emit("validationComplete", report);
return report;
}
async validateDirectory(dirPath, issues) {
const fs = await import("fs/promises");
try {
const entries = await fs.readdir(dirPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
if (entry.isFile() && this.isPromptFile(entry.name)) {
const result = await PromptValidator.validatePromptFile(fullPath);
issues.push({
file: fullPath,
issues: result.issues,
metadata: result.metadata,
});
}
else if (entry.isDirectory()) {
await this.validateDirectory(fullPath, issues);
}
}
}
catch (error) {
logger.error(`Failed to validate directory ${dirPath}:`, error);
}
}
isPromptFile(fileName) {
const config = this.configManager.getConfig();
const patterns = config.defaultOptions.includePatterns;
return patterns.some(pattern => {
const regex = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*");
return new RegExp(regex).test(fileName);
});
}
async syncPrompts(options = {}) {
const config = this.configManager.getConfig();
const resolved = this.pathResolver.resolvePaths(config.sourceDirectories, config.destinationDirectory);
const syncOptions = {
bidirectional: false,
deleteOrphaned: false,
compareHashes: true,
incrementalOnly: true,
...options,
};
// Forward sync (source to destination)
const forwardResult = await this.performIncrementalSync(resolved.sources[0], resolved.destination, syncOptions);
let backwardResult;
// Backward sync if bidirectional
if (syncOptions.bidirectional) {
backwardResult = await this.performIncrementalSync(resolved.destination, resolved.sources[0], syncOptions);
}
return {
forward: forwardResult,
backward: backwardResult,
};
}
async performIncrementalSync(source, destination, options) {
// This would implement incremental sync logic
// For now, we'll use the regular copy with overwrite
return copyPromptsEnhanced({
source,
destination,
conflictResolution: "overwrite",
verify: options.compareHashes,
// Disable parallel in test environment to avoid worker thread issues
parallel: process.env.NODE_ENV === "test" ? false : true,
});
}
async generateReport() {
const config = this.configManager.getConfig();
const resolved = this.pathResolver.resolvePaths(config.sourceDirectories, config.destinationDirectory);
// Analyze sources
const sources = await Promise.all(resolved.sources.map(async (sourcePath) => {
try {
const fs = await import("fs/promises");
const stats = await fs.stat(sourcePath);
if (!stats.isDirectory()) {
return { path: sourcePath, exists: false };
}
// Count files and calculate total size
let fileCount = 0;
let totalSize = 0;
const scanDir = async (dir) => {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isFile() && this.isPromptFile(entry.name)) {
const fileStats = await fs.stat(fullPath);
fileCount++;
totalSize += fileStats.size;
}
else if (entry.isDirectory()) {
await scanDir(fullPath);
}
}
};
await scanDir(sourcePath);
return {
path: sourcePath,
exists: true,
fileCount,
totalSize,
};
}
catch {
return { path: sourcePath, exists: false };
}
}));
return {
configuration: config,
sources,
};
}
// Utility methods
getConfig() {
return this.configManager.getConfig();
}
async updateConfig(updates) {
await this.configManager.saveConfig(updates);
}
getProfiles() {
return this.configManager.listProfiles();
}
getProfile(name) {
return this.configManager.getProfile(name);
}
async discoverPromptDirectories() {
return this.pathResolver.discoverPromptDirectories();
}
}
// Export factory function
export function createPromptManager(options) {
return new PromptManager(options);
}
// Export singleton instance
let defaultManager = null;
export function getDefaultPromptManager() {
if (!defaultManager) {
defaultManager = new PromptManager();
}
return defaultManager;
}
//# sourceMappingURL=prompt-manager.js.map