Skip to content

Latest commit

 

History

History
180 lines (143 loc) · 7.6 KB

CONTRIBUTING.md

File metadata and controls

180 lines (143 loc) · 7.6 KB

Contributing to cats-eo

Thanks for your interest in cats-eo. This guide is the day-one starting point for human contributors. Companion guidance for AI-assisted contributions lives in CLAUDE.md.

Getting set up

The toolchain — JDK, sbt, Scala, scalafmt, scalafix, metals — is installed via Coursier. Full bootstrap (one shell snippet to install all of it) is documented in CLAUDE.md under "Toolchain"; that section is the canonical reference and stays current with what CI runs.

In short, you need:

  • JDK 17 or JDK 21 (Temurin is fine) — JDK 25 for the kyo module and the docs site.
  • sbt 1.13.x (the launcher boots the version pinned in project/build.properties).
  • Scala 3.9.x (pinned as scala3Version in build.sbt).
  • scalafmt 3.11.x (honours the pin in .scalafmt.conf).

Clone the repo, then optionally enable the project git hooks so your local commits run the same gates CI does:

.githooks/install.sh

See CLAUDE.md "Git hooks" for what each hook checks and how to bypass them when you need to.

Running the local checks

Before pushing, run the four commands CI gates on:

sbt scalafmtCheckAll               # formatting
sbt compile                        # full project compile
sbt test                           # core + tests + generics + circe
sbt 'docs/mdoc; docs/laikaSite'    # mdoc-verify every doc fence + render the site

If you installed the git hooks (above), pre-commit runs the format check and the docs build automatically when staged files touch *.scala / *.sbt / *.md / .scalafmt.conf / site/ / build.sbt, and pre-push runs the test sweep. CI runs the same set on every PR.

Submitting a pull request

  1. Fork the repo on GitHub.
  2. Create a topic branch off main.
  3. Make your change. Keep commits focused; the project follows a conventional-commits-style prefix (feat(scope):, fix(scope):, docs(scope):, etc.) — git log shows the pattern.
  4. Run the four local checks above.
  5. Push your branch and open a PR against main.

CI gates on the same checks (scalafmtCheckAll, compile, test, docs build). If any fail, push fixes to the same branch — no need to re-open the PR.

For substantial design changes, open an issue first so we can sketch the approach before you spend time on it.

Running the benchmarks

The benchmarks/ sub-project is not part of the root aggregate, so sbt compile and sbt test skip it. Run benchmarks explicitly:

sbt "benchmarks/Jmh/run -i 5 -wi 3 -f 1 -t 1"     # full sweep
sbt "benchmarks/Jmh/run -i 1 -wi 1 -f 1 LensBench.eoGet"   # single bench

Full instructions, the EO-only vs. paired-with-Monocle layout, and JMH caveats live in CLAUDE.md "Benchmarks (JMH vs Monocle)" and in benchmarks/README.md.

Benchmarks in CI

PRs that touch benchmark-mapped code (core/, avro/, circe/, jsoniter/, schemes/, generics/, the harness, or the build) automatically get a Benchmark A/B sticky comment after successful CI: the affected benchmarks run on the merge-base and on your exact head, on the same VM, at a reduced JMH profile. The benchmark workflow runs separately on the default branch, so it adds no long-running check to your PR head and never gates merging. Read it with the project doctrine in mind — the B/op (allocation) delta is authoritative; the ns/op delta is directional. A perf:full label escalates to the full suite; docs/site/tests-only PRs get no comment, and PRs from forks are excluded (same caveat as the docs-preview workflow). An ❌ comment means a side failed to build or run — it is never a pass. Results for closed PRs or superseded heads are not posted. If you add perf:full after CI completes, rerun with gh workflow run bench-pr.yml --ref main -f pr=<number>.

Current numbers live in BENCHMARKS.md (generated by the nightly sweep — never edit it by hand) with per-sweep history charts on the project's GitHub Pages under /bench/, and each release carries its jmh-results-<tag>.json as a downloadable asset. Operational details (calibration, warning thresholds, mapping maintenance): .github/bench/README.md.

Previewing the docs site locally

sbt 'docs/mdoc; docs/laikaSite'
# Site output:
#   site/target/docs/site/

Serve that directory with any static HTTP server, e.g.:

python3 -m http.server -d site/target/docs/site/ 8080

mdoc verifies every code fence in site/docs/*.md against the live library, so a docs change that breaks a fence fails the build.

Coverage

The project's primary quality signal is statement / branch coverage via sbt-scoverage. The canonical command, output paths, and the rationale for relying on local runs (rather than a CI-uploaded badge) are documented in CLAUDE.md under "Test-suite quality".

If you add new runtime code, please add the matching tests so coverage doesn't drift. Pure type-level machinery doesn't need coverage and is not counted against the target.

Releasing a new version (maintainer reference)

  1. Update CHANGELOG.md: cut a ## [X.Y.Z] - date section out of [Unreleased] (keep the [Unreleased] placeholder above it). This is load-bearing: the GitHub Release is created from this section, and the release workflow fails if it's missing.
  2. Run sbt githubWorkflowGenerate and commit any ci.yml drift — a stale generated workflow has killed a tag build before. Never hand-edit ci.yml.
  3. Commit with release: vX.Y.Z.
  4. git tag vX.Y.Z && git push origin main vX.Y.Z.
  5. The tag build runs the staged Sonatype Central Portal publish. On success, two hand-maintained follow-up workflows fire on their own: update-readme-version.yml bumps the README's artifact versions on main, and github-release.yml publishes the GitHub Release — the tag's CHANGELOG section as the curated headline, with GitHub's auto-generated PR list appended (categorised per .github/release.yml, so label PRs: breaking-change, enhancement, bug, performance, documentation).
  6. Watch the workflow; on success the artifacts appear at https://repo1.maven.org/maven2/dev/constructive/cats-eo_3/X.Y.Z/.

Release-notes conventions: a binary-breaking release leads its changelog section with the recompile warning (inline-spliced call sites fail at runtime against old jars — downstream users must see this on the release page); link site pages (https://eo.constructive.dev/…) rather than source files; when performance is part of the story, include the CI benchmark numbers, never local ones.

Manual recovery for a partial Central Portal publish

If the release workflow errors mid-staging — signing failure, namespace permission lapse, network blip during upload — recover as follows:

  1. Sign in at https://central.sonatype.com/publishing/deployments.
  2. Find the partially-uploaded deployment with status "Failed" or "Pending".
  3. Click "Drop" to discard it. This clears the staging area. Partially-uploaded artifacts in maven-central never become canonical because the deployment never transitioned to "Validated → Released".
  4. Resolve the underlying error (signing failure, namespace permission lapse, etc.). Bump the patch version and push a new tag — there is no concept of "rerun the failed publish"; you publish a new version.
  5. If the failed deployment was a snapshot, it is silently overwritten by the next snapshot publish to the same coords; no manual cleanup is needed.