Thank you for your interest in contributing to GraphOptim! This guide will help you get started.
- Code of Conduct
- Getting Started
- Development Setup
- Making Changes
- Pull Request Process
- Coding Standards
- Adding Optimization Passes
This project adheres to the Contributor Covenant Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior via the repository issues.
- Fork the repository on GitHub
- Clone your fork locally
- Create a branch for your contribution
- Make your changes following the guidelines below
- Submit a Pull Request
# Clone your fork
git clone https://github.com/YOUR-USERNAME/graphoptim.git
cd graphoptim
# Install with uv (recommended)
uv pip install -e ".[dev,benchmark]"
# Verify setup
uv run pytest tests/ -v
uv run graphoptim --version- Python ≥ 3.9
- uv (recommended) or pip
- Git
Use descriptive branch names:
feat/dead-code-pass-improvements— New featuresfix/cfg-builder-async-functions— Bug fixesdocs/update-benchmark-section— Documentationrefactor/metrics-extraction— Code refactoringtest/add-centrality-pass-tests— Test additions
Follow Conventional Commits:
feat: add critical path optimizer pass
fix: handle async functions in CFG builder
docs: update benchmark results in README
test: add regression tests for path shortener
refactor: extract metric computation into helper
- Update tests — All new code must have corresponding tests
- Run the full test suite —
uv run pytest tests/ -v - Run linting —
uv run ruff check . - Run formatting —
uv run black . - Update CHANGELOG.md — Add your changes under
[Unreleased] - Fill out the PR template — Describe what and why
- All tests pass
- Code is formatted (black) and linted (ruff)
- New code has docstrings
- CHANGELOG.md is updated
- No breaking changes without discussion
- Formatter:
blackwith default settings - Linter:
ruffwith rules E, F, I, W - Type hints: Required for all public API functions
- Docstrings: Google-style docstrings for all public classes and functions
def detect_dead_nodes(cfg: nx.DiGraph) -> list[DeadNode]:
"""
Detect dead (unreachable) code blocks in a CFG.
A dead node is one with in-degree=0 that is not the entry node.
Args:
cfg: NetworkX DiGraph representing the CFG.
Returns:
List of DeadNode objects.
"""
...- Never break working code — The optimizer must produce syntactically valid, semantically equivalent code
- Preserve docstrings and comments — AST rewrites must carry through all decorators, docstrings, and inline comments
- Dry-run by default —
optimize()returns a string; never writes to disk unless explicitly asked - Graceful degradation — If a pass fails, skip it rather than crashing
GraphOptim is designed to be extensible. To add a new optimization pass:
Create a new file in graphoptim/optimizer/passes/:
# graphoptim/optimizer/passes/my_pass.py
from dataclasses import dataclass
from typing import Optional
@dataclass
class MyPassFinding:
"""A finding from this pass."""
line: Optional[int]
description: str
class MyPass:
"""Description of what this pass does."""
name = "my_pass"
cost = 0.3 # Risk level (0.0-1.0)
benefit = 0.0 # Set dynamically during detection
def detect(self, source_code: str, func_name=None) -> list[MyPassFinding]:
"""Detect issues."""
...
def fix(self, source_code: str) -> str:
"""Apply fixes. Must return valid Python."""
...Add your pass to PASS_REGISTRY in graphoptim/optimizer/rewriter.py.
Create tests/test_my_pass.py with both detection and fix tests.
- Add the pass to the table in
README.md - Add an entry to
CHANGELOG.md
Open an issue or start a discussion. We're happy to help!