Skip to content

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:

from .module import SomeClass  # noqa: F401  # Re-export