msw-io 1.0.2__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.
- msw_io-1.0.2/.copier-answers.yml +15 -0
- msw_io-1.0.2/.forgejo/workflows/ci.yml +77 -0
- msw_io-1.0.2/.forgejo/workflows/docs.yml +33 -0
- msw_io-1.0.2/.forgejo/workflows/pr-review.yml +105 -0
- msw_io-1.0.2/.forgejo/workflows/release.yml +64 -0
- msw_io-1.0.2/.githooks/fix_commit_msg.py +52 -0
- msw_io-1.0.2/.github/ISSUE_TEMPLATE/bug_report.md +32 -0
- msw_io-1.0.2/.github/ISSUE_TEMPLATE/feature_request.md +21 -0
- msw_io-1.0.2/.github/PULL_REQUEST_TEMPLATE.md +18 -0
- msw_io-1.0.2/.github/workflows/ci.yml +72 -0
- msw_io-1.0.2/.github/workflows/docs.yml +31 -0
- msw_io-1.0.2/.github/workflows/pr-review.yml +96 -0
- msw_io-1.0.2/.github/workflows/release.yml +73 -0
- msw_io-1.0.2/.gitignore +72 -0
- msw_io-1.0.2/.gitleaks.toml +6 -0
- msw_io-1.0.2/.gitlint +2 -0
- msw_io-1.0.2/.pre-commit-config.yaml +49 -0
- msw_io-1.0.2/CITATION.cff +11 -0
- msw_io-1.0.2/CODE_OF_CONDUCT.md +31 -0
- msw_io-1.0.2/CONTRIBUTING.md +29 -0
- msw_io-1.0.2/LICENSE +0 -0
- msw_io-1.0.2/PKG-INFO +80 -0
- msw_io-1.0.2/README.md +54 -0
- msw_io-1.0.2/VERSION +1 -0
- msw_io-1.0.2/docs/api.md +65 -0
- msw_io-1.0.2/docs/concepts.md +91 -0
- msw_io-1.0.2/docs/getting_started.md +92 -0
- msw_io-1.0.2/docs/index.md +32 -0
- msw_io-1.0.2/mkdocs.yml +44 -0
- msw_io-1.0.2/pyproject.toml +96 -0
- msw_io-1.0.2/src/murineshiftwork/_version.py +24 -0
- msw_io-1.0.2/src/murineshiftwork/io/__init__.py +134 -0
- msw_io-1.0.2/src/murineshiftwork/namespace/__init__.py +16 -0
- msw_io-1.0.2/src/murineshiftwork/namespace/manifest.py +163 -0
- msw_io-1.0.2/src/murineshiftwork/namespace/msw_files.py +57 -0
- msw_io-1.0.2/src/murineshiftwork/namespace/namespace.msw.yaml +25 -0
- msw_io-1.0.2/src/murineshiftwork/namespace/paths.py +219 -0
- msw_io-1.0.2/src/murineshiftwork/namespace/spec.py +5 -0
- msw_io-1.0.2/src/murineshiftwork/readers/__init__.py +5 -0
- msw_io-1.0.2/src/murineshiftwork/readers/alignment.py +325 -0
- msw_io-1.0.2/src/murineshiftwork/readers/batch.py +181 -0
- msw_io-1.0.2/src/murineshiftwork/readers/files.py +108 -0
- msw_io-1.0.2/src/murineshiftwork/readers/models.py +105 -0
- msw_io-1.0.2/src/murineshiftwork/readers/namespace.py +143 -0
- msw_io-1.0.2/src/murineshiftwork/readers/session.py +221 -0
- msw_io-1.0.2/src/murineshiftwork/readers/validate.py +93 -0
- msw_io-1.0.2/tests/conftest.py +1 -0
- msw_io-1.0.2/tests/data/fixture_fixedsubjects/_test_subject__20260518_121602_710824__probabilistic_switching_fixedsubjects.msw.csv +1164 -0
- msw_io-1.0.2/tests/data/fixture_fixedsubjects/_test_subject__20260518_121602_710824__probabilistic_switching_fixedsubjects.msw.df.jsonl +9 -0
- msw_io-1.0.2/tests/data/fixture_fixedsubjects/_test_subject__20260518_121602_710824__probabilistic_switching_fixedsubjects.msw.session.yaml +144 -0
- msw_io-1.0.2/tests/data/fixture_jsonl/subject001__20260508_172956_258756__probabilistic_switching_fixedsubjects.msw.csv +682 -0
- msw_io-1.0.2/tests/data/fixture_jsonl/subject001__20260508_172956_258756__probabilistic_switching_fixedsubjects.msw.df.jsonl +3 -0
- msw_io-1.0.2/tests/data/fixture_jsonl/subject001__20260508_172956_258756__probabilistic_switching_fixedsubjects.msw.settings.process.json +1639 -0
- msw_io-1.0.2/tests/data/fixture_jsonl/subject001__20260508_172956_258756__probabilistic_switching_fixedsubjects.msw.settings.task.json +173 -0
- msw_io-1.0.2/tests/data/fixture_legacy/subject003__20210426_183409__probabilistic_switching/subject003__20210426_183409__probabilistic_switching.pkl +0 -0
- msw_io-1.0.2/tests/data/fixture_legacy/subject003__20210426_183409__probabilistic_switching/subject003__20210426_183409__probabilistic_switching.task_settings.py +86 -0
- msw_io-1.0.2/tests/data/fixture_optotagging/_test_subject__20260527_133053_901389__optotagging/_test_subject__20260527_133053_901389__optotagging.msw.session.yaml +20 -0
- msw_io-1.0.2/tests/data/fixture_optotagging/_test_subject__20260527_133053_901389__optotagging/_test_subject__20260527_133053_901389__optotagging_power_ramp_1mw.msw.df.jsonl +2 -0
- msw_io-1.0.2/tests/data/fixture_optotagging/_test_subject__20260527_133053_901389__optotagging/_test_subject__20260527_133053_901389__optotagging_power_ramp_2mw.msw.csv +19 -0
- msw_io-1.0.2/tests/data/fixture_optotagging/_test_subject__20260527_133053_901389__optotagging/session_manifest.yaml +14 -0
- msw_io-1.0.2/tests/data/fixture_pkl/subject002__20260429_153653__probabilistic_switching_fixedsubjects.msw.csv +124 -0
- msw_io-1.0.2/tests/data/fixture_pkl/subject002__20260429_153653__probabilistic_switching_fixedsubjects.msw.df.pkl +0 -0
- msw_io-1.0.2/tests/data/fixture_pkl/subject002__20260429_153653__probabilistic_switching_fixedsubjects.msw.settings.process.json +1435 -0
- msw_io-1.0.2/tests/data/fixture_pkl/subject002__20260429_153653__probabilistic_switching_fixedsubjects.msw.settings.task.json +165 -0
- msw_io-1.0.2/tests/data/fixture_sequence/_test_subject__20260520_114516_534465__sequence.msw.csv +2460 -0
- msw_io-1.0.2/tests/data/fixture_sequence/_test_subject__20260520_114516_534465__sequence.msw.df.jsonl +66 -0
- msw_io-1.0.2/tests/data/fixture_sequence/_test_subject__20260520_114516_534465__sequence.msw.session.yaml +67 -0
- msw_io-1.0.2/tests/data/fixture_v2/_test_subject__20260516_130611_739182__probabilistic_switching_fixedsubjects.msw.csv +3560 -0
- msw_io-1.0.2/tests/data/fixture_v2/_test_subject__20260516_130611_739182__probabilistic_switching_fixedsubjects.msw.df.jsonl +23 -0
- msw_io-1.0.2/tests/data/fixture_v2/_test_subject__20260516_130611_739182__probabilistic_switching_fixedsubjects.msw.session.yaml +184 -0
- msw_io-1.0.2/tests/data/namespace.v1.yaml +21 -0
- msw_io-1.0.2/tests/data/namespace.v2.yaml +27 -0
- msw_io-1.0.2/tests/data/namespace.v3.yaml +26 -0
- msw_io-1.0.2/tests/test_ephys_barcode_alignment.py +269 -0
- msw_io-1.0.2/tests/test_msw_files.py +171 -0
- msw_io-1.0.2/tests/test_namespace.py +234 -0
- msw_io-1.0.2/tests/test_namespace_builder.py +202 -0
- msw_io-1.0.2/tests/test_namespace_readiness.py +23 -0
- msw_io-1.0.2/tests/test_rce_barcode_alignment.py +165 -0
- msw_io-1.0.2/tests/test_reader_batch.py +335 -0
- msw_io-1.0.2/tests/test_reader_dispatch.py +139 -0
- msw_io-1.0.2/tests/test_reader_fixtures.py +402 -0
- msw_io-1.0.2/tests/test_reader_interface_parity.py +269 -0
- msw_io-1.0.2/tests/test_reader_session_format.py +144 -0
- msw_io-1.0.2/tests/test_reader_task_fixtures.py +263 -0
- msw_io-1.0.2/tests/test_reader_v2.py +102 -0
- msw_io-1.0.2/tests/test_readers_session.py +78 -0
- msw_io-1.0.2/tests/test_readers_validate.py +69 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changes here will be overwritten by Copier
|
|
2
|
+
_commit: v0.8.5
|
|
3
|
+
_src_path: gh:larsrollik/templatepy
|
|
4
|
+
author_email: lars@rollik.me
|
|
5
|
+
author_name: Lars B. Rollik
|
|
6
|
+
github_repo: msw-io
|
|
7
|
+
github_username: murineshiftwork
|
|
8
|
+
license_type: noncommercial
|
|
9
|
+
private_repo_deps: false
|
|
10
|
+
project_description: 'Murine Shift Work session data IO: file codec, namespace utilities, and session readers.'
|
|
11
|
+
project_name: msw-io
|
|
12
|
+
project_slug: msw_io
|
|
13
|
+
python_requires: '3.10'
|
|
14
|
+
year: '2026'
|
|
15
|
+
test_on_windows: false
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
tags-ignore: ["v*"]
|
|
7
|
+
pull_request:
|
|
8
|
+
branches: [main]
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
lint:
|
|
15
|
+
name: Lint
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
if: "!startsWith(github.event.head_commit.message, 'bump:')"
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
with:
|
|
21
|
+
fetch-depth: 0
|
|
22
|
+
|
|
23
|
+
- uses: astral-sh/setup-uv@v7
|
|
24
|
+
with:
|
|
25
|
+
python-version: "3.12"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
- name: Install dependencies
|
|
29
|
+
run: uv sync --extra dev
|
|
30
|
+
|
|
31
|
+
- name: Run pre-commit (ruff + mypy + commitizen + gitleaks)
|
|
32
|
+
run: uv run pre-commit run --all-files --show-diff-on-failure
|
|
33
|
+
|
|
34
|
+
test:
|
|
35
|
+
name: Test (Python ${{ matrix.python-version }})
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
if: github.event_name == 'pull_request'
|
|
38
|
+
strategy:
|
|
39
|
+
fail-fast: false
|
|
40
|
+
matrix:
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
python-version: ["3.10", "3.13"]
|
|
44
|
+
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/checkout@v4
|
|
47
|
+
with:
|
|
48
|
+
fetch-depth: 0
|
|
49
|
+
|
|
50
|
+
- uses: astral-sh/setup-uv@v7
|
|
51
|
+
with:
|
|
52
|
+
python-version: ${{ matrix.python-version }}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
- name: Install dependencies
|
|
56
|
+
run: uv sync --extra dev
|
|
57
|
+
|
|
58
|
+
- name: Run tests
|
|
59
|
+
run: uv run pytest
|
|
60
|
+
|
|
61
|
+
# Aggregate gate — add as required status check in branch protection
|
|
62
|
+
ci:
|
|
63
|
+
name: CI
|
|
64
|
+
runs-on: ubuntu-latest
|
|
65
|
+
needs: [lint, test]
|
|
66
|
+
if: always()
|
|
67
|
+
steps:
|
|
68
|
+
- name: Check required jobs
|
|
69
|
+
run: |
|
|
70
|
+
|
|
71
|
+
if [[ "${{ needs.lint.result }}" != "success" && "${{ needs.lint.result }}" != "skipped" ]]; then
|
|
72
|
+
echo "lint failed" && exit 1
|
|
73
|
+
fi
|
|
74
|
+
if [[ "${{ needs.test.result }}" != "success" && "${{ needs.test.result }}" != "skipped" ]]; then
|
|
75
|
+
echo "test failed" && exit 1
|
|
76
|
+
fi
|
|
77
|
+
echo "All required checks passed."
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths:
|
|
7
|
+
- "docs/**"
|
|
8
|
+
- "mkdocs.yml"
|
|
9
|
+
- ".forgejo/workflows/docs.yml"
|
|
10
|
+
- "src/**/*.py"
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: write
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
deploy:
|
|
18
|
+
name: Deploy docs to Pages
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
with:
|
|
23
|
+
fetch-depth: 0
|
|
24
|
+
|
|
25
|
+
- uses: astral-sh/setup-uv@v7
|
|
26
|
+
with:
|
|
27
|
+
python-version: "3.12"
|
|
28
|
+
|
|
29
|
+
- name: Install dependencies
|
|
30
|
+
run: uv sync --extra docs
|
|
31
|
+
|
|
32
|
+
- name: Deploy
|
|
33
|
+
run: uv run mkdocs gh-deploy --force
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
name: PR Review (optional LLM)
|
|
2
|
+
|
|
3
|
+
# Optional AI-assisted pull request review.
|
|
4
|
+
# Uncomment ONE of the sections below and configure the required secret.
|
|
5
|
+
#
|
|
6
|
+
# Option A: Claude API requires ANTHROPIC_API_KEY secret.
|
|
7
|
+
# Option B: Ollama requires a self-hosted runner with Ollama on localhost:11434.
|
|
8
|
+
#
|
|
9
|
+
# This workflow has a no-op placeholder job so it stays valid while
|
|
10
|
+
# both options remain commented out.
|
|
11
|
+
|
|
12
|
+
on:
|
|
13
|
+
pull_request:
|
|
14
|
+
types: [opened, synchronize, ready_for_review]
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
|
|
18
|
+
# ── Option A: Claude API (Anthropic) ─────────────────────────────────────
|
|
19
|
+
# Requires secret: ANTHROPIC_API_KEY
|
|
20
|
+
#
|
|
21
|
+
# llm-review-claude:
|
|
22
|
+
# name: Claude PR review
|
|
23
|
+
# runs-on: ubuntu-latest
|
|
24
|
+
# steps:
|
|
25
|
+
# - uses: actions/checkout@v4
|
|
26
|
+
# with:
|
|
27
|
+
# fetch-depth: 0
|
|
28
|
+
#
|
|
29
|
+
# - name: Generate diff
|
|
30
|
+
# id: diff
|
|
31
|
+
# run: |
|
|
32
|
+
# DIFF=$(git diff origin/main...HEAD -- '*.py' | head -c 8000)
|
|
33
|
+
# echo "content<<EOF" >> "$GITHUB_OUTPUT"
|
|
34
|
+
# echo "$DIFF" >> "$GITHUB_OUTPUT"
|
|
35
|
+
# echo "EOF" >> "$GITHUB_OUTPUT"
|
|
36
|
+
#
|
|
37
|
+
# - name: Review with Claude
|
|
38
|
+
# env:
|
|
39
|
+
# ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
40
|
+
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
41
|
+
# PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
42
|
+
# run: |
|
|
43
|
+
# if [ -z "$ANTHROPIC_API_KEY" ]; then
|
|
44
|
+
# echo "ANTHROPIC_API_KEY not set — skipping."
|
|
45
|
+
# exit 0
|
|
46
|
+
# fi
|
|
47
|
+
# REVIEW=$(curl -s https://api.anthropic.com/v1/messages \
|
|
48
|
+
# -H "x-api-key: $ANTHROPIC_API_KEY" \
|
|
49
|
+
# -H "anthropic-version: 2023-06-01" \
|
|
50
|
+
# -H "content-type: application/json" \
|
|
51
|
+
# -d "{
|
|
52
|
+
# \"model\": \"claude-sonnet-4-6\",
|
|
53
|
+
# \"max_tokens\": 1024,
|
|
54
|
+
# \"messages\": [{
|
|
55
|
+
# \"role\": \"user\",
|
|
56
|
+
# \"content\": \"Review this Python PR diff for bugs, style issues, and improvements. Be concise.\n\n${{ steps.diff.outputs.content }}\"
|
|
57
|
+
# }]
|
|
58
|
+
# }" | python3 -c "import sys,json; print(json.load(sys.stdin)['content'][0]['text'])")
|
|
59
|
+
# curl -s -X POST \
|
|
60
|
+
# "${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" \
|
|
61
|
+
# -H "Authorization: token ${GITHUB_TOKEN}" \
|
|
62
|
+
# -H "Content-Type: application/json" \
|
|
63
|
+
# -d "{\"body\": \"## Claude PR Review\n\n${REVIEW}\"}"
|
|
64
|
+
|
|
65
|
+
# ── Option B: Local Ollama (self-hosted runner) ───────────────────────────
|
|
66
|
+
# Requires: a self-hosted runner with Ollama running on localhost:11434
|
|
67
|
+
#
|
|
68
|
+
# llm-review-ollama:
|
|
69
|
+
# name: Ollama PR review
|
|
70
|
+
# runs-on: self-hosted
|
|
71
|
+
# steps:
|
|
72
|
+
# - uses: actions/checkout@v4
|
|
73
|
+
# with:
|
|
74
|
+
# fetch-depth: 0
|
|
75
|
+
#
|
|
76
|
+
# - name: Generate diff
|
|
77
|
+
# id: diff
|
|
78
|
+
# run: |
|
|
79
|
+
# DIFF=$(git diff origin/main...HEAD -- '*.py' | head -c 4000)
|
|
80
|
+
# echo "content<<EOF" >> "$GITHUB_OUTPUT"
|
|
81
|
+
# echo "$DIFF" >> "$GITHUB_OUTPUT"
|
|
82
|
+
# echo "EOF" >> "$GITHUB_OUTPUT"
|
|
83
|
+
#
|
|
84
|
+
# - name: Review with Ollama
|
|
85
|
+
# env:
|
|
86
|
+
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
87
|
+
# PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
88
|
+
# OLLAMA_MODEL: "llama3.2"
|
|
89
|
+
# run: |
|
|
90
|
+
# PROMPT="Review this Python PR diff for bugs, style issues, and improvements. Be concise.\n\n${{ steps.diff.outputs.content }}"
|
|
91
|
+
# REVIEW=$(curl -s http://localhost:11434/api/generate \
|
|
92
|
+
# -d "{\"model\": \"${OLLAMA_MODEL}\", \"prompt\": \"${PROMPT}\", \"stream\": false}" \
|
|
93
|
+
# | python3 -c "import sys,json; print(json.load(sys.stdin)['response'])")
|
|
94
|
+
# curl -s -X POST \
|
|
95
|
+
# "${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" \
|
|
96
|
+
# -H "Authorization: token ${GITHUB_TOKEN}" \
|
|
97
|
+
# -H "Content-Type: application/json" \
|
|
98
|
+
# -d "{\"body\": \"## Ollama PR Review (${OLLAMA_MODEL})\n\n${REVIEW}\"}"
|
|
99
|
+
|
|
100
|
+
# Placeholder — remove once an option above is uncommented
|
|
101
|
+
no-op:
|
|
102
|
+
name: LLM review (not configured)
|
|
103
|
+
runs-on: ubuntu-latest
|
|
104
|
+
steps:
|
|
105
|
+
- run: echo "LLM PR review not configured. See .forgejo/workflows/pr-review.yml."
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
release:
|
|
13
|
+
name: Bump, build, publish
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
if: "!startsWith(github.event.head_commit.message, 'bump:')"
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
with:
|
|
19
|
+
fetch-depth: 0
|
|
20
|
+
|
|
21
|
+
- name: Configure git identity
|
|
22
|
+
run: |
|
|
23
|
+
git config user.name "forgejo-actions[bot]"
|
|
24
|
+
git config user.email "forgejo-actions[bot]@noreply"
|
|
25
|
+
|
|
26
|
+
- uses: astral-sh/setup-uv@v7
|
|
27
|
+
with:
|
|
28
|
+
python-version: "3.12"
|
|
29
|
+
|
|
30
|
+
- name: Bump version
|
|
31
|
+
id: bump
|
|
32
|
+
run: |
|
|
33
|
+
uvx --from commitizen cz bump --yes || EXIT=$?
|
|
34
|
+
if [ "${EXIT:-0}" -eq 21 ] || [ "${EXIT:-0}" -eq 6 ]; then
|
|
35
|
+
echo "No bumpable commits since last tag — skipping."
|
|
36
|
+
echo "skipped=true" >> "$GITHUB_OUTPUT"
|
|
37
|
+
exit 0
|
|
38
|
+
fi
|
|
39
|
+
[ "${EXIT:-0}" -eq 0 ] || exit $EXIT
|
|
40
|
+
VERSION=$(cat VERSION)
|
|
41
|
+
git push origin HEAD:main
|
|
42
|
+
git push origin "v${VERSION}"
|
|
43
|
+
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
44
|
+
|
|
45
|
+
- name: Build
|
|
46
|
+
if: steps.bump.outputs.skipped != 'true'
|
|
47
|
+
run: uv build
|
|
48
|
+
|
|
49
|
+
- name: Create Forgejo release
|
|
50
|
+
if: steps.bump.outputs.skipped != 'true'
|
|
51
|
+
env:
|
|
52
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
53
|
+
VERSION: ${{ steps.bump.outputs.version }}
|
|
54
|
+
run: |
|
|
55
|
+
curl -s -X POST "${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases" \
|
|
56
|
+
-H "Authorization: token ${GITHUB_TOKEN}" \
|
|
57
|
+
-H "Content-Type: application/json" \
|
|
58
|
+
-d "{\"tag_name\": \"v${VERSION}\", \"name\": \"v${VERSION}\", \"body\": \"Release v${VERSION}\"}"
|
|
59
|
+
|
|
60
|
+
- name: Publish to PyPI
|
|
61
|
+
if: steps.bump.outputs.skipped != 'true'
|
|
62
|
+
env:
|
|
63
|
+
UV_PUBLISH_TOKEN: ${{ secrets.UV_PUBLISH_TOKEN }}
|
|
64
|
+
run: uv publish
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Auto-fix mechanical commit message formatting issues before linting."""
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def fix(path: Path) -> None:
|
|
9
|
+
text = path.read_text()
|
|
10
|
+
lines = text.splitlines(keepends=True)
|
|
11
|
+
|
|
12
|
+
fixed = []
|
|
13
|
+
for i, line in enumerate(lines):
|
|
14
|
+
if line.startswith("#"):
|
|
15
|
+
fixed.append(line)
|
|
16
|
+
continue
|
|
17
|
+
|
|
18
|
+
# Strip trailing whitespace on every line
|
|
19
|
+
line = line.rstrip() + "\n"
|
|
20
|
+
|
|
21
|
+
# Strip leading whitespace from subject line (first content line)
|
|
22
|
+
if i == 0 or all(ln.startswith("#") for ln in lines[:i]):
|
|
23
|
+
line = line.lstrip()
|
|
24
|
+
|
|
25
|
+
# Convert hard tabs to four spaces
|
|
26
|
+
line = line.replace("\t", " ")
|
|
27
|
+
|
|
28
|
+
fixed.append(line)
|
|
29
|
+
|
|
30
|
+
# Ensure blank line between subject and body if body is present
|
|
31
|
+
first = next((i for i, ln in enumerate(fixed) if not ln.startswith("#")), None)
|
|
32
|
+
if first is not None and first + 1 < len(fixed):
|
|
33
|
+
next_content = next(
|
|
34
|
+
(
|
|
35
|
+
i
|
|
36
|
+
for i, ln in enumerate(fixed[first + 1 :], first + 1)
|
|
37
|
+
if not ln.startswith("#")
|
|
38
|
+
),
|
|
39
|
+
None,
|
|
40
|
+
)
|
|
41
|
+
if (
|
|
42
|
+
next_content is not None
|
|
43
|
+
and next_content == first + 1
|
|
44
|
+
and fixed[first + 1].strip()
|
|
45
|
+
):
|
|
46
|
+
fixed.insert(first + 1, "\n")
|
|
47
|
+
|
|
48
|
+
path.write_text("".join(fixed))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
if __name__ == "__main__":
|
|
52
|
+
fix(Path(sys.argv[1]))
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Something isn't working
|
|
4
|
+
labels: bug
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Description
|
|
8
|
+
|
|
9
|
+
_Clear description of the bug._
|
|
10
|
+
|
|
11
|
+
## Steps to reproduce
|
|
12
|
+
|
|
13
|
+
1. ...
|
|
14
|
+
2. ...
|
|
15
|
+
|
|
16
|
+
## Expected behaviour
|
|
17
|
+
|
|
18
|
+
_What should happen._
|
|
19
|
+
|
|
20
|
+
## Actual behaviour
|
|
21
|
+
|
|
22
|
+
_What actually happens._
|
|
23
|
+
|
|
24
|
+
## Environment
|
|
25
|
+
|
|
26
|
+
- OS:
|
|
27
|
+
- Python version:
|
|
28
|
+
- Package version:
|
|
29
|
+
|
|
30
|
+
## Additional context
|
|
31
|
+
|
|
32
|
+
_Logs, screenshots, related issues._
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Suggest an idea or improvement
|
|
4
|
+
labels: enhancement
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Problem
|
|
8
|
+
|
|
9
|
+
_What problem does this solve? What use case does it enable?_
|
|
10
|
+
|
|
11
|
+
## Proposed solution
|
|
12
|
+
|
|
13
|
+
_Describe what you'd like to see._
|
|
14
|
+
|
|
15
|
+
## Alternatives considered
|
|
16
|
+
|
|
17
|
+
_Other approaches you've thought about._
|
|
18
|
+
|
|
19
|
+
## Additional context
|
|
20
|
+
|
|
21
|
+
_Anything else relevant._
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
## Summary
|
|
2
|
+
|
|
3
|
+
_What does this PR do? Link to any relevant issue with `closes #N`._
|
|
4
|
+
|
|
5
|
+
## Type
|
|
6
|
+
|
|
7
|
+
- [ ] Bug fix
|
|
8
|
+
- [ ] New feature
|
|
9
|
+
- [ ] Refactor / cleanup
|
|
10
|
+
- [ ] Docs / chore
|
|
11
|
+
|
|
12
|
+
## Checklist
|
|
13
|
+
|
|
14
|
+
- [ ] Branch created from `main`
|
|
15
|
+
- [ ] Commits follow Conventional Commits (`cz commit`)
|
|
16
|
+
- [ ] Version bumped with `cz bump` and pushed with `--follow-tags` (if releasing)
|
|
17
|
+
- [ ] Tests added or updated
|
|
18
|
+
- [ ] `uv run pre-commit run --all-files` passes locally
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["**"]
|
|
6
|
+
tags-ignore: ["v*"]
|
|
7
|
+
pull_request:
|
|
8
|
+
branches: [main]
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
lint:
|
|
15
|
+
name: Lint
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
if: "!startsWith(github.event.head_commit.message, 'bump:')"
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v6
|
|
20
|
+
with:
|
|
21
|
+
fetch-depth: 0
|
|
22
|
+
|
|
23
|
+
- uses: astral-sh/setup-uv@v7
|
|
24
|
+
with:
|
|
25
|
+
python-version: "3.12"
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: uv sync --extra dev
|
|
29
|
+
|
|
30
|
+
- name: Run pre-commit (ruff + mypy + commitizen + gitleaks)
|
|
31
|
+
run: uv run pre-commit run --all-files --show-diff-on-failure
|
|
32
|
+
|
|
33
|
+
test:
|
|
34
|
+
name: Test (Python ${{ matrix.python-version }} · ${{ matrix.os }})
|
|
35
|
+
runs-on: ${{ matrix.os }}
|
|
36
|
+
if: github.event_name == 'pull_request'
|
|
37
|
+
strategy:
|
|
38
|
+
fail-fast: false
|
|
39
|
+
matrix:
|
|
40
|
+
os: [ubuntu-latest]
|
|
41
|
+
python-version: ["3.11", "3.13"]
|
|
42
|
+
|
|
43
|
+
steps:
|
|
44
|
+
- uses: actions/checkout@v6
|
|
45
|
+
with:
|
|
46
|
+
fetch-depth: 0
|
|
47
|
+
|
|
48
|
+
- uses: astral-sh/setup-uv@v7
|
|
49
|
+
with:
|
|
50
|
+
python-version: ${{ matrix.python-version }}
|
|
51
|
+
|
|
52
|
+
- name: Install dependencies
|
|
53
|
+
run: uv sync --extra dev
|
|
54
|
+
|
|
55
|
+
- name: Run tests
|
|
56
|
+
run: uv run pytest
|
|
57
|
+
|
|
58
|
+
ci:
|
|
59
|
+
name: CI
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
needs: [lint, test]
|
|
62
|
+
if: always()
|
|
63
|
+
steps:
|
|
64
|
+
- name: Check required jobs
|
|
65
|
+
run: |
|
|
66
|
+
if [[ "${{ needs.lint.result }}" != "success" && "${{ needs.lint.result }}" != "skipped" ]]; then
|
|
67
|
+
echo "lint failed" && exit 1
|
|
68
|
+
fi
|
|
69
|
+
if [[ "${{ needs.test.result }}" != "success" && "${{ needs.test.result }}" != "skipped" ]]; then
|
|
70
|
+
echo "test failed" && exit 1
|
|
71
|
+
fi
|
|
72
|
+
echo "All required checks passed."
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths:
|
|
7
|
+
- "docs/**"
|
|
8
|
+
- "mkdocs.yml"
|
|
9
|
+
- "src/**/*.py"
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: write
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
deploy:
|
|
16
|
+
name: Deploy docs to GitHub Pages
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v6
|
|
20
|
+
with:
|
|
21
|
+
fetch-depth: 0
|
|
22
|
+
|
|
23
|
+
- uses: astral-sh/setup-uv@v7
|
|
24
|
+
with:
|
|
25
|
+
python-version: "3.12"
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: uv sync --extra docs
|
|
29
|
+
|
|
30
|
+
- name: Deploy
|
|
31
|
+
run: uv run mkdocs gh-deploy --force
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
name: PR Review (optional LLM)
|
|
2
|
+
|
|
3
|
+
# Optional AI-assisted pull request review.
|
|
4
|
+
# Uncomment ONE of the sections below and configure the required secret.
|
|
5
|
+
#
|
|
6
|
+
# This workflow has a no-op placeholder job so it stays valid while
|
|
7
|
+
# both options remain commented out.
|
|
8
|
+
|
|
9
|
+
on:
|
|
10
|
+
pull_request:
|
|
11
|
+
types: [opened, synchronize, ready_for_review]
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
|
|
15
|
+
# ── Option A: Claude API (Anthropic) ─────────────────────────────────────
|
|
16
|
+
# Requires secret: ANTHROPIC_API_KEY
|
|
17
|
+
# Docs: https://docs.anthropic.com/en/api/getting-started
|
|
18
|
+
#
|
|
19
|
+
# llm-review-claude:
|
|
20
|
+
# name: Claude PR review
|
|
21
|
+
# runs-on: ubuntu-latest
|
|
22
|
+
# steps:
|
|
23
|
+
# - uses: actions/checkout@v6
|
|
24
|
+
# with:
|
|
25
|
+
# fetch-depth: 0
|
|
26
|
+
#
|
|
27
|
+
# - name: Generate diff
|
|
28
|
+
# id: diff
|
|
29
|
+
# run: |
|
|
30
|
+
# DIFF=$(git diff origin/main...HEAD -- '*.py' | head -c 8000)
|
|
31
|
+
# echo "content<<EOF" >> "$GITHUB_OUTPUT"
|
|
32
|
+
# echo "$DIFF" >> "$GITHUB_OUTPUT"
|
|
33
|
+
# echo "EOF" >> "$GITHUB_OUTPUT"
|
|
34
|
+
#
|
|
35
|
+
# - name: Review with Claude
|
|
36
|
+
# env:
|
|
37
|
+
# ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
38
|
+
# GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
39
|
+
# PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
40
|
+
# run: |
|
|
41
|
+
# if [ -z "$ANTHROPIC_API_KEY" ]; then
|
|
42
|
+
# echo "ANTHROPIC_API_KEY not set — skipping."
|
|
43
|
+
# exit 0
|
|
44
|
+
# fi
|
|
45
|
+
# REVIEW=$(curl -s https://api.anthropic.com/v1/messages \
|
|
46
|
+
# -H "x-api-key: $ANTHROPIC_API_KEY" \
|
|
47
|
+
# -H "anthropic-version: 2023-06-01" \
|
|
48
|
+
# -H "content-type: application/json" \
|
|
49
|
+
# -d "{
|
|
50
|
+
# \"model\": \"claude-sonnet-4-6\",
|
|
51
|
+
# \"max_tokens\": 1024,
|
|
52
|
+
# \"messages\": [{
|
|
53
|
+
# \"role\": \"user\",
|
|
54
|
+
# \"content\": \"Review this Python PR diff for bugs, style issues, and improvements. Be concise.\n\n${{ steps.diff.outputs.content }}\"
|
|
55
|
+
# }]
|
|
56
|
+
# }" | python3 -c "import sys,json; print(json.load(sys.stdin)['content'][0]['text'])")
|
|
57
|
+
# gh pr comment "$PR_NUMBER" --body "## Claude PR Review\n\n${REVIEW}"
|
|
58
|
+
|
|
59
|
+
# ── Option B: Local Ollama (self-hosted runner) ───────────────────────────
|
|
60
|
+
# Requires: a self-hosted runner with Ollama running on localhost:11434
|
|
61
|
+
# Docs: https://ollama.com / https://docs.github.com/en/actions/hosting-your-own-runners
|
|
62
|
+
#
|
|
63
|
+
# llm-review-ollama:
|
|
64
|
+
# name: Ollama PR review
|
|
65
|
+
# runs-on: self-hosted
|
|
66
|
+
# steps:
|
|
67
|
+
# - uses: actions/checkout@v6
|
|
68
|
+
# with:
|
|
69
|
+
# fetch-depth: 0
|
|
70
|
+
#
|
|
71
|
+
# - name: Generate diff
|
|
72
|
+
# id: diff
|
|
73
|
+
# run: |
|
|
74
|
+
# DIFF=$(git diff origin/main...HEAD -- '*.py' | head -c 4000)
|
|
75
|
+
# echo "content<<EOF" >> "$GITHUB_OUTPUT"
|
|
76
|
+
# echo "$DIFF" >> "$GITHUB_OUTPUT"
|
|
77
|
+
# echo "EOF" >> "$GITHUB_OUTPUT"
|
|
78
|
+
#
|
|
79
|
+
# - name: Review with Ollama
|
|
80
|
+
# env:
|
|
81
|
+
# GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
82
|
+
# PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
83
|
+
# OLLAMA_MODEL: "llama3.2" # change to your preferred model
|
|
84
|
+
# run: |
|
|
85
|
+
# PROMPT="Review this Python PR diff for bugs, style issues, and improvements. Be concise.\n\n${{ steps.diff.outputs.content }}"
|
|
86
|
+
# REVIEW=$(curl -s http://localhost:11434/api/generate \
|
|
87
|
+
# -d "{\"model\": \"${OLLAMA_MODEL}\", \"prompt\": \"${PROMPT}\", \"stream\": false}" \
|
|
88
|
+
# | python3 -c "import sys,json; print(json.load(sys.stdin)['response'])")
|
|
89
|
+
# gh pr comment "$PR_NUMBER" --body "## Ollama PR Review (${OLLAMA_MODEL})\n\n${REVIEW}"
|
|
90
|
+
|
|
91
|
+
# Placeholder — remove once an option above is uncommented
|
|
92
|
+
no-op:
|
|
93
|
+
name: LLM review (not configured)
|
|
94
|
+
runs-on: ubuntu-latest
|
|
95
|
+
steps:
|
|
96
|
+
- run: echo "LLM PR review not configured. See .github/workflows/pr-review.yml."
|