Contributing to portolan-cli¶
Thank you for your interest in contributing to portolan-cli!
Most of the code here is written by AI agents, so the bar is set by automation, not by reviewer attention: every quality gate is enforced in CI, and a PR is trustable exactly to the degree it turns those gates green. AI-assisted contributions are welcome and encouraged — the strict CI is what makes them safe to merge.
What a finished PR looks like¶
Before you ask for review, a PR should clear this bar:
- [ ] Tests first, and they exercise real behavior. New/changed product code ships with tests written before the implementation (TDD is required, see Testing). Prefer a reproducible failing test as the starting point.
- [ ] Integration coverage across boundaries. If the change spans layers (CLI → API → format handlers → backend), there is a test that crosses them, not only unit tests.
- [ ] All CI is green. Every required check passes — see What CI checks. "Green means green": nothing merges red.
- [ ] Changed lines are covered. The
codecov/patchgate (changed-line coverage) is satisfied by the fast test suite. - [ ] At least one adversarial review. A human or agent has actively tried to break the change (edge cases, failure modes), not just skimmed it.
- [ ] CodeRabbit comments addressed. The automated reviewer's findings are resolved or explicitly answered.
- [ ] Docs and ADRs updated. User-facing behavior is documented; non-obvious
decisions have an ADR (
context/shared/adr/);menarddoc-freshness passes.
If you run prek run --all-files locally and it is green, you have cleared most of
this bar before pushing.
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 prek (git hook manager)
uv tool install prek prek 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. PRs are squash-merged,
so the PR title becomes the commit message — write it in conventional form.
Pull Request Process¶
- Create a branch from
main - Write tests first, then implement (TDD is required — see Testing)
- Run
prek 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¶
There is one rule source: CI runs the same hooks you run locally via
prek run --all-files (see prek.toml), rather than re-listing each tool with its
own arguments. So a green local prek run is a faithful preview of the CI quality
job. That single job covers:
- ruff — lint + format
- mypy — type checking (strict)
- import-linter — architecture contracts (ADR-0025)
- codespell — spelling
- vulture / xenon / pylint — dead code, complexity, duplication
- bandit — security scanning
- deptry — dependency hygiene
- menard — code↔doc freshness
- actionlint / zizmor — GitHub Actions workflow linting + supply-chain audit
Alongside quality, CI runs the test matrix (Python 3.10–3.13 × Linux/macOS/
Windows), pip-audit (dependency CVEs, ignores tracked in .pip-audit-ignores
with expiry dates), the docs build, and the package build.
Required checks (enforced by branch-protection rulesets, applied via
scripts/apply_branch_protection.sh): CI Success (a single job that aggregates
every gate above — so adding a Python/OS never drops a required check),
codecov/patch, and codecov/project. All checks are strict; none allow failures.
Heavier gates run nightly, not per-PR¶
To keep PR feedback fast, the expensive gates run on a schedule and don't block your PR:
- Mutation testing (
mutmut) — verifies tests actually catch injected bugs (currently being repaired, see #612). - Benchmark regression — flags performance regressions.
- Live-network tests — hit real third-party services; because those can be flaky, this job is non-blocking and a failure opens a single self-closing tracking issue instead of turning the nightly red.
Self-healing automation¶
- Dependency vulnerabilities — the security-audit workflow opens/updates/closes a single tracking issue automatically as CVEs appear and get resolved.
- Dependabot auto-merge — patch/minor dependency bumps are approved and auto-merged once the full green check set passes; majors stay for a human.
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¶
Releases are bump-commit-triggered, not tag-triggered: pushing a bump: commit
to main drives the release workflow, which creates the tag itself. Version bumps
follow 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, open a PR that runs:
uv run cz bump --changelog
When that PR merges, the release workflow detects the bump: commit, creates the
git tag, builds the package, publishes to PyPI (trusted publishing), 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/
Spec Changes¶
The Portolan specification lives in spec/ within this repository. The CLI repo
is the source of truth for the spec; the separate
portolan-spec repository is a
read-only mirror synced via CI.
To propose spec changes:
- Open a PR in this repository that modifies files in
spec/ - The PR itself is the proposal — discuss in the PR comments
- On merge, CI automatically syncs changes to portolan-spec
See ADR-0048 for rationale.
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.