Coding Standards · git:20260207.88c1f6e · 2026-02-07 · sha256 007240e821a5fbd7
Coding Standards git:20260207.88c1f6eA
Immutable. This exact content never changes and is served at /api/v1/blob/007240e821a5fbd7.
---
name: Coding Standards
description: Enforces coding standards including naming conventions, file organization, and code style guidelines.
---
# NXTG-Forge Coding Standards
## Overview
This document defines the coding standards for the NXTG-Forge project. These standards ensure consistency, maintainability, and quality across the codebase.
## Table of Contents
1. [Python Style Guide](#python-style-guide)
2. [Code Formatting](#code-formatting)
3. [Type Hinting](#type-hinting)
4. [Naming Conventions](#naming-conventions)
5. [Error Handling](#error-handling)
6. [Testing Requirements](#testing-requirements)
7. [Documentation Standards](#documentation-standards)
8. [Code Complexity](#code-complexity)
9. [Import Organization](#import-organization)
10. [Security Considerations](#security-considerations)
---
## Python Style Guide
NXTG-Forge follows **PEP 8** as the baseline Python style guide, with specific extensions and clarifications documented below.
### Core Principles
1. **Readability counts**: Code is read far more often than it is written
2. **Explicit is better than implicit**: Clear intent over clever tricks
3. **Simple is better than complex**: Favor straightforward solutions
4. **Consistency matters**: Follow existing patterns in the codebase
### Key PEP 8 Requirements
- Maximum line length: **100 characters** (enforced by Black)
- Indentation: **4 spaces** (no tabs)
- Blank lines: 2 before top-level definitions, 1 before method definitions
- String quotes: Prefer **double quotes** `"` for consistency
- Trailing commas: Use for multi-line constructs
---
## Code Formatting
### Automated Formatting
All Python code **must** be formatted with Black:
```bash
black --line-length 100 forge/ tests/
```
### Black Configuration
```toml
# pyproject.toml
[tool.black]
line-length = 100
target-version = ['py311']
include = '\.pyi?$'
```
### Manual Formatting Guidelines
When Black doesn't enforce specific patterns:
**1. Vertical Alignment**
```python
# Good
result = some_function(
argument_one="value",
argument_two="another_value",
argument_three="final_value",
)
# Avoid
result = some_function(argument_one="value",
argument_two="another_value")
```
**2. Line Breaks**
```python
# Good - break before binary operators
total = (
first_value
+ second_value
+ third_value
)
# Avoid - break after
total = (first_value +
second_value +
third_value)
```
**3. Comprehensions**
```python
# Good - simple, single line
squares = [x**2 for x in range(10)]
# Good - complex, multiline
filtered_data = [
transform(item)
for item in collection
if item.is_valid() and item.priority > 5
]
# Avoid - complex single line
result = [transform(x) for x in items if x.valid() and x.priority > 5 and x.status == "active"]
```
---
## Type Hinting
### Requirements
Type hints are **mandatory** for:
- All public functions and methods
- All class attributes
- All function parameters
- All return values
Type hints are **optional** for:
- Private methods (but encouraged)
- Local variables (unless improving clarity)
- Simple lambdas
### Type Hint Style
```python
from typing import Any, Protocol
from pathlib import Path
from collections.abc import Callable, Iterable, Mapping
# Good - Complete type hints
def generate_files(
template_path: Path,
config: dict[str, Any],
output_dir: Path,
*,
dry_run: bool = False,
) -> list[Path]:
"""Generate files from template."""
...
# Good - Dataclass with types
@dataclass(frozen=True)
class Template:
"""Template definition."""
name: str
version: str
files: list[TemplateFile]
variables: dict[str, VariableDefinition]
metadata: dict[str, Any]
# Good - Protocol for structural typing
class TemplateRepository(Protocol):
"""Repository protocol for templates."""
def find_by_name(self, name: str) -> Template | None:
"""Find template by name."""
...
def save(self, template: Template) -> None:
"""Save template."""
...
# Avoid - Missing return type
def process_data(items: list[dict]):
...
# Avoid - Using 'Any' unnecessarily
def transform(data: Any) -> Any:
...
```
### Modern Type Hints (Python 3.11+)
Use modern syntax:
- `list[str]` instead of `List[str]`
- `dict[str, int]` instead of `Dict[str, int]`
- `tuple[int, ...]` instead of `Tuple[int, ...]`
- `X | Y` instead of `Union[X, Y]`
- `X | None` instead of `Optional[X]`
### MyPy Configuration
```toml
# pyproject.toml
[tool.mypy]
python_version = "3.11"
strict = true
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
```
---
## Naming Conventions
### General Rules
| Element | Convention | Example |
|---------|-----------|---------|
| Modules | `snake_case` | `file_generator.py` |
| Packages | `snake_case` | `forge/domain/` |
| Classes | `PascalCase` | `FileGenerator` |
| Functions | `snake_case` | `generate_files()` |
| Methods | `snake_case` | `save_template()` |
| Variables | `snake_case` | `template_path` |
| Constants | `SCREAMING_SNAKE_CASE` | `DEFAULT_TIMEOUT` |
| Private | `_leading_underscore` | `_internal_method()` |
| Type Variables | `PascalCase` with `T` prefix | `TEntity` |
### Detailed Guidelines
**1. Modules and Packages**
```python
# Good
forge/domain/template.py
forge/infrastructure/file_system_repository.py
# Avoid
forge/domain/Template.py
forge/infrastructure/FileSystemRepo.py
```
**2. Classes**
```python
# Good
class TemplateRepository:
"""Repository for template entities."""
...
class FileSystemTemplateRepository(TemplateRepository):
"""File system implementation."""
...
# Avoid
class template_repository: # Not PascalCase
class TemplateRepo: # Unclear abbreviation
```
**3. Functions and Methods**
```python
# Good
def generate_project_from_template(template: Template, config: ProjectConfig) -> Project:
"""Generate project from template."""
...
def is_valid_template(template: Template) -> bool:
"""Check if template is valid."""
...
# Avoid
def genProj(t, c): # Unclear abbreviations
def GenerateProject(): # Not snake_case
```
**4. Variables**
```python
# Good
template_path = Path("/templates")
user_input = get_user_input()
max_retries = 3
# Avoid
tp = Path("/templates") # Unclear
templatePath = get_input() # Not snake_case
MAX_RETRIES = 3 # Should be constant
```
**5. Constants**
```python
# Good
DEFAULT_TEMPLATE_DIR = Path(".claude/templates")
MAX_FILE_SIZE_MB = 10
API_VERSION = "1.0.0"
# Avoid
default_dir = Path(".claude/templates") # Not constant style
maxFileSize = 10 # Not constant style
```
**6. Private vs Public**
```python
class TemplateProcessor:
"""Process templates."""
def __init__(self, config: ProcessorConfig) -> None:
"""Initialize processor."""
self.config = config # Public attribute
self._cache: dict[str, Template] = {} # Private attribute
def process(self, template: Template) -> ProcessedTemplate:
"""Public method."""
return self._apply_transformations(template)
def _apply_transformations(self, template: Template) -> ProcessedTemplate:
"""Private method - internal implementation detail."""
...
```
---
## Error Handling
### Custom Exceptions
Define domain-specific exceptions in the domain layer:
```python
# forge/domain/exceptions.py
class ForgeError(Exception):
"""Base exception for all Forge errors."""
pass
class TemplateNotFoundError(ForgeError):
"""Template could not be found."""
def __init__(self, template_name: str) -> None:
"""Initialize error."""
self.template_name = template_name
super().__init__(f"Template not found: {template_name}")
class TemplateValidationError(ForgeError):
"""Template failed validation."""
def __init__(self, template_name: str, errors: list[str]) -> None:
"""Initialize error."""
self.template_name = template_name
self.errors = errors
super().__init__(
f"Template '{template_name}' validation failed:\n" + "\n".join(f" - {e}" for e in errors)
)
class ConfigurationError(ForgeError):
"""Invalid configuration."""
pass
```
### Error Handling Patterns
**1. Specific Exception Types**
```python
# Good - Specific exceptions
def load_template(name: str) -> Template:
"""Load template by name."""
try:
path = self.template_dir / f"{name}.yaml"
return Template.from_file(path)
except FileNotFoundError as e:
raise TemplateNotFoundError(name) from e
except yaml.YAMLError as e:
raise TemplateValidationError(name, [str(e)]) from e
# Avoid - Generic exceptions
def load_template(name: str) -> Template:
try:
...
except Exception as e: # Too broad
raise Exception(f"Failed: {e}")
```
**2. Context Preservation**
```python
# Good - Preserve context with 'from'
try:
result = external_api_call()
except ConnectionError as e:
raise AgentConnectionError("Failed to connect to agent") from e
# Avoid - Losing context
try:
result = external_api_call()
except ConnectionError:
raise AgentConnectionError("Failed") # Original error lost
```
**3. Resource Cleanup**
```python
# Good - Context manager for cleanup
with open(file_path) as f:
content = f.read()
# Good - Explicit try/finally when needed
lock = acquire_lock()
try:
perform_operation()
finally:
lock.release()
# Avoid - No cleanup
f = open(file_path)
content = f.read()
# File not closed if exception occurs
```
**4. Error Recovery**
```python
# Good - Graceful degradation
def get_cached_template(name: str) -> Template | None:
"""Get template from cache, return None if not found."""
try:
return self._cache[name]
except KeyError:
return None
# Good - Retry logic
def save_with_retry(data: str, max_retries: int = 3) -> None:
"""Save data with retry logic."""
for attempt in range(max_retries):
try:
self._write_file(data)
return
except IOError as e:
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt) # Exponential backoff
```
---
## Testing Requirements
### Coverage Requirements
- **Minimum coverage**: 80% (enforced by CI)
- **Target coverage**: 86%
- **Critical paths**: 100% coverage required
### Test Organization
```
tests/
├── unit/ # 70% of tests
│ ├── domain/
│ ├── application/
│ └── infrastructure/
├── integration/ # 20% of tests
│ └── workflows/
└── e2e/ # 10% of tests
└── scenarios/
```
### Test Naming
```python
# Pattern: test_<method>_<scenario>_<expected>
def test_generate_files_with_valid_template_creates_files():
"""Test file generation with valid template."""
...
def test_load_template_with_missing_file_raises_error():
"""Test loading missing template raises TemplateNotFoundError."""
...
def test_validate_template_with_invalid_vars_returns_errors():
"""Test validation returns errors for invalid variables."""
...
```
### Test Structure (AAA Pattern)
```python
def test_orchestrate_task_delegates_to_correct_agent():
"""Test orchestrator delegates task to correct agent."""
# Arrange
orchestrator = Orchestrator(agent_pool)
task = Task(
description="Implement FastAPI endpoint",
requirements=["api-design", "python"],
)
# Act
result = orchestrator.orchestrate(task)
# Assert
assert result.assigned_agent == "backend-master"
assert result.status == "delegated"
```
### Fixture Guidelines
```python
# conftest.py - Shared fixtures
@pytest.fixture
def temp_project_dir(tmp_path: Path) -> Path:
"""Create temporary project directory with .claude structure."""
claude_dir = tmp_path / ".claude"
claude_dir.mkdir()
(claude_dir / "templates").mkdir()
(claude_dir / "skills").mkdir()
return tmp_path
@pytest.fixture
def sample_template() -> Template:
"""Create sample template for testing."""
return Template(
name="fastapi-basic",
version="1.0.0",
description="Basic FastAPI template",
files=[...],
variables={...},
)
```
### Mocking Strategy
```python
# Good - Mock external dependencies
def test_generate_project_calls_file_system(mocker):
"""Test project generation interacts with file system."""
mock_write = mocker.patch("pathlib.Path.write_text")
generator = FileGenerator(project_dir)
generator.generate(template, config)
assert mock_write.call_count == 5
# Avoid - Mocking internal logic
def test_process_template_internal_method(mocker):
"""DON'T mock internal methods - test behavior, not implementation."""
mocker.patch.object(processor, "_internal_method") # Avoid this
```
### Pytest Configuration
```toml
# pyproject.toml
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["*_test.py", "test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"-v",
"--cov=forge",
"--cov-report=term-missing",
"--cov-report=html",
"--cov-fail-under=80",
]
```
---
## Documentation Standards
### Docstring Style
Use **Google-style docstrings** for all public modules, classes, and functions.
**Module Docstring**
```python
"""File generation module.
This module provides functionality for generating project files from
templates using Jinja2 templating engine.
Example:
generator = FileGenerator(project_root)
files = generator.generate_from_spec(spec)
"""
```
**Class Docstring**
```python
class FileGenerator:
"""Generate project files from templates.
The FileGenerator processes Jinja2 templates and generates complete
project structures based on specifications and configurations.
Attributes:
project_root: Root directory for file generation
templates_dir: Directory containing Jinja2 templates
generated_files: List of successfully generated files
Example:
>>> generator = FileGenerator(Path("/project"))
>>> template = Template.load("fastapi-basic")
>>> files = generator.generate(template, config)
>>> print(f"Generated {len(files)} files")
"""
```
**Function/Method Docstring**
```python
def generate_from_spec(
self,
spec: str,
output_dir: Path | None = None,
*,
dry_run: bool = False,
) -> list[Path]:
"""Generate project files from specification.
Parses the project specification, extracts configuration, and
generates all required project files using templates.
Args:
spec: Project specification in markdown format
output_dir: Target directory for generated files. Defaults to project_root
dry_run: If True, validate but don't write files
Returns:
List of paths to generated files
Raises:
TemplateNotFoundError: If required template is missing
TemplateValidationError: If template validation fails
ConfigurationError: If spec contains invalid configuration
Example:
>>> spec = "# MyApp\\n**Type:** web-app\\n**Framework:** FastAPI"
>>> files = generator.generate_from_spec(spec)
>>> assert Path("README.md") in files
"""
```
### Comment Guidelines
**1. When to Comment**
```python
# Good - Explain WHY, not WHAT
# Use binary search because dataset can exceed 10M items
result = binary_search(sorted_data, target)
# Workaround for Jinja2 bug #1234 - remove when fixed in v3.2
template.globals["workaround"] = True
# Avoid - Obvious comments
# Increment counter
counter += 1
# Create empty list
items = []
```
**2. TODO Comments**
```python
# TODO(username): Brief description of what needs to be done
# TODO(alice): Add caching layer to reduce file I/O
# TODO(bob): Replace with async implementation in v0.4.0
# FIXME(charlie): Race condition when multiple agents write simultaneously
```
**3. Section Comments**
```python
class ComplexProcessor:
"""Process complex data."""
# ===================================================================
# Public API
# ===================================================================
def process(self, data: Data) -> Result:
"""Process data."""
...
# ===================================================================
# Internal Implementation
# ===================================================================
def _validate(self, data: Data) -> bool:
"""Validate data."""
...
```
---
## Code Complexity
### Cyclomatic Complexity
- **Maximum per function**: 10
- **Target**: ≤ 5
- **Enforced by**: Ruff (McCabe complexity checker)
```python
# Good - Low complexity (3)
def calculate_discount(price: float, customer_tier: str) -> float:
"""Calculate discount based on customer tier."""
if customer_tier == "gold":
return price * 0.8
elif customer_tier == "silver":
return price * 0.9
else:
return price
# Refactor - High complexity (reduce with strategy pattern)
# Before (complexity 12)
def process_payment(payment_type, amount, ...):
if payment_type == "credit_card":
if amount > 1000:
if user.verified:
... # Many nested conditions
elif payment_type == "paypal":
...
# After (complexity 3 each)
class PaymentProcessor(Protocol):
def process(self, amount: float, user: User) -> PaymentResult:
...
class CreditCardProcessor(PaymentProcessor):
def process(self, amount: float, user: User) -> PaymentResult:
if not user.verified and amount > 1000:
raise UnverifiedUserError()
return self._process_credit_card(amount)
```
### Function Length
- **Maximum lines**: 25
- **Target**: ≤ 15
- **If longer**: Decompose into helper functions
```python
# Good - Focused function (12 lines)
def generate_project_structure(config: ProjectConfig, output_dir: Path) -> list[Path]:
"""Generate project directory structure."""
created_dirs = []
for dir_spec in config.directories:
dir_path = output_dir / dir_spec.path
dir_path.mkdir(parents=True, exist_ok=True)
created_dirs.append(dir_path)
if dir_spec.add_init:
(dir_path / "__init__.py").touch()
return created_dirs
```
### Nesting Depth
- **Maximum nesting**: 3 levels
- **Prefer**: Early returns, guard clauses
```python
# Good - Guard clauses (nesting: 1)
def validate_template(template: Template) -> ValidationResult:
"""Validate template."""
if not template.files:
return ValidationResult.error("No files defined")
if not template.variables:
return ValidationResult.error("No variables defined")
if not template.name:
return ValidationResult.error("Name required")
return ValidationResult.success()
# Avoid - Deep nesting (nesting: 3)
def validate_template(template: Template) -> ValidationResult:
if template.files:
if template.variables:
if template.name:
return ValidationResult.success()
else:
return ValidationResult.error("Name required")
else:
return ValidationResult.error("No variables")
else:
return ValidationResult.error("No files")
```
### Ruff Configuration
```toml
# pyproject.toml
[tool.ruff]
line-length = 100
target-version = "py311"
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"C90", # mccabe complexity
"N", # pep8-naming
"UP", # pyupgrade
"B", # flake8-bugbear
]
[tool.ruff.lint.mccabe]
max-complexity = 10
```
---
## Import Organization
### Import Order
1. Standard library imports
2. Third-party imports
3. Local application imports
**Blank lines**: One blank line between each group
```python
# Standard library
import json
import sys
from pathlib import Path
from typing import Any
# Third-party
import click
import yaml
from jinja2 import Environment, FileSystemLoader
# Local
from forge.domain.template import Template
from forge.application.use_cases import GenerateProjectUseCase
from forge.infrastructure.file_system_repository import FileSystemTemplateRepository
```
### Import Style
```python
# Good - Explicit imports
from forge.domain.template import Template, TemplateFile, VariableDefinition
from forge.domain.exceptions import TemplateNotFoundError
# Good - Aliasing for clarity
from forge.infrastructure.repository import FileSystemTemplateRepository as FSTemplateRepo
# Avoid - Wildcard imports
from forge.domain import *
# Avoid - Importing entire modules when specific names suffice
import forge.domain.template
template = forge.domain.template.Template() # Too verbose
```
### Conditional Imports
```python
# Good - Type checking imports
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from forge.domain.template import Template # Avoid circular imports
# Good - Optional dependencies
try:
import redis
except ImportError:
redis = None
def get_cache():
"""Get cache backend."""
if redis is None:
return InMemoryCache()
return RedisCache()
```
---
## Security Considerations
### Input Validation
```python
# Good - Validate and sanitize
def load_template(name: str) -> Template:
"""Load template by name."""
# Validate template name (prevent path traversal)
if not name.replace("-", "").replace("_", "").isalnum():
raise ValueError(f"Invalid template name: {name}")
template_path = self.templates_dir / f"{name}.yaml"
# Ensure path is within templates directory
if not template_path.resolve().is_relative_to(self.templates_dir.resolve()):
raise SecurityError(f"Template path outside templates directory: {name}")
return Template.from_file(template_path)
```
### Secrets Management
```python
# Good - Never hardcode secrets
API_KEY = os.environ.get("FORGE_API_KEY")
if not API_KEY:
raise ConfigurationError("FORGE_API_KEY environment variable required")
# Good - Use dedicated secrets management
from forge.infrastructure.secrets import get_secret
database_password = get_secret("database_password")
# Avoid - Hardcoded secrets
API_KEY = "sk-1234567890abcdef" # NEVER DO THIS
```
### File Operations
```python
# Good - Safe file operations
def write_generated_file(path: Path, content: str) -> None:
"""Write generated file with safety checks."""
# Ensure parent directory exists
path.parent.mkdir(parents=True, exist_ok=True)
# Use atomic write
temp_path = path.with_suffix(f"{path.suffix}.tmp")
try:
temp_path.write_text(content, encoding="utf-8")
temp_path.replace(path)
except Exception:
temp_path.unlink(missing_ok=True)
raise
# Avoid - Unsafe operations
def write_file(path: str, content: str) -> None:
"""Unsafe file write."""
open(path, "w").write(content) # No error handling, encoding issues
```
### Command Execution
```python
# Good - Safe subprocess execution
import subprocess
import shlex
def run_safe_command(command: str, cwd: Path) -> str:
"""Run command safely."""
# Validate working directory
if not cwd.is_dir():
raise ValueError(f"Invalid working directory: {cwd}")
# Use list form, not shell=True
result = subprocess.run(
shlex.split(command),
cwd=cwd,
capture_output=True,
text=True,
timeout=30,
check=True,
)
return result.stdout
# Avoid - Shell injection vulnerability
def run_command(command: str) -> str:
return subprocess.check_output(command, shell=True) # DANGEROUS
```
---
## Tools and Automation
### Pre-commit Hooks
```yaml
# .pre-commit-config.yaml
repos:
- repo: https://github.com/psf/black
rev: 23.12.1
hooks:
- id: black
args: [--line-length=100]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.9
hooks:
- id: ruff
args: [--fix]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
hooks:
- id: mypy
additional_dependencies: [types-all]
```
### Development Commands
```bash
# Format code
black --line-length 100 forge/ tests/
# Lint code
ruff check forge/ tests/
# Type check
mypy forge/
# Run tests with coverage
pytest -v --cov=forge --cov-report=term-missing --cov-fail-under=80
# Full quality check (runs all tools)
make quality
```
### Makefile
```makefile
.PHONY: format lint type-check test quality
format:
black --line-length 100 forge/ tests/
lint:
ruff check forge/ tests/
type-check:
mypy forge/
test:
pytest -v --cov=forge --cov-report=term-missing --cov-fail-under=80
quality: format lint type-check test
@echo "✓ All quality checks passed"
```
---
## Code Review Checklist
Before submitting code for review, ensure:
- [ ] Code follows PEP 8 and NXTG-Forge extensions
- [ ] All functions have type hints
- [ ] All public APIs have Google-style docstrings
- [ ] Tests added/updated (coverage ≥ 80%)
- [ ] No functions exceed complexity 10
- [ ] No functions exceed 25 lines
- [ ] Imports properly organized (stdlib, third-party, local)
- [ ] No hardcoded secrets or credentials
- [ ] Error handling includes specific exception types
- [ ] Black formatting applied
- [ ] Ruff linting passes
- [ ] MyPy type checking passes
- [ ] All tests pass locally
---
## References
- [PEP 8 -- Style Guide for Python Code](https://peps.python.org/pep-0008/)
- [Black Code Style](https://black.readthedocs.io/)
- [Ruff Linter](https://docs.astral.sh/ruff/)
- [MyPy Type Checking](https://mypy.readthedocs.io/)
- [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html)
- [Clean Architecture in Python](https://www.amazon.com/Clean-Architecture-Craftsmans-Software-Structure/dp/0134494164)
---
**Last Updated**: 2026-01-06
**Version**: 1.0.0
**Maintainer**: NXTG-Forge Team