instruction_generator.pyā¢18 kB
"""
Instruction generator for AI coding assistants.
Generates:
- CLAUDE.md (for Claude Desktop/Code)
- .github/copilot-instructions.md (for GitHub Copilot)
- .cursor/rules/standards.mdc (for Cursor)
"""
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional
class InstructionGenerator:
"""Generate AI assistant instruction files from extracted standards."""
def __init__(self, standards: Dict[str, Any], conventions: Dict[str, Any]):
"""
Initialize the instruction generator.
Args:
standards: Extracted standards from ConfigParser
conventions: Extracted conventions from StandardsExtractor
"""
self.standards = standards
self.conventions = conventions
self.timestamp = datetime.now().strftime("%Y-%m-%d")
def generate_all(self, output_path: Path, formats: Optional[List[str]] = None) -> Dict[str, str]:
"""
Generate all instruction files.
Args:
output_path: Root path where instruction files should be generated
formats: List of formats to generate (default: all)
Options: "claude", "copilot", "cursor"
Returns:
Dictionary mapping format name to generated file path
"""
if formats is None:
formats = ["claude", "copilot", "cursor"]
generated_files = {}
if "claude" in formats:
claude_path = output_path / "CLAUDE.md"
claude_content = self.generate_claude_md()
claude_path.write_text(claude_content)
generated_files["claude"] = str(claude_path)
if "copilot" in formats:
copilot_dir = output_path / ".github"
copilot_dir.mkdir(exist_ok=True)
copilot_path = copilot_dir / "copilot-instructions.md"
copilot_content = self.generate_copilot_instructions()
copilot_path.write_text(copilot_content)
generated_files["copilot"] = str(copilot_path)
if "cursor" in formats:
cursor_dir = output_path / ".cursor" / "rules"
cursor_dir.mkdir(parents=True, exist_ok=True)
cursor_path = cursor_dir / "standards.mdc"
cursor_content = self.generate_cursor_rules()
cursor_path.write_text(cursor_content)
generated_files["cursor"] = str(cursor_path)
return generated_files
def generate_claude_md(self) -> str:
"""Generate CLAUDE.md content."""
sections = []
# Header with AirMCP branding
sections.append("# Project Coding Standards")
sections.append("")
sections.append("> š¤ **Auto-generated by [AirMCP](https://airmcp.com)** - AI Coding Standards Made Easy")
sections.append("> ")
sections.append("> Stop manually writing AI instruction files. Extract your standards from config files, use everywhere.")
sections.append("")
sections.append(f"Auto-generated from project config files on {self.timestamp}.")
sections.append("")
sections.append("---")
sections.append("")
# Project Info
if self.conventions.get("project_type"):
sections.append("## Project Information")
sections.append("")
sections.append(f"**Type**: {self.conventions['project_type']} project")
if self.conventions.get("languages"):
langs = ", ".join(self.conventions["languages"])
sections.append(f"**Languages**: {langs}")
if self.conventions.get("package_manager"):
sections.append(
f"**Package Manager**: {self.conventions['package_manager']}"
)
if self.conventions.get("test_framework"):
sections.append(f"**Test Framework**: {self.conventions['test_framework']}")
if self.conventions.get("test_command"):
sections.append(f"**Test Command**: `{self.conventions['test_command']}`")
sections.append("")
sections.append("---")
sections.append("")
# Enhancement tip: Development commands
sections.append("<!-- ")
sections.append("š” ENHANCEMENT TIP: Add your development commands here")
sections.append("")
sections.append("## Key Commands")
sections.append("- Setup: `npm install` or `uv sync`")
sections.append("- Test: `npm test` or `pytest`")
sections.append("- Build: `npm run build`")
sections.append("- Lint: `npm run lint` or `ruff check`")
sections.append("-->")
sections.append("")
# Formatting Rules
if self.standards.get("formatting"):
sections.append("## Code Formatting")
sections.append("")
sections.append("**IMPORTANT**: Always follow these formatting rules:")
sections.append("")
formatting = self.standards["formatting"]
# Indentation
if "indent_style" in formatting:
indent_style = formatting["indent_style"]["value"]
source = formatting["indent_style"]["source"]
if indent_style == "space" and "indent_size" in formatting:
indent_size = formatting["indent_size"]["value"]
sections.append(f"- **Indentation**: {indent_size} spaces (from {source})")
elif indent_style == "tab":
sections.append(f"- **Indentation**: Tabs (from {source})")
# Quote style
if "quote_style" in formatting:
quote_style = formatting["quote_style"]["value"]
source = formatting["quote_style"]["source"]
sections.append(f"- **Quotes**: Use {quote_style} quotes (from {source})")
# Line length
if "max_line_length" in formatting:
max_len = formatting["max_line_length"]["value"]
source = formatting["max_line_length"]["source"]
sections.append(f"- **Max line length**: {max_len} characters (from {source})")
# Semicolons
if "semicolons" in formatting:
use_semi = formatting["semicolons"]["value"]
source = formatting["semicolons"]["source"]
if use_semi:
sections.append(f"- **Semicolons**: Always use semicolons (from {source})")
else:
sections.append(f"- **Semicolons**: Never use semicolons (from {source})")
# Line ending
if "line_ending" in formatting:
line_end = formatting["line_ending"]["value"]
source = formatting["line_ending"]["source"]
sections.append(f"- **Line endings**: {line_end} (from {source})")
# Trailing commas
if "trailing_commas" in formatting:
trailing = formatting["trailing_commas"]["value"]
source = formatting["trailing_commas"]["source"]
sections.append(f"- **Trailing commas**: {trailing} (from {source})")
sections.append("")
sections.append("---")
sections.append("")
# Project Patterns
if self.conventions.get("patterns"):
sections.append("## Project Structure")
sections.append("")
for pattern in self.conventions["patterns"]:
sections.append(f"- {pattern}")
sections.append("")
sections.append("---")
sections.append("")
# Conventions from README
if self.conventions.get("conventions"):
sections.append("## Project Conventions")
sections.append("")
sections.append("**Follow these project-specific conventions:**")
sections.append("")
for conv in self.conventions["conventions"]:
if isinstance(conv, dict):
sections.append(f"- {conv['text']}")
else:
sections.append(f"- {conv}")
sections.append("")
sections.append("---")
sections.append("")
# Testing
if self.conventions.get("test_command"):
sections.append("## Testing Requirements")
sections.append("")
sections.append("**Always run tests before committing:**")
sections.append("")
sections.append(f"```bash")
sections.append(f"{self.conventions['test_command']}")
sections.append(f"```")
sections.append("")
sections.append("---")
sections.append("")
# Enhancement tip: Git workflow
sections.append("<!-- ")
sections.append("š” ENHANCEMENT TIP: Document your git workflow")
sections.append("")
sections.append("## Development Workflow")
sections.append("1. Create feature branch: `git checkout -b feature/name`")
sections.append("2. Make changes and run tests")
sections.append("3. Commit with conventional commits: `feat: add feature`")
sections.append("4. Create PR with description")
sections.append("5. Wait for CI/CD checks")
sections.append("6. Merge after approval")
sections.append("-->")
sections.append("")
sections.append("<!-- ")
sections.append("š” ENHANCEMENT TIP: Add project-specific architecture notes")
sections.append("")
sections.append("## Architecture")
sections.append("- Backend: FastAPI + PostgreSQL")
sections.append("- Frontend: React + TypeScript")
sections.append("- Cache: Redis")
sections.append("- Queue: Celery")
sections.append("- Key decisions: See docs/architecture/decisions/")
sections.append("-->")
sections.append("")
# Footer with AirMCP branding
sections.append("---")
sections.append("")
sections.append("## Auto-Generated")
sections.append("")
sections.append(
f"This file was automatically generated on {self.timestamp} "
"by [AirMCP](https://airmcp.com)."
)
sections.append("")
sections.append("**Source files analyzed:**")
sources = self._get_analyzed_sources()
for source in sources:
sections.append(f"- {source}")
sections.append("")
sections.append("**Regenerate anytime:**")
sections.append("```bash")
sections.append("generate_ai_standards(project_path=\".\")")
sections.append("```")
sections.append("")
sections.append("**Need help?** Visit [airmcp.com](https://airmcp.com) for docs and examples.")
sections.append("")
sections.append("---")
sections.append("")
sections.append("*Powered by [AirMCP](https://airmcp.com) - Making AI coding assistants smarter*")
return "\n".join(sections)
def generate_copilot_instructions(self) -> str:
"""Generate GitHub Copilot instructions."""
sections = []
sections.append("# GitHub Copilot Instructions")
sections.append("")
sections.append("> š¤ Auto-generated by [AirMCP](https://airmcp.com)")
sections.append("")
sections.append(
f"Auto-generated coding standards for this project ({self.timestamp})."
)
sections.append("")
# Project Context
if self.conventions.get("project_type"):
sections.append(f"This is a {self.conventions['project_type']} project")
if self.conventions.get("package_manager"):
sections.append(
f"using {self.conventions['package_manager']} for package management."
)
sections.append("")
# Formatting Rules
if self.standards.get("formatting"):
sections.append("## Code Style")
sections.append("")
formatting = self.standards["formatting"]
rules = []
if "indent_style" in formatting and "indent_size" in formatting:
indent_style = formatting["indent_style"]["value"]
if indent_style == "space":
indent_size = formatting["indent_size"]["value"]
rules.append(f"Use {indent_size} spaces for indentation")
else:
rules.append("Use tabs for indentation")
if "quote_style" in formatting:
quote_style = formatting["quote_style"]["value"]
rules.append(f"Use {quote_style} quotes for strings")
if "max_line_length" in formatting:
max_len = formatting["max_line_length"]["value"]
rules.append(f"Keep lines under {max_len} characters")
if "semicolons" in formatting:
use_semi = formatting["semicolons"]["value"]
if use_semi:
rules.append("Always include semicolons")
else:
rules.append("Omit semicolons")
for rule in rules:
sections.append(f"- {rule}")
sections.append("")
# Testing
if self.conventions.get("test_command"):
sections.append("## Testing")
sections.append("")
sections.append("Before suggesting code commits, verify tests pass:")
sections.append(f"```bash")
sections.append(f"{self.conventions['test_command']}")
sections.append(f"```")
sections.append("")
# Conventions
if self.conventions.get("conventions"):
sections.append("## Project Conventions")
sections.append("")
for conv in self.conventions["conventions"][:10]: # Limit to top 10
if isinstance(conv, dict):
sections.append(f"- {conv['text']}")
else:
sections.append(f"- {conv}")
sections.append("")
# Footer
sections.append("---")
sections.append("")
sections.append("*Generated by [AirMCP](https://airmcp.com) - Auto-generate AI instruction files from your existing config files*")
return "\n".join(sections)
def generate_cursor_rules(self) -> str:
"""Generate Cursor rules in MDC format."""
sections = []
sections.append("---")
sections.append("description: Auto-generated coding standards by AirMCP")
sections.append(f"generated: {self.timestamp}")
sections.append("generator: AirMCP (airmcp.com)")
sections.append("---")
sections.append("")
sections.append("# Project Coding Standards")
sections.append("")
sections.append("> š¤ Auto-generated by [AirMCP](https://airmcp.com)")
sections.append("")
# Formatting
if self.standards.get("formatting"):
sections.append("## Formatting Rules")
sections.append("")
formatting = self.standards["formatting"]
if "indent_style" in formatting and "indent_size" in formatting:
indent_style = formatting["indent_style"]["value"]
if indent_style == "space":
indent_size = formatting["indent_size"]["value"]
sections.append(f"- Indentation: {indent_size} spaces")
else:
sections.append("- Indentation: tabs")
if "quote_style" in formatting:
quote_style = formatting["quote_style"]["value"]
sections.append(f"- String quotes: {quote_style}")
if "max_line_length" in formatting:
max_len = formatting["max_line_length"]["value"]
sections.append(f"- Maximum line length: {max_len}")
sections.append("")
# Project Info
if self.conventions.get("package_manager"):
sections.append("## Package Management")
sections.append("")
sections.append(
f"Always use `{self.conventions['package_manager']}` for package operations."
)
sections.append("")
# Testing
if self.conventions.get("test_command"):
sections.append("## Testing")
sections.append("")
sections.append(f"Run tests with: `{self.conventions['test_command']}`")
sections.append("")
# Footer
sections.append("---")
sections.append("")
sections.append("*Powered by [AirMCP](https://airmcp.com)*")
return "\n".join(sections)
def _get_analyzed_sources(self) -> List[str]:
"""Get list of config files that were analyzed."""
sources = []
# Check formatting sources
formatting = self.standards.get("formatting", {})
for key, data in formatting.items():
source = data.get("source")
if source:
if source == "editorconfig" and ".editorconfig" not in sources:
sources.append(".editorconfig")
elif source == "prettier" and ".prettierrc" not in sources:
sources.append(".prettierrc")
elif source == "eslint" and ".eslintrc" not in sources:
sources.append(".eslintrc")
# Check for project files
if self.conventions.get("project_type") == "Python":
sources.append("pyproject.toml")
elif self.conventions.get("project_type") in ["JavaScript", "TypeScript"]:
sources.append("package.json")
elif self.conventions.get("project_type") == "Rust":
sources.append("Cargo.toml")
if self.conventions.get("conventions"):
sources.append("README.md")
return sources or ["(no config files found)"]