UNPKG

@sprouted/create-vibes

Version:
151 lines (132 loc) 4.78 kB
import * as path from 'path'; import * as fs from 'fs/promises'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; import chalk from 'chalk'; import inquirer from 'inquirer'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); export class CreateSpecCommand { async execute(options) { const projectRoot = options.projectRoot || process.cwd(); const type = options.type || 'feature'; const status = options.status || 'draft'; // Convert spec name to kebab-case const specName = options.name.toLowerCase().replace(/\s+/g, '-'); const specType = type === 'feature' ? 'features' : type; // Create specs directory structure const specsDir = path.join(projectRoot, 'specs', specType); await fs.mkdir(specsDir, { recursive: true }); // Define spec file path const specFile = path.join(specsDir, `${specName}.md`); // Check if spec already exists if (!options.force) { try { await fs.access(specFile); console.log(chalk.yellow(`Spec '${specName}' already exists at ${specFile}`)); console.log(chalk.yellow('Use --force to overwrite')); return; } catch { // File doesn't exist, continue } } else if (options.force) { console.log(chalk.yellow(`Overwriting existing spec: ${specFile}`)); } // Create spec content const specContent = this.generateSpecContent(specName, type, status); // Write spec file await fs.writeFile(specFile, specContent); console.log(chalk.green(`✅ Created spec: ${specFile}`)); console.log('\nNext steps:'); console.log(chalk.cyan('1. Fill out the specification')); console.log(chalk.cyan('2. Get it reviewed and approved')); console.log(chalk.cyan(`3. Generate tests: vibe test ${specName}`)); console.log(chalk.cyan(`4. Implement: vibe create ${specName}`)); } generateSpecContent(name, type, status) { const title = name.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' '); const template = `# ${type === 'feature' ? 'Feature' : type === 'api' ? 'API' : 'Architecture'}: ${name} **Status**: ${status} **Created**: ${new Date().toISOString().split('T')[0]} **Type**: ${type} ## Overview <!-- Provide a brief description of what this ${type} does --> ## Motivation <!-- Why is this ${type} needed? What problem does it solve? --> ## Requirements ### Functional Requirements <!-- List the functional requirements --> - [ ] Requirement 1 - [ ] Requirement 2 ### Non-Functional Requirements <!-- List non-functional requirements (performance, security, etc.) --> - [ ] Performance: ... - [ ] Security: ... ## Technical Design ### Architecture <!-- Describe the high-level architecture --> ### API Design <!-- Define the API interface (if applicable) --> ### Data Model <!-- Define data structures and models --> ## Implementation Plan ### Phase 1: Foundation - [ ] Task 1 - [ ] Task 2 ### Phase 2: Core Features - [ ] Task 3 - [ ] Task 4 ## Testing Strategy ### Unit Tests - [ ] Test case 1 - [ ] Test case 2 ### Integration Tests - [ ] Test scenario 1 - [ ] Test scenario 2 ## Success Criteria <!-- How will we know this ${type} is successful? --> - [ ] Criteria 1 - [ ] Criteria 2 ## Dependencies <!-- List any dependencies on other features or systems --> ## Notes <!-- Additional notes or considerations --> `; return template; } async interactivePrompt() { const answers = await inquirer.prompt([ { type: 'input', name: 'name', message: 'What should we call this spec?', validate: (input) => { if (!input.trim()) return 'Spec name is required'; if (!/^[a-zA-Z0-9-\s]+$/.test(input)) { return 'Spec name can only contain letters, numbers, hyphens, and spaces'; } return true; } }, { type: 'list', name: 'type', message: 'What type of spec?', choices: ['feature', 'api', 'architecture'], default: 'feature' }, { type: 'list', name: 'status', message: 'Initial status?', choices: ['draft', 'review', 'approved'], default: 'draft' } ]); return answers; } } //# sourceMappingURL=create-spec.js.map