Thanks for helping build a Haskell implementation of libp2p. This document
covers how work is picked up, how changes land on main, and how releases are
cut. The authoritative references for any protocol work are the
upstream libp2p specs and the reference
implementations (go-libp2p,
rust-libp2p).
Build and test commands, and the GHC version requirement, are in the Building and Tests sections of the README. Docker is needed only for the interop tests below.
- Open issues are the backlog. Issues labeled
good first issueare scoped to a single module and a good way in. - Before starting, leave a comment on the issue saying you are taking it. This avoids two people implementing the same thing.
- Issues are grouped into GitHub Milestones named after the release they
target (for example
v0.2.0.0). If your change is not on a milestone, that is fine; it will go out with the next release regardless. - For anything larger than an issue describes, open an issue first and outline the approach. Check the relevant spec section before proposing a design.
mainis the only long-lived branch. It is protected: no direct pushes, all changes arrive through pull requests.- Always branch from the latest
main. Never branch from another feature branch; stacked PRs get auto-closed when their base branch is deleted. - Name branches by kind and issue number:
feat/286-switch-events,fix/284-dht-empty-addrs,docs/release-process. Kinds:feat/,fix/,docs/,refactor/,chore/. - Merged branches are deleted automatically. Do not reuse a branch after its PR merges.
- Title must follow Conventional Commits:
feat: …,fix: …,docs: …,refactor: …,test: …,chore: …,perf: …. Add!after the type for a breaking change (feat!: …). PRs are squash-merged, so the title becomes the commit message onmainand is what appears in release notes. - Fill in the PR template. Link the issue with
Closes #NNN. - Keep a PR to one logical change. Split unrelated fixes into separate PRs.
- Merging requires:
- one approving review from someone other than the author;
- the
buildjob and the three interop jobs (transport-interop,perf-interop,kad-dht-interop) passing.
- If you are contributing from a fork for the first time, a maintainer has to approve the workflow run before CI starts. This is a GitHub default, not a judgement on your PR.
- Interop failures caused by upstream images being flaky can be re-run by a maintainer. Mention it in the PR if you suspect that is the case.
- Follow the style of the surrounding module. The project uses a single Cabal
library; add new modules to
exposed-modulesinlibp2p-hs.cabal. - Protocol buffers are encoded by hand (see
LibP2P.Crypto.ProtobufandCore.Binary); do not add a protobuf compiler dependency. - Every new protocol feature needs tests. Wire-format tests against vectors from the spec or from go-libp2p are preferred over round-trip-only tests.
- Code, comments, and commit messages are in English.
The interop/ directory holds the local harness used by CI to test against
go-libp2p, nim-libp2p, and rust-libp2p. Run the transport interop locally with:
make -C interop build
make -C interop cross-test-go-listener
make -C interop cross-test-hs-listenerSee interop/Makefile for the perf, kad-dht, and gossipsub targets, and
interop/RESULTS.md for what has been verified so far.
libp2p-hs follows the
Haskell Package Versioning Policy (PVP). The
version: field in libp2p-hs.cabal is the single source of truth. While the
project is on 0.x:
| Change | Bump | Example |
|---|---|---|
| Breaking API change | second component | 0.1.0.0 → 0.2.0.0 |
| Backwards-compatible addition | third component | 0.1.0.0 → 0.1.1.0 |
| Bug fix, no API change | fourth component | 0.1.0.0 → 0.1.0.1 |
Releases are git tags plus GitHub Releases; the package is not yet published to Hackage.
-
Decide the new version. List the PRs merged since the last release:
gh pr list --state merged --search "merged:>$(gh release view --json publishedAt -q .publishedAt)"Take the highest row from the table above that applies: any PR with
!in its title or any change to an exported type or function signature means the second component; otherwise afeat:PR means the third; otherwise the fourth. -
If the repository's Milestones page has a milestone for this release, confirm every issue on it is closed or moved to the next milestone. If none exists, skip this step.
-
Open a PR titled
chore: release vX.Y.Z.Wthat changes only theversion:line inlibp2p-hs.cabal. -
Once merged, the
Releaseworkflow (.github/workflows/release.yml) creates the tagvX.Y.Z.Won that commit and a GitHub Release with notes generated from the merged PRs, grouped by label. Nobody pushes tags by hand. -
Close the milestone if there was one.
Release notes group PRs by label. Labels are applied automatically from the PR
title prefix by the PR Labeler workflow; if a PR is grouped wrongly, fix its
label before the release PR merges.
Only when a fix must ship for a release that is no longer the head of main.
Do not create release branches ahead of time.
- Create the branch from the tag being patched:
git switch -c release/v0.1 v0.1.0.0(branch name isrelease/plus the first two components). If the branch already exists, use it. - Push the branch and open the fix PR with
release/v0.1as the base branch. The same review and CI rules apply as formain. - After the fix merges, open a second PR against
release/v0.1titledchore: release v0.1.0.1that bumps the fourth component inlibp2p-hs.cabal. - The
Releaseworkflow only watchesmain, so after that PR merges tag the release by hand (no local checkout needed):gh release create v0.1.0.1 --target release/v0.1 --title v0.1.0.1 --generate-notes. - If the fix also applies to
main, open a separate PR there; do not mergerelease/v0.1intomain.
Open a GitHub issue with the question label.