UNPKG

cmte

Version:

Design by Committee™ except it's just you and LLMs

154 lines (133 loc) 4.34 kB
import path from 'path'; /** * Components that make up a Committee path */ /** * Utility class for handling Committee paths */ export class CommitteePaths { /** * Constructs a path from components */ static constructPath(base, components) { const parts = []; // Add phase parts.push(components.phase); // Add phase iteration if present if (components.phaseIteration) { parts.push(components.phaseIteration); } // Add set if (components.set) { parts.push(components.set); // Add set iteration if present if (components.setIteration) { parts.push(components.setIteration); } } // Add task if present if (components.task) { parts.push(components.task); // Add task iteration if present if (components.taskIteration) { parts.push(components.taskIteration); } } // Filter out any empty or undefined parts const filteredParts = parts.filter(part => part && part.trim()); return path.join(base, ...filteredParts); } /** * Resolves a full path back into its components */ static resolvePath(fullPath) { // Remove file extension if present const withoutExt = fullPath.replace(/\.[^/.]+$/, ''); // Split path into parts const parts = withoutExt.split(path.sep).filter(Boolean); // We expect paths to be in the format: // phase/phase-iteration?/set/set-iteration?/task?/task-iteration? const components = { phase: parts[0], set: parts[parts.length >= 2 ? 1 : 0] }; // If we have more than 2 parts, we need to determine what they are if (parts.length > 2) { let currentIndex = 2; // Check if we have a phase iteration if (parts[1].startsWith(parts[0]) || !parts[1].includes('/')) { components.phaseIteration = parts[1]; components.set = parts[2]; currentIndex = 3; } // Check for set iteration if (currentIndex < parts.length) { if (parts[currentIndex].startsWith(components.set) || !parts[currentIndex].includes('/')) { components.setIteration = parts[currentIndex]; currentIndex++; } } // Check for task and task iteration if (currentIndex < parts.length) { components.task = parts[parts.length - 1]; if (currentIndex < parts.length - 1) { components.taskIteration = parts[parts.length - 2]; } } } return components; } /** * Gets a name for an iteration based on the item */ static getIterationName(item, index, prefix) { // Sanitize the prefix to be filesystem safe const safePrefix = prefix.replace(/[^a-zA-Z0-9-_]/g, '-'); if (typeof item === 'string') { // Sanitize string values to be filesystem safe return item.replace(/[^a-zA-Z0-9-_]/g, '-'); } if (item?.name) { return String(item.name).replace(/[^a-zA-Z0-9-_]/g, '-'); } if (item?.id) { return String(item.id).replace(/[^a-zA-Z0-9-_]/g, '-'); } return `${safePrefix}-${index + 1}`; } /** * Converts a camelCase string to kebab-case */ static camelToKebab(str) { return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase(); } /** * Constructs an output file path */ static constructOutputPath(base, components, type) { // For task outputs, save directly in the phase directory if (components.task) { const basePath = path.join(base, components.phase); const outputName = this.camelToKebab(components.outputName || components.task); if (type) { return path.join(basePath, `${outputName}-${type}.o.md`); } return path.join(basePath, `${outputName}.o.md`); } // For set and phase outputs, use the full path structure const basePath = this.constructPath(base, components); const filename = components.set; if (type) { return path.join(basePath, `${filename}-${type}.o.md`); } return path.join(basePath, `${filename}.o.md`); } /** * Constructs a prompt file path */ static constructPromptPath(base, components, type) { const basePath = this.constructPath(base, components); const filename = components.task || components.set; return path.join(basePath, `${filename}-${type}.prompt.xml`); } }