sheridan-diffract 1.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sheridan_diffract-1.0.1/.github/workflows/bump.yaml +99 -0
- sheridan_diffract-1.0.1/.github/workflows/ci.yaml +37 -0
- sheridan_diffract-1.0.1/.github/workflows/docs.yaml +35 -0
- sheridan_diffract-1.0.1/.github/workflows/pr-title.yaml +24 -0
- sheridan_diffract-1.0.1/.github/workflows/publish.yaml +68 -0
- sheridan_diffract-1.0.1/.github/workflows/tag-on-merge.yaml +51 -0
- sheridan_diffract-1.0.1/.gitignore +41 -0
- sheridan_diffract-1.0.1/.pre-commit-config.yaml +20 -0
- sheridan_diffract-1.0.1/.pre-commit-hooks.yaml +23 -0
- sheridan_diffract-1.0.1/CHANGELOG.md +12 -0
- sheridan_diffract-1.0.1/CLAUDE.md +146 -0
- sheridan_diffract-1.0.1/CONTRIBUTING.md +63 -0
- sheridan_diffract-1.0.1/LICENSE +21 -0
- sheridan_diffract-1.0.1/PKG-INFO +228 -0
- sheridan_diffract-1.0.1/README.md +202 -0
- sheridan_diffract-1.0.1/Taskfile.yaml +113 -0
- sheridan_diffract-1.0.1/ci/__init__.py +0 -0
- sheridan_diffract-1.0.1/ci/pyproject.toml +12 -0
- sheridan_diffract-1.0.1/ci/src/__init__.py +0 -0
- sheridan_diffract-1.0.1/ci/src/main/__init__.py +128 -0
- sheridan_diffract-1.0.1/dagger.json +9 -0
- sheridan_diffract-1.0.1/docs/decisions/.gitkeep +0 -0
- sheridan_diffract-1.0.1/docs/decisions/001-delegate-to-iceberg.md +33 -0
- sheridan_diffract-1.0.1/docs/decisions/002-name-level-comparison.md +42 -0
- sheridan_diffract-1.0.1/docs/decisions/003-gitpython.md +46 -0
- sheridan_diffract-1.0.1/docs/decisions/004-pytest-mock.md +34 -0
- sheridan_diffract-1.0.1/docs/decisions/005-staged-index-comparison.md +38 -0
- sheridan_diffract-1.0.1/pyproject.toml +126 -0
- sheridan_diffract-1.0.1/src/sheridan/diffract/__init__.py +32 -0
- sheridan_diffract-1.0.1/src/sheridan/diffract/checker.py +106 -0
- sheridan_diffract-1.0.1/src/sheridan/diffract/classifier.py +74 -0
- sheridan_diffract-1.0.1/src/sheridan/diffract/cli.py +316 -0
- sheridan_diffract-1.0.1/src/sheridan/diffract/config.py +100 -0
- sheridan_diffract-1.0.1/src/sheridan/diffract/differ.py +48 -0
- sheridan_diffract-1.0.1/src/sheridan/diffract/enums.py +36 -0
- sheridan_diffract-1.0.1/src/sheridan/diffract/exceptions.py +17 -0
- sheridan_diffract-1.0.1/src/sheridan/diffract/git_utils.py +172 -0
- sheridan_diffract-1.0.1/src/sheridan/diffract/models.py +90 -0
- sheridan_diffract-1.0.1/src/sheridan/diffract/py.typed +0 -0
- sheridan_diffract-1.0.1/tests/__init__.py +0 -0
- sheridan_diffract-1.0.1/tests/conftest.py +41 -0
- sheridan_diffract-1.0.1/tests/test_checker.py +213 -0
- sheridan_diffract-1.0.1/tests/test_classifier.py +53 -0
- sheridan_diffract-1.0.1/tests/test_cli.py +827 -0
- sheridan_diffract-1.0.1/tests/test_config.py +98 -0
- sheridan_diffract-1.0.1/tests/test_differ.py +77 -0
- sheridan_diffract-1.0.1/tests/test_enums.py +42 -0
- sheridan_diffract-1.0.1/tests/test_exceptions.py +51 -0
- sheridan_diffract-1.0.1/tests/test_git_utils.py +299 -0
- sheridan_diffract-1.0.1/tests/test_models.py +137 -0
- sheridan_diffract-1.0.1/tests/test_package.py +60 -0
- sheridan_diffract-1.0.1/zensical.toml +24 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
name: Bump version
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
bump:
|
|
9
|
+
name: Commitizen bump
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: write
|
|
13
|
+
pull-requests: write
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- name: Check for bump commit (infinite loop guard)
|
|
17
|
+
id: guard
|
|
18
|
+
env:
|
|
19
|
+
HEAD_MSG: ${{ github.event.head_commit.message }}
|
|
20
|
+
run: |
|
|
21
|
+
if [[ "$HEAD_MSG" == bump:* ]]; then
|
|
22
|
+
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
23
|
+
else
|
|
24
|
+
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
- uses: actions/checkout@v6
|
|
28
|
+
if: steps.guard.outputs.skip == 'false'
|
|
29
|
+
with:
|
|
30
|
+
fetch-depth: 0
|
|
31
|
+
|
|
32
|
+
- name: Set up Python 3.14
|
|
33
|
+
if: steps.guard.outputs.skip == 'false'
|
|
34
|
+
uses: actions/setup-python@v6
|
|
35
|
+
with:
|
|
36
|
+
python-version: "3.14"
|
|
37
|
+
|
|
38
|
+
- name: Set up uv
|
|
39
|
+
if: steps.guard.outputs.skip == 'false'
|
|
40
|
+
uses: astral-sh/setup-uv@v7
|
|
41
|
+
|
|
42
|
+
- name: Install dependencies
|
|
43
|
+
if: steps.guard.outputs.skip == 'false'
|
|
44
|
+
run: uv sync --all-extras --dev
|
|
45
|
+
|
|
46
|
+
- name: Configure git identity
|
|
47
|
+
if: steps.guard.outputs.skip == 'false'
|
|
48
|
+
run: |
|
|
49
|
+
git config user.name "github-actions[bot]"
|
|
50
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
51
|
+
|
|
52
|
+
- name: Bump version
|
|
53
|
+
if: steps.guard.outputs.skip == 'false'
|
|
54
|
+
id: bump
|
|
55
|
+
run: |
|
|
56
|
+
set +e
|
|
57
|
+
uv run cz bump --yes
|
|
58
|
+
code=$?
|
|
59
|
+
set -e
|
|
60
|
+
if [[ $code -eq 0 ]]; then
|
|
61
|
+
NEW_VERSION=$(uv run python -c "import tomllib; f=open('pyproject.toml','rb'); d=tomllib.load(f); print(d['project']['version'])")
|
|
62
|
+
echo "bumped=true" >> "$GITHUB_OUTPUT"
|
|
63
|
+
echo "new_version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
|
|
64
|
+
elif [[ $code -eq 21 ]]; then
|
|
65
|
+
echo "No releasable commits — nothing to bump."
|
|
66
|
+
echo "bumped=false" >> "$GITHUB_OUTPUT"
|
|
67
|
+
else
|
|
68
|
+
echo "cz bump failed with exit code $code."
|
|
69
|
+
exit $code
|
|
70
|
+
fi
|
|
71
|
+
env:
|
|
72
|
+
FORCE_COLOR: "1"
|
|
73
|
+
|
|
74
|
+
- name: Push bump branch
|
|
75
|
+
if: steps.guard.outputs.skip == 'false' && steps.bump.outputs.bumped == 'true'
|
|
76
|
+
env:
|
|
77
|
+
NEW_VERSION: ${{ steps.bump.outputs.new_version }}
|
|
78
|
+
run: |
|
|
79
|
+
git push origin --delete "chore/version-bump-$NEW_VERSION" || true
|
|
80
|
+
git checkout -b "chore/version-bump-$NEW_VERSION"
|
|
81
|
+
git push origin "chore/version-bump-$NEW_VERSION"
|
|
82
|
+
|
|
83
|
+
- name: Open and auto-merge version bump PR
|
|
84
|
+
if: steps.guard.outputs.skip == 'false' && steps.bump.outputs.bumped == 'true'
|
|
85
|
+
env:
|
|
86
|
+
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
|
87
|
+
NEW_VERSION: ${{ steps.bump.outputs.new_version }}
|
|
88
|
+
run: |
|
|
89
|
+
if gh pr list --head "chore/version-bump-$NEW_VERSION" --state open | grep -q .; then
|
|
90
|
+
echo "PR already open for chore/version-bump-$NEW_VERSION — skipping."
|
|
91
|
+
else
|
|
92
|
+
gh pr create \
|
|
93
|
+
--title "bump: $NEW_VERSION" \
|
|
94
|
+
--body "Automated version bump to $NEW_VERSION generated by Commitizen." \
|
|
95
|
+
--base main \
|
|
96
|
+
--head "chore/version-bump-$NEW_VERSION"
|
|
97
|
+
gh pr edit "chore/version-bump-$NEW_VERSION" --add-label automated || true
|
|
98
|
+
gh pr merge "chore/version-bump-$NEW_VERSION" --squash --auto
|
|
99
|
+
fi
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
ci:
|
|
10
|
+
name: All gates
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
if: |
|
|
13
|
+
!startsWith(github.event.head_commit.message, 'bump:') &&
|
|
14
|
+
!startsWith(github.head_ref, 'chore/version-bump-')
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v6
|
|
17
|
+
|
|
18
|
+
- name: Restore Dagger engine cache
|
|
19
|
+
id: cache-dagger
|
|
20
|
+
uses: actions/cache@v5
|
|
21
|
+
with:
|
|
22
|
+
path: /tmp/dagger-engine.tar
|
|
23
|
+
key: dagger-engine-v0.20.3
|
|
24
|
+
|
|
25
|
+
- name: Load Dagger engine image
|
|
26
|
+
if: steps.cache-dagger.outputs.cache-hit == 'true'
|
|
27
|
+
run: docker load -i /tmp/dagger-engine.tar
|
|
28
|
+
|
|
29
|
+
- name: Run CI pipeline
|
|
30
|
+
uses: dagger/dagger-for-github@v8.4.0
|
|
31
|
+
with:
|
|
32
|
+
version: "0.20.3"
|
|
33
|
+
call: check --source=.
|
|
34
|
+
|
|
35
|
+
- name: Save Dagger engine image
|
|
36
|
+
if: steps.cache-dagger.outputs.cache-hit != 'true'
|
|
37
|
+
run: docker save registry.dagger.io/engine:v0.20.3 -o /tmp/dagger-engine.tar
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: Documentation
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches:
|
|
5
|
+
- master
|
|
6
|
+
- main
|
|
7
|
+
concurrency:
|
|
8
|
+
group: "pages"
|
|
9
|
+
cancel-in-progress: false
|
|
10
|
+
env:
|
|
11
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
pages: write
|
|
15
|
+
id-token: write
|
|
16
|
+
jobs:
|
|
17
|
+
deploy:
|
|
18
|
+
environment:
|
|
19
|
+
name: github-pages
|
|
20
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/configure-pages@v5
|
|
24
|
+
- uses: actions/checkout@v6
|
|
25
|
+
- uses: actions/setup-python@v6
|
|
26
|
+
with:
|
|
27
|
+
python-version: 3.14
|
|
28
|
+
- run: pip install zensical "mkdocstrings[python]"
|
|
29
|
+
- run: cp README.md docs/index.md
|
|
30
|
+
- run: zensical build --clean
|
|
31
|
+
- uses: actions/upload-pages-artifact@v4
|
|
32
|
+
with:
|
|
33
|
+
path: site
|
|
34
|
+
- uses: actions/deploy-pages@v4
|
|
35
|
+
id: deployment
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Enforce Conventional Commits format on pull request titles.
|
|
2
|
+
#
|
|
3
|
+
# Uses pull_request_target so the workflow runs with repo permissions even for
|
|
4
|
+
# forks. Only pull-requests: read is needed — no write access required.
|
|
5
|
+
|
|
6
|
+
name: PR title
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
pull_request_target:
|
|
10
|
+
types: [opened, reopened, edited, synchronize]
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
lint-pr-title:
|
|
14
|
+
name: Lint PR title
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
if: "!startsWith(github.event.pull_request.head.ref, 'chore/version-bump-')"
|
|
17
|
+
permissions:
|
|
18
|
+
pull-requests: read
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- name: Validate conventional commit format
|
|
22
|
+
uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
|
|
23
|
+
env:
|
|
24
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Publish sheridan-diffract to PyPI on version tag push.
|
|
2
|
+
#
|
|
3
|
+
# Prerequisites — register this repo as a trusted publisher on pypi.org:
|
|
4
|
+
# pypi.org → Account Settings → Publishing → Add a new pending publisher
|
|
5
|
+
# Owner: andrewasheridan
|
|
6
|
+
# Repository: diffract
|
|
7
|
+
# Workflow: publish.yaml
|
|
8
|
+
# Environment: pypi (must match the `environment:` name on the publish job)
|
|
9
|
+
#
|
|
10
|
+
# Once registered, PyPI trusts GitHub's OIDC token — no API token secrets needed.
|
|
11
|
+
#
|
|
12
|
+
# Triggered by tag-on-merge.yaml pushing a v* tag. Using push.tags (not
|
|
13
|
+
# workflow_call) so GitHub issues a first-party OIDC token — workflow_call
|
|
14
|
+
# produces a reusable-workflow token that PyPI's trusted publisher rejects.
|
|
15
|
+
|
|
16
|
+
name: Publish
|
|
17
|
+
|
|
18
|
+
on:
|
|
19
|
+
push:
|
|
20
|
+
tags:
|
|
21
|
+
- "v*"
|
|
22
|
+
workflow_dispatch: # Allows manual re-runs via gh CLI or UI
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
build:
|
|
26
|
+
name: Build distribution
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
permissions:
|
|
29
|
+
contents: read
|
|
30
|
+
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v6
|
|
33
|
+
|
|
34
|
+
- uses: actions/setup-python@v6
|
|
35
|
+
with:
|
|
36
|
+
python-version: "3.14"
|
|
37
|
+
|
|
38
|
+
- uses: astral-sh/setup-uv@v7
|
|
39
|
+
|
|
40
|
+
- name: Build sdist and wheel
|
|
41
|
+
run: uv build
|
|
42
|
+
|
|
43
|
+
- name: Upload dist artifacts
|
|
44
|
+
uses: actions/upload-artifact@v4
|
|
45
|
+
with:
|
|
46
|
+
name: dist
|
|
47
|
+
path: dist/
|
|
48
|
+
|
|
49
|
+
publish:
|
|
50
|
+
name: Publish to PyPI
|
|
51
|
+
needs: build
|
|
52
|
+
runs-on: ubuntu-latest
|
|
53
|
+
environment:
|
|
54
|
+
name: pypi
|
|
55
|
+
url: https://pypi.org/p/sheridan-diffract
|
|
56
|
+
permissions:
|
|
57
|
+
contents: read
|
|
58
|
+
id-token: write # required for OIDC trusted publishing
|
|
59
|
+
|
|
60
|
+
steps:
|
|
61
|
+
- name: Download dist artifacts
|
|
62
|
+
uses: actions/download-artifact@v8
|
|
63
|
+
with:
|
|
64
|
+
name: dist
|
|
65
|
+
path: dist/
|
|
66
|
+
|
|
67
|
+
- name: Publish to PyPI
|
|
68
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# When a version-bump PR is squash-merged into main, create an annotated tag on
|
|
2
|
+
# the merge commit. publish.yaml listens for the resulting tag push and handles
|
|
3
|
+
# the PyPI release — workflow_call cannot be used because it produces a
|
|
4
|
+
# reusable-workflow OIDC token that PyPI's trusted publisher rejects.
|
|
5
|
+
#
|
|
6
|
+
# RELEASE_TOKEN is passed to actions/checkout so that checkout's git credential
|
|
7
|
+
# helper is wired to the PAT rather than GITHUB_TOKEN. Without this, the helper
|
|
8
|
+
# installed by checkout overrides any credential embedded in the remote URL, the
|
|
9
|
+
# push is attributed to GITHUB_TOKEN, and GitHub suppresses the push.tags event
|
|
10
|
+
# that publish.yaml depends on.
|
|
11
|
+
|
|
12
|
+
name: Tag on merge
|
|
13
|
+
|
|
14
|
+
on:
|
|
15
|
+
pull_request:
|
|
16
|
+
types: [closed]
|
|
17
|
+
branches: [main]
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
tag-merge-commit:
|
|
21
|
+
name: Tag merge commit
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'chore/version-bump-')
|
|
24
|
+
permissions:
|
|
25
|
+
contents: write
|
|
26
|
+
|
|
27
|
+
steps:
|
|
28
|
+
- name: Checkout
|
|
29
|
+
uses: actions/checkout@v6
|
|
30
|
+
with:
|
|
31
|
+
fetch-depth: 0
|
|
32
|
+
token: ${{ secrets.RELEASE_TOKEN }} # wire credential helper to PAT so tag push triggers publish.yaml
|
|
33
|
+
|
|
34
|
+
- name: Configure git identity
|
|
35
|
+
run: |
|
|
36
|
+
git config user.name "github-actions[bot]"
|
|
37
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
38
|
+
|
|
39
|
+
- name: Extract version from branch name
|
|
40
|
+
id: extract
|
|
41
|
+
run: |
|
|
42
|
+
BRANCH="${{ github.event.pull_request.head.ref }}"
|
|
43
|
+
VERSION="${BRANCH#chore/version-bump-}"
|
|
44
|
+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
45
|
+
|
|
46
|
+
- name: Create and push tag
|
|
47
|
+
env:
|
|
48
|
+
VERSION: ${{ steps.extract.outputs.version }}
|
|
49
|
+
run: |
|
|
50
|
+
git tag -a "v$VERSION" "${{ github.event.pull_request.merge_commit_sha }}" -m "Version $VERSION"
|
|
51
|
+
git push origin "v$VERSION"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.pyo
|
|
5
|
+
*.pyd
|
|
6
|
+
*.so
|
|
7
|
+
|
|
8
|
+
# Distribution / packaging
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
*.egg-info/
|
|
12
|
+
*.egg
|
|
13
|
+
|
|
14
|
+
# Virtual environments
|
|
15
|
+
.venv/
|
|
16
|
+
venv/
|
|
17
|
+
|
|
18
|
+
# Testing & coverage
|
|
19
|
+
.coverage
|
|
20
|
+
.coverage.*
|
|
21
|
+
htmlcov/
|
|
22
|
+
.pytest_cache/
|
|
23
|
+
|
|
24
|
+
# Type checking
|
|
25
|
+
.mypy_cache/
|
|
26
|
+
|
|
27
|
+
# uv
|
|
28
|
+
uv.lock
|
|
29
|
+
|
|
30
|
+
# Editors
|
|
31
|
+
.idea/
|
|
32
|
+
.vscode/
|
|
33
|
+
*.swp
|
|
34
|
+
*.swo
|
|
35
|
+
|
|
36
|
+
# macOS
|
|
37
|
+
.DS_Store
|
|
38
|
+
|
|
39
|
+
# Claude
|
|
40
|
+
.claude/worktrees/
|
|
41
|
+
.claude/settings.local.json
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.15.7
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff-check
|
|
6
|
+
args: [--fix]
|
|
7
|
+
- id: ruff-format
|
|
8
|
+
|
|
9
|
+
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
10
|
+
rev: v1.19.1
|
|
11
|
+
hooks:
|
|
12
|
+
- id: mypy
|
|
13
|
+
args: [--strict]
|
|
14
|
+
files: ^src/
|
|
15
|
+
additional_dependencies: [gitpython, sheridan-iceberg]
|
|
16
|
+
|
|
17
|
+
- repo: https://github.com/andrewasheridan/diffract
|
|
18
|
+
rev: v1.0.1 # diffract
|
|
19
|
+
hooks:
|
|
20
|
+
- id: diffract-validate
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Pre-commit hook definitions for sheridan-diffract.
|
|
2
|
+
#
|
|
3
|
+
# To use these hooks in another project, add the following to your
|
|
4
|
+
# .pre-commit-config.yaml:
|
|
5
|
+
#
|
|
6
|
+
# repos:
|
|
7
|
+
# - repo: https://github.com/sheridan/diffract
|
|
8
|
+
# rev: v<VERSION>
|
|
9
|
+
# hooks:
|
|
10
|
+
# - id: diffract-validate
|
|
11
|
+
|
|
12
|
+
- id: diffract-validate
|
|
13
|
+
name: diffract (validate commit message type)
|
|
14
|
+
description: >
|
|
15
|
+
Validates that the conventional commit type in the commit message matches
|
|
16
|
+
the API change diffract detects. Exits 0 on match or unrecognised type;
|
|
17
|
+
exits 1 on mismatch. Non-conventional commits (docs:, chore:, etc.) are
|
|
18
|
+
never blocked. Configure the source directory via diffract.toml or
|
|
19
|
+
pyproject.toml [tool.diffract] rather than hook args.
|
|
20
|
+
language: python
|
|
21
|
+
entry: diffract --validate-msg-file
|
|
22
|
+
pass_filenames: true
|
|
23
|
+
stages: [commit-msg]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `sheridan-diffract` will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## ['0.1.0'] - Initial release
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Initial project structure
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# CLAUDE.md — sheridan-diffract
|
|
2
|
+
|
|
3
|
+
## Project overview
|
|
4
|
+
|
|
5
|
+
`sheridan-diffract` detects how a Python package's public API changed between two git commits and classifies that change as a conventional commit type. It has no AST logic of its own — it delegates API surface extraction entirely to `sheridan-iceberg`.
|
|
6
|
+
|
|
7
|
+
## Commands
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
task check # full gate: lint, format, typecheck, tests (run before every commit)
|
|
11
|
+
task lint # ruff check --fix
|
|
12
|
+
task lint:check # ruff check (read-only, used in CI)
|
|
13
|
+
task format # ruff format
|
|
14
|
+
task format:check # ruff format --check (read-only, used in CI)
|
|
15
|
+
task typecheck # mypy --strict src/
|
|
16
|
+
task test # pytest --cov (coverage ≥ 90% required)
|
|
17
|
+
task install # uv sync --all-extras --dev
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Architecture
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
src/sheridan/diffract/
|
|
24
|
+
├── __init__.py # Public re-exports; __version__ from importlib.metadata
|
|
25
|
+
├── enums.py # CommitType, ChangeKind (StrEnum)
|
|
26
|
+
├── exceptions.py # DiffractError → GitError / SurfaceError
|
|
27
|
+
├── models.py # NameChange, ApiDiff, DiffractResult (frozen dataclasses)
|
|
28
|
+
├── differ.py # diff_surfaces() — set-diff two iceberg API surfaces
|
|
29
|
+
├── classifier.py # classify() — maps ApiDiff → CommitType + summary
|
|
30
|
+
├── git_utils.py # get_repo(), get_api_at_ref(), has_python_changes(), get_api_at_index(), has_python_changes_index()
|
|
31
|
+
├── checker.py # check() — orchestrates git → iceberg → diff → classify; check_staged() — HEAD vs index
|
|
32
|
+
├── config.py # load_config() — DiffractConfig from diffract.toml / pyproject.toml
|
|
33
|
+
└── cli.py # argparse CLI: `diffract [BASE] [HEAD] [--src] [--json] [--exit-code] [--validate-msg-file]`
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Key dependency: sheridan-iceberg
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from sheridan.iceberg import get_public_api
|
|
40
|
+
# get_public_api(path: Path | str) -> dict[str, list[str]]
|
|
41
|
+
# Returns: {"module.qualified.name": ["PublicName1", "ClassFoo", ...], ...}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`diffract` calls `get_public_api` twice (once per ref), then diffs the two dicts.
|
|
45
|
+
|
|
46
|
+
### Classification rules (in priority order)
|
|
47
|
+
|
|
48
|
+
1. Any public name **removed** → `feat!` (breaking)
|
|
49
|
+
2. Any public name **added** (no removals) → `feat`
|
|
50
|
+
3. No name changes, but `.py` files changed → `refactor`
|
|
51
|
+
4. No changes detected → `fix`
|
|
52
|
+
|
|
53
|
+
## Code conventions
|
|
54
|
+
|
|
55
|
+
- **Python ≥ 3.14** — use modern syntax freely (`X | Y`, `match`, etc.)
|
|
56
|
+
- **`from __future__ import annotations`** at top of every file
|
|
57
|
+
- **`__all__`** declared in every module
|
|
58
|
+
- **Frozen dataclasses** for all result/model types
|
|
59
|
+
- **`StrEnum`** for enumerations
|
|
60
|
+
- **Google-style docstrings** on all public functions, classes, and methods
|
|
61
|
+
- **Line length**: 120 characters (ruff enforced)
|
|
62
|
+
- **Import order**: stdlib → third-party → local (isort via ruff)
|
|
63
|
+
|
|
64
|
+
## Testing conventions
|
|
65
|
+
|
|
66
|
+
- **pytest** only — never `unittest.TestCase`
|
|
67
|
+
- **pytest-mock** for patching: use the `mocker` fixture, not `unittest.mock.patch`
|
|
68
|
+
- **`monkeypatch`** for environment/`sys.argv` modification
|
|
69
|
+
- **No `MagicMock` as fixture arguments** — construct them inline with `MagicMock()`
|
|
70
|
+
- Shared fixtures live in `tests/conftest.py`
|
|
71
|
+
- One test file per source module: `test_<module>.py`
|
|
72
|
+
- Coverage threshold: **≥ 90%** (enforced by pytest-cov)
|
|
73
|
+
|
|
74
|
+
## Dependencies
|
|
75
|
+
|
|
76
|
+
- **Runtime**: `sheridan-iceberg`, `gitpython`
|
|
77
|
+
- **Dev**: `ruff`, `mypy`, `pytest`, `pytest-cov`, `pytest-mock`, `commitizen`, `pre-commit`, `bandit`, `zensical`, `mkdocstrings`
|
|
78
|
+
|
|
79
|
+
### Adding dependencies
|
|
80
|
+
|
|
81
|
+
Always run the dependency auditor before adding a new runtime dependency. Context matters: the automated auditor rejected `gitpython` on security grounds, but human review approved it (see ADR-002) because the CVEs are patched, inputs are controlled, and it handles git hook environment variables correctly.
|
|
82
|
+
|
|
83
|
+
## Commit format (Conventional Commits via commitizen)
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
feat: add new public API
|
|
87
|
+
feat!: remove or rename a public API (breaking)
|
|
88
|
+
fix: correct a bug with no API change
|
|
89
|
+
refactor: internal restructuring, no API change
|
|
90
|
+
docs: documentation only
|
|
91
|
+
test: add or update tests
|
|
92
|
+
chore: maintenance (deps, config, tooling)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## GitHub Actions workflows
|
|
96
|
+
|
|
97
|
+
Workflow files live in `.github/workflows/` and use the **`.yaml` extension** (not `.yml`).
|
|
98
|
+
|
|
99
|
+
## Architecture Decision Records
|
|
100
|
+
|
|
101
|
+
Significant decisions are documented in `docs/decisions/`. Use the `adr` skill to add new ones.
|
|
102
|
+
|
|
103
|
+
## Configuration
|
|
104
|
+
|
|
105
|
+
diffract reads `src` from the first config file found in the repo root:
|
|
106
|
+
|
|
107
|
+
| File | Key |
|
|
108
|
+
|---|---|
|
|
109
|
+
| `diffract.toml` | `src = "python/src"` |
|
|
110
|
+
| `pyproject.toml` | `[tool.diffract]` → `src = "python/src"` |
|
|
111
|
+
|
|
112
|
+
If neither file is present, `--src` CLI flag is used, falling back to `src/`.
|
|
113
|
+
|
|
114
|
+
Priority: `--src` flag > `diffract.toml` > `pyproject.toml` > default (`src/`)
|
|
115
|
+
|
|
116
|
+
## CLI flags
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
diffract [BASE] [HEAD] # defaults: HEAD~1, HEAD
|
|
120
|
+
--src PATH # source directory (default: src); use --src . for flat layouts
|
|
121
|
+
--json # output JSON instead of human-readable text
|
|
122
|
+
--exit-code # exit 1 (breaking), 2 (any API change), 0 (no change)
|
|
123
|
+
--validate-msg-file MSGFILE # validate conventional commit type against detected API change
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
When `--validate-msg-file` is used **without** explicit `BASE`/`HEAD` refs (the standard commit-msg hook invocation), diffract automatically compares `HEAD` against the **git staging area** rather than `HEAD~1 → HEAD`. This ensures the hook validates what is actually being committed, not what was committed last. Pass explicit refs to override (e.g. for CI use with `diffract base.sha head.sha --validate-msg-file …`).
|
|
127
|
+
|
|
128
|
+
## pre-commit hooks
|
|
129
|
+
|
|
130
|
+
One hook is exported from `.pre-commit-hooks.yaml`:
|
|
131
|
+
|
|
132
|
+
| Hook id | Stage | Purpose |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| `diffract-validate` | commit-msg | Fails if the commit message type doesn't match the detected API change |
|
|
135
|
+
|
|
136
|
+
The hook reads `src` from `diffract.toml` or `pyproject.toml` automatically:
|
|
137
|
+
|
|
138
|
+
```yaml
|
|
139
|
+
- id: diffract-validate
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## What diffract does NOT do
|
|
143
|
+
|
|
144
|
+
- No AST logic — all API surface extraction is delegated to `sheridan-iceberg`
|
|
145
|
+
- No signature diffing — only name-level additions/removals are detected
|
|
146
|
+
- No automatic commit rewriting — diffract only reports and optionally fails CI
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Contributing to sheridan-diffract
|
|
2
|
+
|
|
3
|
+
Thank you for considering a contribution!
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
task install
|
|
9
|
+
pre-commit install
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Workflow
|
|
13
|
+
|
|
14
|
+
1. Create a feature branch from `main`
|
|
15
|
+
2. Write your code and tests (coverage must remain ≥ 90%)
|
|
16
|
+
3. Run `task check` — all gates must pass
|
|
17
|
+
4. Commit using [Conventional Commits](https://www.conventionalcommits.org/) format
|
|
18
|
+
5. Open a pull request
|
|
19
|
+
|
|
20
|
+
## Running checks
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
task check # all gates: lint, format, typecheck, test
|
|
24
|
+
task lint # ruff check + autofix
|
|
25
|
+
task format # ruff format
|
|
26
|
+
task typecheck # mypy --strict
|
|
27
|
+
task test # pytest with coverage
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Commit format
|
|
31
|
+
|
|
32
|
+
This project uses [Commitizen](https://commitizen-tools.github.io/commitizen/) with Conventional Commits:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
feat: add new feature
|
|
36
|
+
fix: correct a bug
|
|
37
|
+
docs: update documentation
|
|
38
|
+
chore: maintenance tasks
|
|
39
|
+
refactor: code restructuring without behavior change
|
|
40
|
+
test: add or update tests
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Adding dependencies
|
|
44
|
+
|
|
45
|
+
- Propose new runtime dependencies in a PR with a clear justification
|
|
46
|
+
- Keep the runtime dependency list minimal
|
|
47
|
+
- Dev dependencies go under `[project.optional-dependencies] dev`
|
|
48
|
+
|
|
49
|
+
## Architecture Decision Records
|
|
50
|
+
|
|
51
|
+
Significant decisions are recorded as ADRs in `docs/decisions/`.
|
|
52
|
+
Use the `docs/decisions/` directory to document any non-obvious choices.
|
|
53
|
+
|
|
54
|
+
## CI pipeline
|
|
55
|
+
|
|
56
|
+
The full CI pipeline runs via Dagger:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
task ci-init # first time only
|
|
60
|
+
task ci # run all gates in containers
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Individual gates: `task lint:check`, `task format:check`, `task typecheck`, `task test`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 sheridan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|