Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,19 @@ jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
- uses: astral-sh/setup-uv@d0d8abe699bfb85fec6de9f7adb5ae17292296ff # v6
with:
enable-cache: true
- uses: actions/setup-python@v5
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
- run: uv sync --all-extras --dev --locked
- run: uv run ruff check .
- run: uv run ruff format --check .
- run: uv run mypy src tests
- run: uv run pytest --cov=contribcheck --cov-report=term-missing --cov-report=xml
- run: uv build
- run: uvx --from twine==7.0.0 twine check dist/*
84 changes: 84 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: Publish to PyPI

on:
release:
types: [published]

concurrency:
group: pypi-${{ github.event.release.tag_name }}
cancel-in-progress: false

permissions: {}

jobs:
build:
name: Build and verify distributions
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Check out release tag
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"

- name: Set up uv
uses: astral-sh/setup-uv@d0d8abe699bfb85fec6de9f7adb5ae17292296ff # v6
with:
enable-cache: false

- name: Verify tag matches package version
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
tag_version="${RELEASE_TAG#v}"
package_version="$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')"
if [[ "$tag_version" != "$package_version" ]]; then
echo "Release tag $RELEASE_TAG does not match package version $package_version" >&2
exit 1
fi

- name: Build wheel and source distribution
run: uv build

- name: Verify package metadata
run: uvx --from twine==7.0.0 twine check dist/*

- name: Smoke-test the wheel
run: |
python -m venv /tmp/contribcheck-release
/tmp/contribcheck-release/bin/python -m pip install dist/*.whl
/tmp/contribcheck-release/bin/contribcheck --version

- name: Upload distributions
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: python-package-distributions
path: dist/
if-no-files-found: error
retention-days: 7

publish:
name: Publish distributions to PyPI
if: github.repository == 'gokul-debugger/contribcheck'
needs: build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/project/contribcheck/
permissions:
id-token: write
steps:
- name: Download distributions
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: python-package-distributions
path: dist/

- name: Publish distributions
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
An evidence-based CLI and API that checks whether a GitHub issue is genuinely ready for an open-source contribution.

[![CI](https://github.com/gokul-debugger/contribcheck/actions/workflows/ci.yml/badge.svg)](https://github.com/gokul-debugger/contribcheck/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/contribcheck?logo=pypi&logoColor=white)](https://pypi.org/project/contribcheck/)
[![Python 3.11+](https://img.shields.io/badge/Python-3.11%2B-3776AB?logo=python&logoColor=white)](https://www.python.org/)
[![FastAPI](https://img.shields.io/badge/API-FastAPI-009688?logo=fastapi&logoColor=white)](https://fastapi.tiangolo.com/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/gokul-debugger/contribcheck/blob/main/LICENSE)

ContribCheck identifies blockers that issue-discovery lists often miss: dependency issues, missing base branches, existing assignments, comment claims, competing pull requests, unhealthy default-branch CI, and missing contribution guidance.

Expand Down Expand Up @@ -98,11 +99,11 @@ The overall result is one of:
ContribCheck requires Python 3.11 or newer.

```bash
python3.12 -m venv .venv
source .venv/bin/activate
pip install -e .
pipx install contribcheck
```

Alternatively, install it into an existing Python environment with `pip install contribcheck`. The optional web service is available with `pip install "contribcheck[server]"`.

Public GitHub requests work without authentication, but GitHub applies a much smaller anonymous rate limit. Set a token for regular use:

```bash
Expand Down Expand Up @@ -203,7 +204,7 @@ contribcheck serve

Open `http://127.0.0.1:8000/` for the browser interface or `http://127.0.0.1:8000/docs` for the interactive API documentation. The browser interface sends only the issue reference and optional actor to the local API. It does not accept, store, or log GitHub tokens.

![ContribCheck web interface](docs/contribcheck-ui.png)
![ContribCheck web interface](https://raw.githubusercontent.com/gokul-debugger/contribcheck/main/docs/contribcheck-ui.png)

Call the API directly:

Expand Down Expand Up @@ -243,7 +244,7 @@ Tests use a mocked GitHub transport and do not consume API quota.

Contributions are welcome. Start with the [good first issues](https://github.com/gokul-debugger/contribcheck/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22) or browse tasks marked [help wanted](https://github.com/gokul-debugger/contribcheck/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22help%20wanted%22).

Read [CONTRIBUTING.md](CONTRIBUTING.md), comment with a short implementation plan, and wait for confirmation before starting work that changes a public contract.
Read [CONTRIBUTING.md](https://github.com/gokul-debugger/contribcheck/blob/main/CONTRIBUTING.md), comment with a short implementation plan, and wait for confirmation before starting work that changes a public contract.

## Current Limitations

Expand Down
31 changes: 31 additions & 0 deletions docs/RELEASING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Release Process

ContribCheck publishes signed distributions to PyPI through GitHub Trusted Publishing. No PyPI password or long-lived API token is stored in the repository.

## One-time PyPI configuration

Create a pending Trusted Publisher on PyPI with these exact values:

| Field | Value |
|---|---|
| PyPI project name | `contribcheck` |
| GitHub owner | `gokul-debugger` |
| Repository | `contribcheck` |
| Workflow | `publish.yml` |
| Environment | `pypi` |

Create a GitHub environment named `pypi`. Add required-reviewer protection before publishing if the repository plan supports it.

## Release checklist

1. Confirm `main` is clean and CI is passing.
2. Set the same release version in `pyproject.toml` and `src/contribcheck/__init__.py`.
3. Update `uv.lock` when the project version or dependencies change.
4. Run the full local quality suite.
5. Build both distributions and inspect their contents.
6. Create an annotated `vX.Y.Z` tag from the intended commit.
7. Publish a GitHub release for that tag.
8. Wait for the `Publish to PyPI` workflow to finish.
9. Verify installation in a clean environment with `pipx install contribcheck`.

The workflow refuses to publish when the release tag and package metadata versions differ. PyPI does not permit replacing an uploaded version, so publish only from a reviewed commit.
14 changes: 13 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ keywords = [
classifiers = [
"Development Status :: 3 - Alpha",
"Environment :: Console",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
Expand Down Expand Up @@ -57,13 +59,23 @@ contribcheck = "contribcheck.cli:app"

[project.urls]
Homepage = "https://github.com/gokul-debugger/contribcheck"
Documentation = "https://github.com/gokul-debugger/contribcheck#readme"
Issues = "https://github.com/gokul-debugger/contribcheck/issues"
Releases = "https://github.com/gokul-debugger/contribcheck/releases"
Source = "https://github.com/gokul-debugger/contribcheck"

[tool.hatch.build.targets.wheel]
packages = ["src/contribcheck"]

[tool.hatch.build.targets.sdist]
include = ["src/contribcheck", "tests", "README.md", "LICENSE", "CONTRIBUTING.md"]
include = [
"src/contribcheck",
"tests",
"docs/RELEASING.md",
"README.md",
"LICENSE",
"CONTRIBUTING.md",
]

[tool.ruff]
line-length = 100
Expand Down
3 changes: 2 additions & 1 deletion src/contribcheck/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""ContribCheck public package interface."""

__version__ = "0.1.0"

from contribcheck.analyzer import IssueAnalyzer
from contribcheck.github import GitHubClient
from contribcheck.models import InspectionReport, OverallStatus

__all__ = ["GitHubClient", "InspectionReport", "IssueAnalyzer", "OverallStatus"]
__version__ = "0.1.0"
3 changes: 2 additions & 1 deletion src/contribcheck/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import httpx

from contribcheck import __version__
from contribcheck.exceptions import GitHubAPIError, GitHubRateLimitError
from contribcheck.models import IssueTarget, JsonObject

Expand All @@ -36,7 +37,7 @@ def __init__(
)
headers = {
"Accept": "application/vnd.github+json",
"User-Agent": "contribcheck/0.1.0",
"User-Agent": f"contribcheck/{__version__}",
"X-GitHub-Api-Version": API_VERSION,
}
if resolved_token:
Expand Down
2 changes: 2 additions & 0 deletions tests/test_github.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import httpx
import pytest

from contribcheck import __version__
from contribcheck.exceptions import GitHubRateLimitError
from contribcheck.github import API_VERSION, GitHubClient, normalize_api_base_url
from contribcheck.models import IssueTarget
Expand All @@ -17,6 +18,7 @@ async def test_client_sends_version_and_authentication_headers() -> None:
async def handler(request: httpx.Request) -> httpx.Response:
assert request.headers["X-GitHub-Api-Version"] == API_VERSION
assert request.headers["Authorization"] == "Bearer secret"
assert request.headers["User-Agent"] == f"contribcheck/{__version__}"
return httpx.Response(200, json={"default_branch": "main"})

async with GitHubClient(token="secret", transport=httpx.MockTransport(handler)) as client:
Expand Down
Loading