Contributing¶
owlcompare is built component-by-component against written specifications. This page gets you a working development environment, shows you how to run the checks, and explains the spec-driven workflow the project follows.
Set up the dev environment¶
owlcompare uses uv for everything — dependencies, virtual environments, and running tools. Install uv first if you don't have it, then:
git clone https://github.com/Ajala111/owlcompare.git
cd owlcompare
uv sync # create the venv and install runtime + dev dependencies
uv sync does not install the documentation toolchain — those live in an
optional group. Add them only when you're working on the site:
Requirements: Python 3.11+ (3.12 recommended). On Windows with Smart App
Control, install Python via winget install Python.Python.3.11 and run
uv python pin 3.11 to avoid intermittent interpreter blocks.
Run the checks¶
owlcompare has four quality gates. All four must be green before a change lands:
uv run pytest # tests
uv run ruff check . # lint
uv run ruff format --check . # formatting
uv run mypy src/owlcompare # type-checking (strict on the public API)
Run a single test file while iterating:
Windows invocation
If uv run owlcompare ... is blocked by Smart App Control, use
uv run python -m owlcompare ... instead — it routes through the trusted
interpreter and behaves identically.
Build the docs locally¶
uv sync --group docs
uv run mkdocs serve # live preview at http://127.0.0.1:8000/
uv run mkdocs build --strict # production build; --strict fails on broken links
The landing page (site_src/index.html) is overlaid onto the built site via the
overlay_landing_page hook. Local mkdocs serve shows the same landing page as
the deployed site.
We pin to mkdocs 1.x and mkdocs-material 9.x. The MkDocs ecosystem may evolve significantly in 2026–2027; revisit pinning when migrating.
The spec-driven workflow¶
Every component starts as a spec in specs/NN-*.md and is built to that contract.
The required reading order each session is in CLAUDE.md, but in short:
- Read
docs/ROADMAP.mdto see what's done and what's next. - Read the relevant
specs/NN-*.mdfor the component you're building. - Follow
docs/CONVENTIONS.mdfor code style. - Check
docs/DESIGN_DECISIONS.mdbefore introducing any new dependency or pattern.
A few operating principles the project holds to:
- Stay in scope. Build what the spec describes. Out-of-scope problems go in the roadmap's Backlog, not into the current change.
- Stop and ask, don't guess. One clarifying question beats a wrong implementation.
- Write tests as you go. Every spec includes acceptance tests; they're not optional.
- One component per pull request. Reference the spec file in the PR description.
Adding a new diff slice¶
The structural diff is organized as independent slices under
src/owlcompare/diff/ (entities, hierarchy, restrictions, annotations). To add a
new kind of structural analysis:
- Write the spec first, or extend the existing one.
- Add the slice module and have it emit
Changerecords with the correctkind,severity, andsubsumesset. - Register it in the orchestrator pipeline.
- Add small, hand-crafted Turtle fixtures under
tests/fixtures/that exercise the new behavior, plus golden output where applicable. - If you add a new
Changekind, document it in Change kinds — and remember that adding a kind is forward-compatible with the JSON schema (it isn't an enum).
Regenerating the screenshots¶
The landing-page screenshots are captured from real owlcompare output, not
committed as generated artifacts. When the HTML or Markdown rendering changes
materially, regenerate them — the instructions live in
site_src/docs/assets/README.md. There's no automated drift detection, so this
is a manual step at review time.
Publishing the docs¶
The site deploys automatically: a push to main triggers
.github/workflows/docs.yml, which builds with mkdocs build --strict (the
overlay_landing_page hook drops the custom landing page over the generated
index during that build) and deploys to GitHub Pages. If a deploy fails, check
Settings → Pages → Source: GitHub Actions is enabled on the repository.
Cutting a release¶
owlcompare publishes to PyPI through a tag-triggered GitHub Actions workflow using OIDC Trusted Publishing — there is no API token stored in the repository.
The version is single-sourced¶
The version lives in one place: src/owlcompare/_version.py
(__version__ = "X.Y.Z"). pyproject.toml reads it dynamically via Hatchling
(DD-013), so you never edit the version in two files. The release workflow
refuses to publish if the pushed tag doesn't match __version__.
Steps to release X.Y.Z¶
- Bump the version. Edit
src/owlcompare/_version.pyto the new version. - Update the changelog. Move the relevant
## [Unreleased]notes into a new## [X.Y.Z] - YYYY-MM-DDsection inCHANGELOG.md(Keep a Changelog format). Add thecompare/taglink references at the bottom. - Commit both on
main(via PR):Release X.Y.Z. - Tag and push:
Pushing the vX.Y.Z tag triggers .github/workflows/release.yml, which:
- rejects the tag unless it's a clean final release (
vMAJOR.MINOR.PATCH); - verifies the tag matches
_version.py; - builds the sdist + wheel with
python -m build; - runs
twine check dist/*; - publishes to PyPI via
pypa/gh-action-pypi-publish(OIDC, environmentpypi); - creates a GitHub Release whose body is this version's
CHANGELOG.mdsection, with the build artifacts attached.
Stage on TestPyPI first¶
For a dry run, bump _version.py to a pre-release (e.g. 0.1.0rc1) and push a
pre/... tag:
That triggers .github/workflows/release-test.yml, which runs the same build and
checks but publishes to TestPyPI (environment
testpypi) and does not cut a GitHub Release. Install the staged build to
verify it:
pip install --index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ owlcompare==0.1.0rc1
Versioning policy¶
owlcompare follows Semantic Versioning with a pre-1.0 caveat:
0.x: minor bumps (0.1→0.2) may break the CLI surface or JSON schema; patch bumps (0.1.0→0.1.1) are bug-fix only.1.0onward: the CLI and JSON schema become a stability commitment; breaking changes bump the major version.
Recovering from a bad release¶
PyPI release artifacts are immutable — you cannot re-upload the same version. If a published release is broken:
- Yank it on PyPI (Manage → Release → Options → Yank). Yanking hides the version from new installs but keeps it resolvable for anyone who pinned it — it is not a delete.
- Fix the metadata/code on
main. - Bump to the next patch (
0.1.0→0.1.1) in_version.pyand add aCHANGELOG.mdentry — never reuse a version number. - Delete the bad tag if it never published successfully
(
git push --delete origin vX.Y.Z), then retag the fix. If it did publish, leave the tag and move forward with the new patch version.
A good habit is to stage on TestPyPI first (above), which catches metadata
problems (twine check, missing long-description rendering) before they reach the
real index.
License¶
owlcompare is MIT licensed. By contributing, you agree your contributions are licensed under the same terms.