UNPKG

agent-rules-generator

Version:

Interactive CLI tool to generate .agent.md and .windsurfrules files for AI-assisted development

542 lines (451 loc) 19.2 kB
const fs = require('fs').promises; const path = require('path'); /** * Generates the content for .agent.md or .windsurfrules files * @param {Object} config - Configuration object containing all project settings * @param {Object} inquirer - Inquirer instance for user prompts * @returns {string} Generated file content */ async function generateAgentFile(config, inquirer) { const isAgent = config.fileType === 'agent'; const templateName = isAgent ? 'agent-template' : 'windsurf-template'; const template = await loadTemplate(templateName); if (template) { const { useTemplate } = await inquirer.prompt([ { type: 'confirm', name: 'useTemplate', message: `A template (${templateName}.md) was found. Would you like to use it?`, default: true } ]); if (useTemplate) { return replacePlaceholders(template, config); } } return isAgent ? generateAgentMd(config) : generateWindsurfRules(config); } /** * Generates .agent.md content for Cursor AI * @param {Object} config - Configuration object * @returns {string} Generated .agent.md content */ function generateAgentMd(config) { const { overview, codingStandards, projectStructure, technologyStack, projectManagement, workflowGuidelines } = config; return `# ${overview.projectName} - AI Assistant Rules ## Project Overview **Project Name:** ${overview.projectName} **Version:** ${overview.version} **Description:** ${overview.description} **Project Type:** ${overview.projectType.join(', ')} ## Technology Stack ${Object.entries(technologyStack) .filter(([key, value]) => value && value.trim()) .map(([key, value]) => `- **${capitalize(key)}:** ${value}`) .join('\n')} ## Project Structure \`\`\` ${overview.projectName}/ ├── ${projectStructure.sourceDir}/ # Source code ├── ${projectStructure.testDir}/ # Test files ├── ${projectStructure.buildDir}/ # Build output ├── ${projectStructure.configDir}/ # Configuration files └── README.md \`\`\` **Organization Pattern:** ${projectStructure.organization} ## Coding Standards ### Code Style - **Indentation:** ${codingStandards.indentation} - **Quotes:** ${codingStandards.quotes} - **Naming Conventions:** ${codingStandards.naming} ### Tools ${codingStandards.linting.map(tool => `- ${tool}`).join('\n')} ### Comments ${codingStandards.comments} ## Development Workflow ### Git Workflow - **Strategy:** ${workflowGuidelines.gitWorkflow} - **Branch Naming:** ${workflowGuidelines.branchNaming} - **Commit Style:** ${workflowGuidelines.commitStyle} ### CI/CD ${workflowGuidelines.cicd.map(process => `- ${process}`).join('\n')} ### Deployment ${workflowGuidelines.deploymentSteps} ## Project Management ### Methodology ${projectManagement.methodology.join(', ')} ### Tools - **Issue Tracking:** ${projectManagement.issueTracking} - **Documentation:** ${projectManagement.documentation} ### Code Review ${projectManagement.codeReview.map(process => `- ${process}`).join('\n')} ## AI Assistant Guidelines When working on this project, please: 1. **Follow the established coding standards** outlined above 2. **Maintain the project structure** and organization patterns 3. **Use the specified technology stack** and avoid introducing new dependencies without discussion 4. **Write tests** for new features and bug fixes 5. **Follow the git workflow** for branch naming and commit messages 6. **Document your changes** appropriately 7. **Consider performance and maintainability** in your solutions ### Code Generation Preferences - Generate code that follows the project's naming conventions - Include appropriate error handling and validation - Add relevant comments for complex logic - Suggest improvements for code quality when applicable - Provide test cases for new functionality ### File Modification Guidelines - Always backup important files before major changes - Follow the project's directory structure - Update documentation when adding new features - Maintain consistency with existing code patterns --- *This file was generated by agent-rules-generator v1.0.0*`; } /** * Generates .windsurfrules content for Windsurf * @param {Object} config - Configuration object * @returns {string} Generated .windsurfrules content */ function generateWindsurfRules(config) { const { overview, codingStandards, projectStructure, technologyStack, projectManagement, workflowGuidelines } = config; return `# ${overview.projectName} - Windsurf Rules ## Project Context This is a ${overview.projectType.join(', ')} project called "${overview.projectName}". **Description:** ${overview.description} **Version:** ${overview.version} ## Technology Stack ${Object.entries(technologyStack) .filter(([key, value]) => value && value.trim()) .map(([key, value]) => `${capitalize(key)}: ${value}`) .join('\n')} ## Code Style Rules ### Formatting - Use ${codingStandards.indentation} for indentation - Use ${codingStandards.quotes} quotes - Follow ${codingStandards.naming} naming conventions ### Linting Active linters: ${codingStandards.linting.join(', ')} ### Comments ${codingStandards.comments} ## Project Structure Source code is organized in the \`${projectStructure.sourceDir}/\` directory. Tests are located in \`${projectStructure.testDir}/\`. Build output goes to \`${projectStructure.buildDir}/\`. Configuration files are in \`${projectStructure.configDir}/\`. Organization: ${projectStructure.organization} ## Development Workflow ### Git - Workflow: ${workflowGuidelines.gitWorkflow} - Branch naming: ${workflowGuidelines.branchNaming} - Commit style: ${workflowGuidelines.commitStyle} ### CI/CD ${workflowGuidelines.cicd.map(process => `- ${process}`).join('\n')} ### Deployment ${workflowGuidelines.deploymentSteps} ## Code Generation Rules 1. **Consistency**: Always follow the established patterns and conventions 2. **Testing**: Include test cases for new functionality 3. **Documentation**: Add JSDoc comments for functions and classes 4. **Error Handling**: Implement proper error handling and validation 5. **Performance**: Consider performance implications of generated code 6. **Security**: Follow security best practices for the technology stack ## File Organization Rules - Place components in appropriate directories following the project structure - Use consistent file naming conventions - Import statements should be organized and clean - Export statements should be clear and documented ## Specific Technology Guidelines ${generateTechSpecificGuidelines(technologyStack)} ## Project Management Methodology: ${projectManagement.methodology.join(', ')} Issue tracking: ${projectManagement.issueTracking} Documentation: ${projectManagement.documentation} Code review: ${projectManagement.codeReview.join(', ')} ## Quality Standards - All code must pass linting checks - Tests should achieve reasonable coverage - Code should be self-documenting with clear variable names - Complex logic should include explanatory comments - Performance should be considered for user-facing features --- *Generated by agent-rules-generator v1.0.0*`; } /** * Generates technology-specific guidelines based on the stack * @param {Object} techStack - Technology stack configuration * @returns {string} Technology-specific guidelines */ function generateTechSpecificGuidelines(techStack) { const guidelines = []; // Frontend frameworks if (techStack.frontend) { const frontend = techStack.frontend.toLowerCase(); if (frontend.includes('react')) { guidelines.push('### React Guidelines\n- Use functional components with hooks\n- Follow React best practices for state management\n- Use proper prop types or TypeScript types'); } if (frontend.includes('vue')) { guidelines.push('### Vue Guidelines\n- Use Composition API for new components\n- Follow Vue 3 best practices\n- Use proper component naming conventions'); } if (frontend.includes('angular')) { guidelines.push('### Angular Guidelines\n- Use Angular CLI for project structure\n- Follow Angular style guide\n- Use TypeScript and RxJS patterns'); } if (frontend.includes('svelte')) { guidelines.push('### Svelte Guidelines\n- Use Svelte stores for state management\n- Follow Svelte component patterns\n- Leverage reactive declarations'); } } // Backend frameworks if (techStack.backend) { const backend = techStack.backend.toLowerCase(); if (backend.includes('express')) { guidelines.push('### Express Guidelines\n- Use proper middleware structure\n- Implement proper error handling\n- Follow RESTful API conventions'); } if (backend.includes('fastapi')) { guidelines.push('### FastAPI Guidelines\n- Use proper type hints\n- Follow FastAPI documentation patterns\n- Implement proper dependency injection'); } if (backend.includes('django')) { guidelines.push('### Django Guidelines\n- Follow Django project structure\n- Use Django ORM best practices\n- Implement proper authentication'); } if (backend.includes('spring')) { guidelines.push('### Spring Boot Guidelines\n- Use Spring annotations properly\n- Follow dependency injection patterns\n- Implement proper exception handling'); } } // Databases if (techStack.database) { const database = techStack.database.toLowerCase(); if (database.includes('mongodb')) { guidelines.push('### MongoDB Guidelines\n- Use proper schema design\n- Implement proper indexing\n- Follow MongoDB best practices'); } if (database.includes('postgresql')) { guidelines.push('### PostgreSQL Guidelines\n- Use proper normalization\n- Implement proper constraints\n- Follow SQL best practices'); } if (database.includes('mysql')) { guidelines.push('### MySQL Guidelines\n- Use proper indexing strategies\n- Follow MySQL naming conventions\n- Implement proper backup strategies'); } if (database.includes('sqlite')) { guidelines.push('### SQLite Guidelines\n- Use appropriate data types\n- Implement proper transaction handling\n- Consider file-based limitations'); } } // CLI frameworks if (techStack.cliFramework) { const cli = techStack.cliFramework.toLowerCase(); if (cli.includes('commander')) { guidelines.push('### Commander.js Guidelines\n- Use proper command structure\n- Implement help and version commands\n- Handle arguments and options correctly'); } if (cli.includes('yargs')) { guidelines.push('### Yargs Guidelines\n- Use command builders\n- Implement proper validation\n- Provide meaningful help text'); } if (cli.includes('inquirer')) { guidelines.push('### Inquirer.js Guidelines\n- Use appropriate prompt types\n- Implement validation functions\n- Handle user interruption gracefully'); } } // Mobile frameworks if (techStack.mobileFramework) { const mobile = techStack.mobileFramework.toLowerCase(); if (mobile.includes('react native')) { guidelines.push('### React Native Guidelines\n- Use platform-specific code when needed\n- Follow React Native performance best practices\n- Use proper navigation patterns'); } if (mobile.includes('flutter')) { guidelines.push('### Flutter Guidelines\n- Use proper widget composition\n- Follow Dart language conventions\n- Implement proper state management'); } } // Desktop frameworks if (techStack.desktopFramework) { const desktop = techStack.desktopFramework.toLowerCase(); if (desktop.includes('electron')) { guidelines.push('### Electron Guidelines\n- Follow security best practices\n- Optimize for performance and memory usage\n- Use proper IPC communication'); } if (desktop.includes('tauri')) { guidelines.push('### Tauri Guidelines\n- Leverage Rust for performance-critical code\n- Use proper frontend-backend communication\n- Follow Tauri security guidelines'); } } // Build tools if (techStack.tools) { const tools = techStack.tools.toLowerCase(); if (tools.includes('webpack')) { guidelines.push('### Webpack Guidelines\n- Use proper configuration structure\n- Optimize bundle size and performance\n- Implement proper development/production configs'); } if (tools.includes('vite')) { guidelines.push('### Vite Guidelines\n- Leverage ES modules for fast development\n- Use proper plugin configuration\n- Optimize for production builds'); } } return guidelines.join('\n\n'); } /** * Capitalizes the first letter of a string * @param {string} str - String to capitalize * @returns {string} Capitalized string */ function capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } /** * Gets the file extension based on language * @param {string} language - Programming language * @returns {string} File extension */ function getFileExtension(language) { const extensions = { 'JavaScript': 'js', 'TypeScript': 'ts', 'Python': 'py', 'Java': 'java', 'C#': 'cs', 'Go': 'go', 'Rust': 'rs', 'PHP': 'php', 'Ruby': 'rb' }; return extensions[language] || 'js'; } /** * Loads a custom template if available * @param {string} templateName - Name of the template to load * @returns {string|null} Template content or null if not found */ async function loadTemplate(templateName) { try { const templatePath = path.join(__dirname, '..', 'templates', `${templateName}.md`); const template = await fs.readFile(templatePath, 'utf8'); return template; } catch (error) { return null; } } /** * Replaces placeholders in a template with config values * @param {string} template - Template content * @param {Object} config - Configuration object * @returns {string} Processed template content */ function replacePlaceholders(template, config) { let result = template; // Import project type utilities const { getProjectTypeFlags } = require('./project_types'); // Flatten config for simple key-value replacements const flatConfig = { projectName: config.overview.projectName, description: config.overview.description, version: config.overview.version, projectType: config.overview.projectType.join(', '), indentation: config.codingStandards.indentation, quotes: config.codingStandards.quotes, naming: config.codingStandards.naming, linting: config.codingStandards.linting.join(', '), comments: config.codingStandards.comments, sourceDir: config.projectStructure.sourceDir, testDir: config.projectStructure.testDir, buildDir: config.projectStructure.buildDir, configDir: config.projectStructure.configDir, organization: config.projectStructure.organization, gitWorkflow: config.workflowGuidelines.gitWorkflow, branchNaming: config.workflowGuidelines.branchNaming, commitStyle: config.workflowGuidelines.commitStyle, cicd: config.workflowGuidelines.cicd.join(', '), deploymentSteps: config.workflowGuidelines.deploymentSteps, methodology: config.projectManagement.methodology.join(', '), issueTracking: config.projectManagement.issueTracking, documentation: config.projectManagement.documentation, codeReview: config.projectManagement.codeReview.join(', '), techSpecificGuidelines: generateTechSpecificGuidelines(config.technologyStack), // Add additional fields for MDC templates language: config.technologyStack.language || 'JavaScript', testingFramework: config.technologyStack.testing || 'Jest', extension: getFileExtension(config.technologyStack.language || 'JavaScript'), // Add project type flags for conditional template sections ...getProjectTypeFlags(config.overview.projectType) }; // Replace simple {{key}} placeholders for (const [key, value] of Object.entries(flatConfig)) { result = result.replace(new RegExp(`{{${key}}}`, 'g'), value); } // Handle technologyStack loop const techStackRegex = /{{#technologyStack}}(.*?){{\/technologyStack}}/s; const techStackTemplate = result.match(techStackRegex)?.[1] || ''; let techStackOutput = ''; for (const [key, value] of Object.entries(config.technologyStack)) { if (value && value.trim()) { techStackOutput += techStackTemplate .replace('{{key}}', capitalize(key)) .replace('{{value}}', value); } } result = result.replace(techStackRegex, techStackOutput); return result; } /** * Generates Cursor MDC files in .cursor/rules/ directory * @param {Object} config - Configuration object * @param {Object} inquirer - Inquirer instance for user prompts * @returns {Object} Object containing file paths and contents */ async function generateCursorMDC(config, inquirer) { const modules = ['coding-standards', 'architecture', 'testing', 'deployment', 'security']; const mdcFiles = {}; // Ask user which modules to generate const { selectedModules } = await inquirer.prompt([ { type: 'checkbox', name: 'selectedModules', message: 'Select which rule modules to generate:', choices: [ { name: 'Coding Standards', value: 'coding-standards', checked: true }, { name: 'Architecture Guidelines', value: 'architecture', checked: true }, { name: 'Testing Rules', value: 'testing', checked: true }, { name: 'Deployment & CI/CD', value: 'deployment', checked: false }, { name: 'Security Guidelines', value: 'security', checked: false } ] } ]); // Generate each selected module for (const module of selectedModules) { const template = await loadMDCTemplate(module); if (template) { mdcFiles[`${module}.mdc`] = replacePlaceholders(template, config); } } return mdcFiles; } /** * Loads an MDC template file * @param {string} templateName - Name of the MDC template to load * @returns {string|null} Template content or null if not found */ async function loadMDCTemplate(templateName) { try { const templatePath = path.join(__dirname, '..', 'templates', `cursor-${templateName}.mdc`); const template = await fs.readFile(templatePath, 'utf8'); return template; } catch (error) { return null; } } /** * Creates .cursor/rules/ directory and writes MDC files * @param {Object} mdcFiles - Object containing file names and contents * @param {string} outputDir - Directory to create the .cursor/rules folder in */ async function writeMDCFiles(mdcFiles, outputDir = '.') { const cursorDir = path.join(outputDir, '.cursor'); const rulesDir = path.join(cursorDir, 'rules'); // Create directories if they don't exist await fs.mkdir(cursorDir, { recursive: true }); await fs.mkdir(rulesDir, { recursive: true }); // Write each MDC file const filePaths = []; for (const [filename, content] of Object.entries(mdcFiles)) { const filePath = path.join(rulesDir, filename); await fs.writeFile(filePath, content, 'utf8'); filePaths.push(filePath); } return filePaths; } module.exports = { generateAgentFile, generateAgentMd, generateWindsurfRules, generateCursorMDC, writeMDCFiles, loadTemplate, loadMDCTemplate };