Contributing to portolan-cli¶
Thank you for your interest in contributing to portolan-cli!
Development Setup¶
-
Clone the repository
git clone https://github.com/portolan-sdi/portolan-cli.git cd portolan-cli -
Install uv (if not already installed)
curl -LsSf https://astral.sh/uv/install.sh | sh -
Install dependencies
uv sync --all-extras -
Install pre-commit hooks
uv run pre-commit install -
Verify setup
uv run pytest uv run portolan --help
Making Changes¶
Branch Naming¶
- Feature:
feature/description - Bug fix:
fix/description - Documentation:
docs/description - Refactor:
refactor/description
Commit Messages¶
We use Conventional Commits enforced by commitizen:
feat(scope): add new feature
fix(scope): fix bug
docs(scope): update documentation
refactor(scope): restructure code
test(scope): add tests
Use uv run cz commit for interactive commit creation.
Pull Request Process¶
- Create a branch from
main - Write tests first, then implement (TDD is required — see Testing section)
- Run
uv run pre-commit run --all-filesto check everything locally - Push and open a PR — CI runs automatically
- Fill in the PR template and link related issues
What CI Checks¶
All PRs must pass:
- ruff — Linting and formatting
- mypy — Type checking (strict mode)
- bandit — Security scanning
- pip-audit — Dependency vulnerabilities
- vulture — Dead code detection
- xenon — Complexity limits
- pytest — Full test suite with coverage
- mkdocs — Documentation build
All checks are strict — none allow failures.
Testing¶
Tests use pytest with markers to categorize test types:
| Marker | Description |
|---|---|
@pytest.mark.unit |
Fast, isolated, no I/O (< 100ms each) |
@pytest.mark.integration |
Multi-component, may touch filesystem |
@pytest.mark.network |
Requires network (mocked locally, real in CI nightly) |
@pytest.mark.realdata |
Uses real-world fixtures from tests/fixtures/realdata/ |
@pytest.mark.snapshot |
Compares output against golden files |
@pytest.mark.benchmark |
Performance measurement, tracked over time |
@pytest.mark.slow |
Takes > 5 seconds |
# All tests
uv run pytest
# Only unit tests
uv run pytest -m unit
# With coverage report
uv run pytest --cov=portolan_cli --cov-report=html
Test-driven development is required. Write tests before implementation. Tests must fail before the implementation exists and pass after. This is not optional.
Release Process¶
Portolan uses a tag-based release workflow driven by conventional commits:
| Commit type | Version bump |
|---|---|
feat: |
Minor (0.x.0) |
fix: |
Patch (0.0.x) |
BREAKING CHANGE: |
Major (x.0.0) |
docs:, refactor:, test:, chore: |
No release |
To cut a release, create a PR that runs:
uv run cz bump --changelog
Merging that PR triggers the release workflow: it creates a git tag, builds the package, publishes to PyPI, and creates a GitHub Release.
Code Standards¶
- All code requires type annotations (
mypy --strict) - Use
portolan_cli/output.pyfor all user-facing terminal messages - Non-obvious design decisions require an ADR in
context/shared/adr/
AI-Assisted Development (Optional)¶
We use AI assistants (Claude Code, etc.) extensively. Useful tools:
| Tool | Purpose | Install |
|---|---|---|
| Speckit | Specification-driven development | Claude Code plugin |
| Context7 | Up-to-date library docs via MCP | MCP server config |
| Gitingest | Source code exploration | pipx install gitingest |
| grepai | Semantic code search via MCP | pipx install grepai |
The project includes .mcp.json for automatic MCP registration. grepai must be installed locally (pipx install grepai) and initialized with grepai init in the project directory. The .grepai/ index is gitignored — it's regenerated per-developer.
Questions?¶
- Bug reports / feature requests: Open an issue
- Questions: Use GitHub Discussions
Code of Conduct¶
Be respectful and constructive. Help create a welcoming environment.
License¶
By contributing, you agree your contributions will be licensed under Apache 2.0.