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
JavaScript
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
};