Contributing to the Convert Ruby SDK
Thanks for contributing. This guide covers the dev setup, the conventions CI enforces, the test layout, and how releases happen.
Development setup
Requires Ruby ≥ 3.1 (the gem supports CRuby 3.1–3.4 and JRuby).
bundle install # install dev/test dependencies
bundle exec rake # the default task: RSpec + RuboCop
Type checking (RBS + Steep) lives in a separate Gemfile group so the JRuby matrix leg can skip the C-extension build:
bundle exec rbs -r net-http -r uri -r json -I sig validate # validate RBS signatures
bundle exec steep check # static type check
The gem has zero runtime dependencies by design (stdlib only). Adding a
runtime dependency is an architecture change, not a routine PR — discuss it
first. All dev/test gems live in the Gemfile.
Conventions CI enforces
The Quality Checks workflow (.github/workflows/qa.yml) gates every PR:
- RuboCop —
bundle exec rubocopmust pass (config in.rubocop.yml). Notably: frozen-string-literal magic comment on every file, double-quoted strings, 120-char lines. - RBS + Steep — signatures in
sig/must validate and type-check againstlib/. - RSpec — the full suite runs on the CRuby 3.1–3.4 + JRuby matrix, with coverage gates (see below).
- Cross-SDK parity — the 75-vector MurmurHash3 parity suite must pass 100% (a byte-identical-bucketing release gate).
- Full-chain + gem-smoke + demo fork-smoke — end-to-end release gates.
Conventional Commits + squash merge
This repo is squash-merge only, so the PR title becomes the squash commit subject — and CI validates it as a Conventional Commit:
<type>(<scope>)?!?: <description>
Allowed types: feat, fix, docs, test, refactor, perf, build, ci,
chore. A feat or fix (or a ! breaking-change marker) drives the next
release version (see below), so title accuracy matters.
Examples:
feat(context): add run_features bulk evaluation
fix(api): retain queue on a failed flush POST
docs(readme): document the sentinel return contract
Coverage gates
Coverage is single-sourced in spec/spec_helper.rb (do not declare thresholds
elsewhere). The global floor is 85% line coverage; the critical algorithm units
(Hashing, Bucketing, Rules) require ≥ 95% line and branch coverage. Coverage
runs on CRuby only — the JRuby leg runs the suite without the gate.
Test layout
| Directory | What lives there |
|---|---|
spec/unit/ |
Per-class unit specs (mock collaborators, isolated logic). |
spec/integration/ |
End-to-end specs: full_chain_spec.rb (the release-blocking create→decide→track→flush loop), runtime_recipes_spec.rb (the runtime-lifecycle recipes the quickstarts are transcribed from), fork_safety_spec.rb, factory_wiring_spec.rb. |
spec/cross_sdk/ |
The cross-SDK MurmurHash3 parity vectors (the byte-identical bucketing proof). |
spec/docs/ |
Docs-snippet smoke specs — run the README/quickstart code samples against the real gem so documentation never drifts. |
spec/staging/ |
The live-platform suite (runs on schedule/dispatch only, never in PR CI). |
spec/support/ |
Shared helpers (e.g. runtime_recipe_helpers.rb, which co-locates the recipe wiring snippets the quickstarts ship). |
spec/fixtures/ |
Vendored config fixtures (test-config.json). |
demo/rails/ |
The living Rails example app + fork-safety smoke (an app, not the lib — excluded from gem/RuboCop/coverage gates). |
When adding a documented code sample, copy it from a working spec or the demo
and add it to the spec/docs/ smoke spec — never ship doc-only code that can
drift from the real gem.
Testing & verification
Run the default gate before opening a PR:
bundle exec rake # RSpec + RuboCop — the same pair CI enforces
See the Testing wiki page
for the full command reference: individual RSpec invocations, the RBS/Steep type-check
commands, the cross-SDK parity gate, the full-chain release gate, and the Puma-cluster
fork smoke. Do not change the coverage configuration — it is single-sourced in
spec/spec_helper.rb (see Coverage gates above).
API documentation (YARD)
All public classes and methods are YARD-documented; @api private internals are
excluded from the published docs (.yardopts sets --no-private). Build the
docs locally with:
bundle exec yard doc # output to doc/
bundle exec yard stats --list-undoc # audit the public surface
The published docs deploy to GitHub Pages
on every push to main (.github/workflows/pages.yml).
Releases
Releases are fully automated and run only from main — there is no
rake release task (it is deliberately absent from the Rakefile). The release
pipeline (semantic-release in .github/workflows/release.yml) reads the merged
Conventional Commit subjects since the last release, computes the next semantic
version, tags it, and publishes the gem to RubyGems via OIDC Trusted Publishing.
The changelog is GitHub Releases (there is no CHANGELOG.md in the repo).
You never bump the version or publish manually — write an accurate PR title and the pipeline does the rest on merge.
See RELEASE.md for the full release runbook: the release chain,
the Conventional-Commit → version map, one-time repo-admin setup (RubyGems
trusted-publisher registration, GitHub Pages, branch-protection required checks),
dry-runs, the first v1.0.0 release, the fork-PR safeguard, rollback (gem yank), and troubleshooting.