Code Style Guide¶
Code Scalpel follows strict code style guidelines to ensure consistency and readability.
Formatting Tools¶
| Tool | Purpose | Config |
|---|---|---|
| Black | Code formatting | pyproject.toml |
| Ruff | Linting | pyproject.toml |
| Pyright | Type checking | pyrightconfig.json |
Black Configuration¶
# pyproject.toml
[tool.black]
line-length = 88
target-version = ['py310', 'py311', 'py312', 'py313']
Ruff Configuration¶
# pyproject.toml
[tool.ruff]
line-length = 88
target-version = "py310"
[tool.ruff.lint]
select = ["E", "F", "I", "N", "W", "UP"]
ignore = ["E501"] # Line length handled by Black
Import Organization¶
Imports should be grouped and sorted:
# 1. Standard library
import asyncio
import os
from typing import TYPE_CHECKING
# 2. Third-party
from pydantic import BaseModel
from mcp import Server
# 3. Local imports
from code_scalpel.mcp.tools import context
Type Hints¶
Type hints are required for all public functions:
# ✅ Good
def analyze_code(code: str, language: str = "auto") -> AnalysisResult:
"""Analyze source code."""
...
# ❌ Bad - missing type hints
def analyze_code(code, language="auto"):
...
Common Type Patterns¶
from typing import Optional, List, Dict, Union, Any
# Optional parameters
def process(data: Optional[str] = None) -> bool:
...
# List/Dict types
def handle(items: List[str], config: Dict[str, Any]) -> None:
...
# Union types
def parse(value: Union[str, int]) -> str:
...
Docstrings¶
All public functions and classes require docstrings:
def extract_code(
file_path: str,
target_name: str,
target_type: str
) -> ExtractionResult:
"""
Extract a code symbol from a file.
Args:
file_path: Path to the source file
target_name: Name of the symbol to extract
target_type: Type of symbol (function, class, method)
Returns:
ExtractionResult containing the extracted code and metadata
Raises:
SymbolNotFoundError: If the symbol doesn't exist
ParseError: If the file cannot be parsed
Example:
>>> result = extract_code("utils.py", "calculate_tax", "function")
>>> print(result.code)
"""
...
Naming Conventions¶
| Type | Convention | Example |
|---|---|---|
| Classes | PascalCase | ContextAnalyzer |
| Functions | snake_case | extract_ast_nodes |
| Variables | snake_case | file_path |
| Constants | UPPER_CASE | DEFAULT_TIMEOUT |
| Private | Leading underscore | _internal_cache |
Error Handling¶
No Bare Except¶
# ✅ Good
try:
process_data()
except ValueError as e:
logger.error(f"Invalid data: {e}")
except IOError as e:
logger.error(f"IO error: {e}")
# ❌ Bad - bare except
try:
process_data()
except:
pass
Descriptive Messages¶
# ✅ Good
raise ValueError(f"Invalid age: {age}. Must be between 0 and 150.")
# ❌ Bad
raise ValueError("invalid")
Async Patterns¶
Use asyncio.to_thread for Blocking I/O¶
# ✅ Good - non-blocking
async def read_file(path: str) -> str:
return await asyncio.to_thread(Path(path).read_text)
# ❌ Bad - blocks event loop
async def read_file(path: str) -> str:
return Path(path).read_text()
Proper Async Function Declaration¶
# ✅ Good
async def analyze_async(code: str) -> Result:
...
# ❌ Bad - don't use sync function for async work
def analyze(code: str) -> Result:
return asyncio.run(analyze_async(code))
Change Tagging¶
All changes must include a tag:
# [20260201_FEATURE] New fuzzy matching for symbol names
def fuzzy_match(query: str, candidates: List[str]) -> List[str]:
...
# [20260201_BUGFIX] Fixed off-by-one error in line numbers
line_number = node.lineno # Was incorrectly using node.lineno - 1
Tag Types¶
| Type | Description |
|---|---|
FEATURE | New feature |
BUGFIX | Bug fix |
SECURITY | Security fix |
REFACTOR | Code restructuring |
PERF | Performance improvement |
TEST | Test changes |
DOCS | Documentation |
DEPRECATE | Deprecation |
Pre-Commit Checks¶
Before committing, ensure:
# 1. Format code
black src/ tests/
# 2. Run linter
ruff check src/ tests/
# 3. Type check
pyright src/
# 4. Run tests
pytest tests/
# 5. Check coverage
pytest --cov=src/code_scalpel tests/
Common Issues¶
Line Too Long¶
Black handles most cases. For strings that can't be split:
# Use noqa comment sparingly
long_message = "This is a very long message that cannot be split" # noqa: E501
Unused Imports¶
Ruff will flag these. Remove or use # noqa: F401 if intentionally re-exporting: