chronocratic-models 0.1.0a1__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.
- chronocratic_models-0.1.0a1/.github/workflows/auto-changelog-fragment.yml +128 -0
- chronocratic_models-0.1.0a1/.github/workflows/build-and-test.yml +80 -0
- chronocratic_models-0.1.0a1/.github/workflows/ff-merge-check.yml +46 -0
- chronocratic_models-0.1.0a1/.github/workflows/ff-merge-do.yml +98 -0
- chronocratic_models-0.1.0a1/.github/workflows/main-pre-merge-gate.yml +61 -0
- chronocratic_models-0.1.0a1/.github/workflows/pypi-publish.yml +30 -0
- chronocratic_models-0.1.0a1/.github/workflows/release-notes.yml +44 -0
- chronocratic_models-0.1.0a1/.github/workflows/release-prep.yml +83 -0
- chronocratic_models-0.1.0a1/.gitignore +232 -0
- chronocratic_models-0.1.0a1/.readthedocs.yaml +16 -0
- chronocratic_models-0.1.0a1/.vscode/settings.json +22 -0
- chronocratic_models-0.1.0a1/CHANGELOG.md +47 -0
- chronocratic_models-0.1.0a1/LICENSE +29 -0
- chronocratic_models-0.1.0a1/PKG-INFO +125 -0
- chronocratic_models-0.1.0a1/README.md +90 -0
- chronocratic_models-0.1.0a1/changelog.d/21.changed.md +1 -0
- chronocratic_models-0.1.0a1/changelog.d/README.md +43 -0
- chronocratic_models-0.1.0a1/docs/_static/custom.css +1 -0
- chronocratic_models-0.1.0a1/docs/api/augmentation.md +53 -0
- chronocratic_models-0.1.0a1/docs/api/conv_dilated.md +46 -0
- chronocratic_models-0.1.0a1/docs/api/conv_standard.md +45 -0
- chronocratic_models-0.1.0a1/docs/api/distances.md +13 -0
- chronocratic_models-0.1.0a1/docs/api/generative.md +21 -0
- chronocratic_models-0.1.0a1/docs/api/index.md +27 -0
- chronocratic_models-0.1.0a1/docs/api/layers.md +9 -0
- chronocratic_models-0.1.0a1/docs/api/mixins.md +23 -0
- chronocratic_models-0.1.0a1/docs/api/recurrent.md +17 -0
- chronocratic_models-0.1.0a1/docs/api/supervised.md +23 -0
- chronocratic_models-0.1.0a1/docs/api/transformer.md +17 -0
- chronocratic_models-0.1.0a1/docs/changelog.md +4 -0
- chronocratic_models-0.1.0a1/docs/conf.py +71 -0
- chronocratic_models-0.1.0a1/docs/contributing.md +86 -0
- chronocratic_models-0.1.0a1/docs/index.md +52 -0
- chronocratic_models-0.1.0a1/docs/quickstart.md +56 -0
- chronocratic_models-0.1.0a1/pyproject.toml +122 -0
- chronocratic_models-0.1.0a1/ruff.toml +53 -0
- chronocratic_models-0.1.0a1/setup.cfg +4 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/__init__.py +48 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/_mixin/__init__.py +3 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/_mixin/encoding.py +147 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/_version.py +24 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/augmentation/__init__.py +70 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/augmentation/base.py +328 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/augmentation/decorators.py +65 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/augmentation/primitives.py +251 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/augmentation/producers.py +164 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/augmentation/trainable_support.py +99 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/__init__.py +35 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/__init__.py +16 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/_mixin/__init__.py +7 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/_mixin/encoding.py +379 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/autotcl/__init__.py +4 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/autotcl/augmentation/__init__.py +20 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/autotcl/augmentation/methods.py +196 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/autotcl/augmentation/training.py +128 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/autotcl/config.py +51 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/autotcl/losses.py +332 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/autotcl/model.py +247 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/autotcl/utils.py +93 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/cost/__init__.py +4 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/cost/augmentation.py +148 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/cost/config.py +52 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/cost/model.py +331 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/cost/utils.py +50 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/encoders/__init__.py +13 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/encoders/encoders.py +639 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/encoders/masking.py +192 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/layers/__init__.py +7 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/layers/dilated.py +50 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/layers/same_pad.py +131 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/ts2vec/__init__.py +4 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/ts2vec/augmentation.py +126 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/ts2vec/config.py +45 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/ts2vec/model.py +163 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/dilated/ts2vec/utils.py +23 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/standard/__init__.py +16 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/standard/mcl/__init__.py +4 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/standard/mcl/config.py +27 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/standard/mcl/encoder.py +34 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/standard/mcl/losses.py +40 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/standard/mcl/model.py +85 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/standard/series2vec/__init__.py +4 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/standard/series2vec/config.py +54 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/standard/series2vec/encoder.py +44 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/standard/series2vec/filters.py +63 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/standard/series2vec/losses.py +79 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/standard/series2vec/model.py +162 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/standard/series2vec/network.py +134 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/standard/tstcc/__init__.py +4 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/standard/tstcc/augmentations.py +61 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/standard/tstcc/config.py +59 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/standard/tstcc/encoder.py +68 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/standard/tstcc/losses.py +58 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/standard/tstcc/model.py +205 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/convolutional/standard/tstcc/temporal_contrast.py +188 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/distances/__init__.py +3 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/distances/soft_dtw/__init__.py +3 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/distances/soft_dtw/soft_dtw_cuda.py +453 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/generative/__init__.py +7 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/generative/timevae/__init__.py +4 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/generative/timevae/config.py +44 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/generative/timevae/model.py +191 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/generative/timevae/vae_base.py +137 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/layers/__init__.py +3 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/layers/general.py +249 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/losses/__init__.py +11 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/losses/contrastive.py +144 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/recurrent/__init__.py +7 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/recurrent/timenet/__init__.py +4 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/recurrent/timenet/config.py +31 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/recurrent/timenet/model.py +115 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/supervised/__init__.py +62 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/supervised/_adapters.py +102 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/supervised/_callbacks.py +78 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/supervised/_utils.py +43 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/supervised/factory.py +188 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/supervised/supervised.py +214 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/transformer/__init__.py +7 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/transformer/tst/__init__.py +4 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/transformer/tst/config.py +67 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/transformer/tst/loss.py +40 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/transformer/tst/model.py +223 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/transformer/tst/ts_transformer.py +303 -0
- chronocratic_models-0.1.0a1/src/chronocratic/models/utils.py +219 -0
- chronocratic_models-0.1.0a1/src/chronocratic_models.egg-info/PKG-INFO +125 -0
- chronocratic_models-0.1.0a1/src/chronocratic_models.egg-info/SOURCES.txt +156 -0
- chronocratic_models-0.1.0a1/src/chronocratic_models.egg-info/dependency_links.txt +1 -0
- chronocratic_models-0.1.0a1/src/chronocratic_models.egg-info/requires.txt +12 -0
- chronocratic_models-0.1.0a1/src/chronocratic_models.egg-info/top_level.txt +1 -0
- chronocratic_models-0.1.0a1/tests/conftest.py +132 -0
- chronocratic_models-0.1.0a1/tests/integration/__init__.py +0 -0
- chronocratic_models-0.1.0a1/tests/integration/test_supervised_integration.py +317 -0
- chronocratic_models-0.1.0a1/tests/test_aug_config.py +203 -0
- chronocratic_models-0.1.0a1/tests/test_aug_contract.py +277 -0
- chronocratic_models-0.1.0a1/tests/test_aug_covariance.py +86 -0
- chronocratic_models-0.1.0a1/tests/test_aug_cross_model.py +228 -0
- chronocratic_models-0.1.0a1/tests/test_aug_decorators.py +169 -0
- chronocratic_models-0.1.0a1/tests/test_aug_primitives.py +158 -0
- chronocratic_models-0.1.0a1/tests/test_aug_producers.py +187 -0
- chronocratic_models-0.1.0a1/tests/test_aug_trainable_support.py +204 -0
- chronocratic_models-0.1.0a1/tests/test_augmentation.py +331 -0
- chronocratic_models-0.1.0a1/tests/test_augmentation_base.py +142 -0
- chronocratic_models-0.1.0a1/tests/test_augmentation_per_model.py +417 -0
- chronocratic_models-0.1.0a1/tests/test_autotcl_producer.py +200 -0
- chronocratic_models-0.1.0a1/tests/test_config.py +261 -0
- chronocratic_models-0.1.0a1/tests/test_config_hierarchy.py +169 -0
- chronocratic_models-0.1.0a1/tests/test_cost_producer.py +119 -0
- chronocratic_models-0.1.0a1/tests/test_from_config.py +258 -0
- chronocratic_models-0.1.0a1/tests/test_mixin.py +322 -0
- chronocratic_models-0.1.0a1/tests/test_smoke.py +240 -0
- chronocratic_models-0.1.0a1/tests/test_ts2vec_producer.py +165 -0
- chronocratic_models-0.1.0a1/tests/test_tstcc_producer.py +167 -0
- chronocratic_models-0.1.0a1/tests/unit/test_backbone_representation_dim.py +154 -0
- chronocratic_models-0.1.0a1/tests/unit/test_series2vec_supervised.py +91 -0
- chronocratic_models-0.1.0a1/tests/unit/test_supervised_package.py +454 -0
- chronocratic_models-0.1.0a1/tests/unit/test_tst_supervised.py +85 -0
- chronocratic_models-0.1.0a1/tests/unit/test_tstcc_supervised.py +149 -0
- chronocratic_models-0.1.0a1/uv.lock +3050 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
name: Changelog fragment
|
|
2
|
+
|
|
3
|
+
# Single source of truth for changelog fragments on PRs targeting `dev`:
|
|
4
|
+
# 1. Same-repo PR with no fragment -> create one from the PR title and push it.
|
|
5
|
+
# 2. Always (incl. fork PRs) -> `towncrier check` so every PR has a fragment.
|
|
6
|
+
#
|
|
7
|
+
# Both happen in one job so the check sees the freshly-created fragment in the
|
|
8
|
+
# same run. A push made with GITHUB_TOKEN does NOT re-trigger workflows, so the
|
|
9
|
+
# check must not live in a separate workflow that never re-runs after the push.
|
|
10
|
+
#
|
|
11
|
+
# Fork PRs cannot be pushed to, so they are only checked — the contributor must
|
|
12
|
+
# add a fragment by hand (or a maintainer pushes one to the PR branch).
|
|
13
|
+
#
|
|
14
|
+
# Label convention for fragment type (defaults to `changed`):
|
|
15
|
+
# changelog:added | changelog:fixed | changelog:changed
|
|
16
|
+
# changelog:removed | changelog:deprecated | changelog:security
|
|
17
|
+
# `skip-changelog` opts the PR out entirely.
|
|
18
|
+
|
|
19
|
+
on:
|
|
20
|
+
pull_request:
|
|
21
|
+
branches: [dev]
|
|
22
|
+
types: [opened, edited, synchronize, labeled, unlabeled]
|
|
23
|
+
|
|
24
|
+
permissions:
|
|
25
|
+
contents: write
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
fragment:
|
|
29
|
+
name: Changelog fragment
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
# Skip release-prep PRs two ways: the `skip-changelog` label (may be missing
|
|
32
|
+
# if the PR was opened by hand) AND the `release-prep/*` head branch, which is
|
|
33
|
+
# always present in the event payload regardless of labelling.
|
|
34
|
+
if: >-
|
|
35
|
+
!contains(github.event.pull_request.labels.*.name, 'skip-changelog') &&
|
|
36
|
+
!startsWith(github.event.pull_request.head.ref, 'release-prep/')
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@v6
|
|
39
|
+
with:
|
|
40
|
+
# For same-repo PRs check out the head branch so we can commit + push
|
|
41
|
+
# a fragment. For forks this ref is still readable for the check step.
|
|
42
|
+
ref: ${{ github.event.pull_request.head.ref }}
|
|
43
|
+
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
|
44
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
45
|
+
fetch-depth: 0
|
|
46
|
+
|
|
47
|
+
- name: Set up Python
|
|
48
|
+
uses: actions/setup-python@v6
|
|
49
|
+
with:
|
|
50
|
+
python-version: "3.12"
|
|
51
|
+
|
|
52
|
+
- name: Install towncrier
|
|
53
|
+
run: pip install towncrier
|
|
54
|
+
|
|
55
|
+
- name: Check if a fragment already exists
|
|
56
|
+
id: check
|
|
57
|
+
env:
|
|
58
|
+
PR: ${{ github.event.pull_request.number }}
|
|
59
|
+
run: |
|
|
60
|
+
if compgen -G "changelog.d/${PR}.*.md" > /dev/null 2>&1; then
|
|
61
|
+
echo "exists=true" >> "$GITHUB_OUTPUT"
|
|
62
|
+
else
|
|
63
|
+
echo "exists=false" >> "$GITHUB_OUTPUT"
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
# A `changelog:<type>` label wins; otherwise infer from the Conventional
|
|
67
|
+
# Commits prefix of the PR title; otherwise fall back to `changed`.
|
|
68
|
+
- name: Resolve fragment type
|
|
69
|
+
if: steps.check.outputs.exists == 'false'
|
|
70
|
+
id: type
|
|
71
|
+
env:
|
|
72
|
+
LABELS: ${{ toJSON(github.event.pull_request.labels.*.name) }}
|
|
73
|
+
PR_TITLE: ${{ github.event.pull_request.title }}
|
|
74
|
+
run: |
|
|
75
|
+
TYPE=""
|
|
76
|
+
if echo "$LABELS" | grep -q '"changelog:added"'; then TYPE="added"
|
|
77
|
+
elif echo "$LABELS" | grep -q '"changelog:fixed"'; then TYPE="fixed"
|
|
78
|
+
elif echo "$LABELS" | grep -q '"changelog:changed"'; then TYPE="changed"
|
|
79
|
+
elif echo "$LABELS" | grep -q '"changelog:removed"'; then TYPE="removed"
|
|
80
|
+
elif echo "$LABELS" | grep -q '"changelog:deprecated"'; then TYPE="deprecated"
|
|
81
|
+
elif echo "$LABELS" | grep -q '"changelog:security"'; then TYPE="security"
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
if [ -z "$TYPE" ]; then
|
|
85
|
+
# Lowercase Conventional Commits verb before the first ( or : or !
|
|
86
|
+
PREFIX="$(printf '%s' "$PR_TITLE" | sed -E 's/^([a-zA-Z]+).*/\1/' | tr '[:upper:]' '[:lower:]')"
|
|
87
|
+
case "$PREFIX" in
|
|
88
|
+
fix) TYPE="fixed" ;;
|
|
89
|
+
feat) TYPE="added" ;;
|
|
90
|
+
revert|remove) TYPE="removed" ;;
|
|
91
|
+
deprecate) TYPE="deprecated" ;;
|
|
92
|
+
security) TYPE="security" ;;
|
|
93
|
+
refactor|perf|change) TYPE="changed" ;;
|
|
94
|
+
*) TYPE="changed" ;;
|
|
95
|
+
esac
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
echo "type=$TYPE" >> "$GITHUB_OUTPUT"
|
|
99
|
+
|
|
100
|
+
# Only same-repo PRs can be pushed to. Forks fall through to the check
|
|
101
|
+
# step, which fails until the contributor adds a fragment by hand.
|
|
102
|
+
- name: Create and commit fragment
|
|
103
|
+
if: >-
|
|
104
|
+
steps.check.outputs.exists == 'false' &&
|
|
105
|
+
github.event.pull_request.head.repo.full_name == github.repository
|
|
106
|
+
env:
|
|
107
|
+
PR_TITLE: ${{ github.event.pull_request.title }}
|
|
108
|
+
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
109
|
+
FRAGMENT_TYPE: ${{ steps.type.outputs.type }}
|
|
110
|
+
run: |
|
|
111
|
+
# Strip a leading Conventional Commits prefix (e.g. "ci(changelog): ")
|
|
112
|
+
# so the changelog reads as user-facing prose, then capitalise and
|
|
113
|
+
# ensure a single trailing period.
|
|
114
|
+
BODY="$(printf '%s' "${PR_TITLE}" | sed -E 's/^[a-z]+(\([^)]*\))?!?:[[:space:]]*//')"
|
|
115
|
+
BODY="$(printf '%s' "${BODY}" | awk '{ print toupper(substr($0,1,1)) substr($0,2) }')"
|
|
116
|
+
BODY="${BODY%.}."
|
|
117
|
+
|
|
118
|
+
FRAGMENT="changelog.d/${PR_NUMBER}.${FRAGMENT_TYPE}.md"
|
|
119
|
+
printf '%s\n' "${BODY}" > "${FRAGMENT}"
|
|
120
|
+
|
|
121
|
+
git config user.name "github-actions[bot]"
|
|
122
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
123
|
+
git add "${FRAGMENT}"
|
|
124
|
+
git commit -m "ci: add towncrier fragment for PR #${PR_NUMBER}"
|
|
125
|
+
git push
|
|
126
|
+
|
|
127
|
+
- name: Verify a fragment is present
|
|
128
|
+
run: towncrier check --compare-with origin/${{ github.base_ref }}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
name: Build and Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main, dev]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v6
|
|
12
|
+
with:
|
|
13
|
+
fetch-depth: 0
|
|
14
|
+
fetch-tags: true
|
|
15
|
+
- name: Set up Python
|
|
16
|
+
uses: actions/setup-python@v6
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.12"
|
|
19
|
+
- name: Install uv
|
|
20
|
+
run: pip install uv
|
|
21
|
+
- name: Install dependencies
|
|
22
|
+
run: uv sync --all-extras
|
|
23
|
+
- name: Run tests
|
|
24
|
+
run: uv run pytest tests/ --cov=src/chronocratic/models --cov-report=xml
|
|
25
|
+
|
|
26
|
+
lint:
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v6
|
|
30
|
+
with:
|
|
31
|
+
fetch-depth: 0
|
|
32
|
+
fetch-tags: true
|
|
33
|
+
- name: Set up Python
|
|
34
|
+
uses: actions/setup-python@v6
|
|
35
|
+
with:
|
|
36
|
+
python-version: "3.12"
|
|
37
|
+
- name: Install ruff
|
|
38
|
+
run: pip install ruff
|
|
39
|
+
- name: Run ruff check
|
|
40
|
+
run: ruff check src/
|
|
41
|
+
- name: Run ruff format check
|
|
42
|
+
run: ruff format --check src/ tests/
|
|
43
|
+
|
|
44
|
+
build:
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
needs: [test, lint]
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@v6
|
|
49
|
+
with:
|
|
50
|
+
fetch-depth: 0
|
|
51
|
+
fetch-tags: true
|
|
52
|
+
- name: Set up Python
|
|
53
|
+
uses: actions/setup-python@v6
|
|
54
|
+
with:
|
|
55
|
+
python-version: "3.12"
|
|
56
|
+
- name: Install build
|
|
57
|
+
run: pip install build
|
|
58
|
+
- name: Build package
|
|
59
|
+
run: python -m build
|
|
60
|
+
- name: Validate with twine
|
|
61
|
+
run: pip install twine && twine check dist/*
|
|
62
|
+
|
|
63
|
+
docs:
|
|
64
|
+
runs-on: ubuntu-latest
|
|
65
|
+
needs: [test, lint]
|
|
66
|
+
steps:
|
|
67
|
+
- uses: actions/checkout@v6
|
|
68
|
+
with:
|
|
69
|
+
fetch-depth: 0
|
|
70
|
+
fetch-tags: true
|
|
71
|
+
- name: Set up Python
|
|
72
|
+
uses: actions/setup-python@v6
|
|
73
|
+
with:
|
|
74
|
+
python-version: "3.12"
|
|
75
|
+
- name: Install uv
|
|
76
|
+
run: pip install uv
|
|
77
|
+
- name: Install dependencies
|
|
78
|
+
run: uv sync --extra docs
|
|
79
|
+
- name: Build docs
|
|
80
|
+
run: uv run sphinx-build -b html docs/ docs/_build/
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: Check Fast-Forward
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
issue_comment:
|
|
5
|
+
types: [created]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
check-fast-forward:
|
|
9
|
+
if: ${{ contains(github.event.comment.body, '/check-fast-forward')
|
|
10
|
+
&& github.event.issue.pull_request }}
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
pull-requests: write
|
|
15
|
+
issues: write
|
|
16
|
+
steps:
|
|
17
|
+
- name: Verify target branch
|
|
18
|
+
uses: actions/github-script@v9
|
|
19
|
+
with:
|
|
20
|
+
script: |
|
|
21
|
+
const { data: pr } = await github.rest.pulls.get({
|
|
22
|
+
owner: context.repo.owner,
|
|
23
|
+
repo: context.repo.repo,
|
|
24
|
+
pull_number: context.issue.number
|
|
25
|
+
});
|
|
26
|
+
if (pr.base.ref !== 'main' || pr.head.ref !== 'dev') {
|
|
27
|
+
await github.rest.issues.createComment({
|
|
28
|
+
owner: context.repo.owner,
|
|
29
|
+
repo: context.repo.repo,
|
|
30
|
+
issue_number: context.issue.number,
|
|
31
|
+
body: `❌ \`/check-fast-forward\` only works on \`dev → main\` PRs `
|
|
32
|
+
+ `(this PR: \`${pr.head.ref} → ${pr.base.ref}\`).`
|
|
33
|
+
});
|
|
34
|
+
core.setFailed('Fast-forward check is only allowed for dev → main PRs.');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
- name: Checkout repo
|
|
38
|
+
uses: actions/checkout@v6
|
|
39
|
+
with:
|
|
40
|
+
fetch-depth: 0
|
|
41
|
+
|
|
42
|
+
- name: Check if fast-forward is possible
|
|
43
|
+
uses: sequoia-pgp/fast-forward@v1
|
|
44
|
+
with:
|
|
45
|
+
merge: false
|
|
46
|
+
comment: always
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
name: Fast Forward Merge
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
issue_comment:
|
|
5
|
+
types: [created]
|
|
6
|
+
|
|
7
|
+
concurrency:
|
|
8
|
+
group: fast-forward-${{ github.event.issue.number }}
|
|
9
|
+
cancel-in-progress: false
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
fast-forward:
|
|
13
|
+
if: ${{ contains(github.event.comment.body, '/fast-forward')
|
|
14
|
+
&& github.event.issue.pull_request }}
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
# The FF_MERGE_TOKEN secret lives in the `ff_merge` environment. It is a PAT
|
|
17
|
+
# of a ruleset bypass actor — the GITHUB_TOKEN (github-actions[bot]) is not on
|
|
18
|
+
# the main ruleset bypass list, so its push to main is rejected.
|
|
19
|
+
environment: ff_merge
|
|
20
|
+
permissions:
|
|
21
|
+
contents: write
|
|
22
|
+
pull-requests: write
|
|
23
|
+
issues: write
|
|
24
|
+
steps:
|
|
25
|
+
- name: Checkout repo
|
|
26
|
+
uses: actions/checkout@v6
|
|
27
|
+
with:
|
|
28
|
+
fetch-depth: 0
|
|
29
|
+
token: ${{ secrets.FF_MERGE_TOKEN }}
|
|
30
|
+
|
|
31
|
+
- name: Configure git
|
|
32
|
+
run: |
|
|
33
|
+
git config user.name "github-actions[bot]"
|
|
34
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
35
|
+
|
|
36
|
+
- name: Verify PR and actor permissions
|
|
37
|
+
uses: actions/github-script@v9
|
|
38
|
+
with:
|
|
39
|
+
script: |
|
|
40
|
+
const { data: pr } = await github.rest.pulls.get({
|
|
41
|
+
owner: context.repo.owner,
|
|
42
|
+
repo: context.repo.repo,
|
|
43
|
+
pull_number: context.issue.number
|
|
44
|
+
});
|
|
45
|
+
if (pr.base.ref !== 'main') {
|
|
46
|
+
core.setFailed('Fast-forward is only allowed for PRs targeting main.');
|
|
47
|
+
}
|
|
48
|
+
if (pr.head.ref !== 'dev') {
|
|
49
|
+
core.setFailed('Fast-forward is only allowed for PRs from dev to main.');
|
|
50
|
+
}
|
|
51
|
+
const { data: perm } = await github.rest.repos.getCollaboratorPermissionLevel({
|
|
52
|
+
owner: context.repo.owner,
|
|
53
|
+
repo: context.repo.repo,
|
|
54
|
+
username: context.actor
|
|
55
|
+
});
|
|
56
|
+
if (!['admin', 'maintain'].includes(perm.permission)) {
|
|
57
|
+
core.setFailed(`@${context.actor} (role: ${perm.permission}) — only admin/maintainer can fast-forward.`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Disabled while repo is a personal fork with no org reviewers — a
|
|
61
|
+
# solo maintainer cannot approve their own PR. Re-enable once moved
|
|
62
|
+
# to an org with a second reviewer (or branch-protection approval rule).
|
|
63
|
+
# - name: Check PR is approved
|
|
64
|
+
# uses: actions/github-script@v9
|
|
65
|
+
# with:
|
|
66
|
+
# script: |
|
|
67
|
+
# const { data: reviews } = await github.rest.pulls.listReviews({
|
|
68
|
+
# owner: context.repo.owner,
|
|
69
|
+
# repo: context.repo.repo,
|
|
70
|
+
# pull_number: context.issue.number
|
|
71
|
+
# });
|
|
72
|
+
# const approved = reviews.filter(r => r.state === 'APPROVED');
|
|
73
|
+
# if (approved.length === 0) {
|
|
74
|
+
# core.setFailed('PR must have at least one approval before fast-forward merge.');
|
|
75
|
+
# }
|
|
76
|
+
|
|
77
|
+
- name: Backup main before merge
|
|
78
|
+
run: |
|
|
79
|
+
BACKUP="main.backup.${{ github.event.issue.number }}.$(date +%Y%m%d-%H%M%S)"
|
|
80
|
+
git branch "$BACKUP" origin/main
|
|
81
|
+
git push origin "$BACKUP"
|
|
82
|
+
echo "Created backup: $BACKUP"
|
|
83
|
+
|
|
84
|
+
- name: Fast-forward merge
|
|
85
|
+
uses: sequoia-pgp/fast-forward@v1
|
|
86
|
+
with:
|
|
87
|
+
merge: true
|
|
88
|
+
comment: always
|
|
89
|
+
github_token: ${{ secrets.FF_MERGE_TOKEN }}
|
|
90
|
+
|
|
91
|
+
- name: Clean up old backups
|
|
92
|
+
run: |
|
|
93
|
+
git fetch --prune origin
|
|
94
|
+
# Sort by the trailing YYYYMMDD-HHMMSS timestamp (4th dot field) so
|
|
95
|
+
# retention is chronological regardless of PR-number digit width.
|
|
96
|
+
git branch -r | grep 'origin/main.backup.' | sed 's| *origin/||' | \
|
|
97
|
+
sort -t. -k4 | head -n -5 | \
|
|
98
|
+
xargs -I {} git push origin --delete {} || true
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
name: Main Pre-merge Gate
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main]
|
|
6
|
+
types: [opened, synchronize, reopened]
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: pre-merge-gate-${{ github.event.pull_request.number }}
|
|
10
|
+
cancel-in-progress: true
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
forbidden-files:
|
|
14
|
+
name: Check forbidden paths
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- name: Reject non-dev PRs
|
|
18
|
+
if: ${{ github.head_ref != 'dev' }}
|
|
19
|
+
run: |
|
|
20
|
+
echo "::error::PRs from '${{ github.head_ref }}' to main are not allowed. Only dev→main merges are permitted."
|
|
21
|
+
exit 1
|
|
22
|
+
|
|
23
|
+
- name: Checkout
|
|
24
|
+
uses: actions/checkout@v6
|
|
25
|
+
with:
|
|
26
|
+
fetch-depth: 0
|
|
27
|
+
|
|
28
|
+
- name: Scan PR for forbidden files
|
|
29
|
+
run: |
|
|
30
|
+
FORBIDDEN=(
|
|
31
|
+
".claude/"
|
|
32
|
+
".remember/"
|
|
33
|
+
".planning/"
|
|
34
|
+
"MEMORY.md"
|
|
35
|
+
"CLAUDE.md"
|
|
36
|
+
"CONTEXT.md"
|
|
37
|
+
"STATE.md"
|
|
38
|
+
"ROADMAP.md"
|
|
39
|
+
".continue-here.md"
|
|
40
|
+
"HANDOFF.json"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
CHANGED=$(git diff --diff-filter=ACMR --name-only ${{ github.event.pull_request.base.sha }}..HEAD)
|
|
44
|
+
MATCHED=()
|
|
45
|
+
|
|
46
|
+
for path in "${FORBIDDEN[@]}"; do
|
|
47
|
+
# Anchor to a path boundary (start of path or after a '/') so e.g.
|
|
48
|
+
# CLAUDE.md does not match NOTCLAUDE.md.
|
|
49
|
+
while IFS= read -r f; do
|
|
50
|
+
[ -n "$f" ] && MATCHED+=("$f")
|
|
51
|
+
done < <(printf '%s\n' "$CHANGED" | grep -E "(^|/)$path" || true)
|
|
52
|
+
done
|
|
53
|
+
|
|
54
|
+
if [ "${#MATCHED[@]}" -gt 0 ]; then
|
|
55
|
+
echo "::error::Forbidden files detected:"
|
|
56
|
+
for f in "${MATCHED[@]}"; do
|
|
57
|
+
echo "::error:: - $f"
|
|
58
|
+
done
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
echo "All clear."
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
pypi-publish:
|
|
9
|
+
name: Upload release to PyPI
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
environment:
|
|
12
|
+
name: pypi
|
|
13
|
+
url: https://pypi.org/p/chronocratic-models
|
|
14
|
+
permissions:
|
|
15
|
+
id-token: write
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v6
|
|
18
|
+
with:
|
|
19
|
+
fetch-depth: 0
|
|
20
|
+
fetch-tags: true
|
|
21
|
+
- name: Set up Python
|
|
22
|
+
uses: actions/setup-python@v6
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.12"
|
|
25
|
+
- name: Install build
|
|
26
|
+
run: pip install build
|
|
27
|
+
- name: Build package
|
|
28
|
+
run: python -m build
|
|
29
|
+
- name: Publish to PyPI
|
|
30
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Sync release notes
|
|
2
|
+
|
|
3
|
+
# When a GitHub release is published, replace its body with the matching
|
|
4
|
+
# section from CHANGELOG.md so the repo, GitHub, and Read the Docs all show
|
|
5
|
+
# identical notes. CHANGELOG.md is assembled from towncrier fragments on `dev`
|
|
6
|
+
# before the release, so the section already exists at tag time.
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
release:
|
|
10
|
+
types: [published]
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: write
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
sync:
|
|
17
|
+
name: Sync notes from CHANGELOG.md
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v6
|
|
21
|
+
with:
|
|
22
|
+
ref: main
|
|
23
|
+
fetch-depth: 0
|
|
24
|
+
|
|
25
|
+
- name: Extract changelog section
|
|
26
|
+
run: |
|
|
27
|
+
TAG="${{ github.event.release.tag_name }}"
|
|
28
|
+
VERSION="${TAG#v}"
|
|
29
|
+
awk -v v="$VERSION" '
|
|
30
|
+
index($0, "## v" v " ") == 1 { f = 1; print; next }
|
|
31
|
+
f && /^## v/ { exit }
|
|
32
|
+
f { print }
|
|
33
|
+
' CHANGELOG.md > release-notes.md
|
|
34
|
+
if [ ! -s release-notes.md ]; then
|
|
35
|
+
echo "::error::No CHANGELOG.md section found for $TAG (expected a '## v$VERSION ' heading)."
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
echo "Extracted notes for $TAG:"
|
|
39
|
+
cat release-notes.md
|
|
40
|
+
|
|
41
|
+
- name: Update release body
|
|
42
|
+
env:
|
|
43
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
44
|
+
run: gh release edit "${{ github.event.release.tag_name }}" --notes-file release-notes.md
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
name: Release prep
|
|
2
|
+
|
|
3
|
+
# Assembles CHANGELOG.md from the accumulated towncrier fragments on `dev`,
|
|
4
|
+
# BEFORE the release. Run this, merge the resulting PR into `dev`, then open
|
|
5
|
+
# the dev -> main PR and fast-forward it (`/fast-forward`). The dev -> main
|
|
6
|
+
# merge cannot build the changelog itself: that merge is fast-forward only,
|
|
7
|
+
# so main must equal dev's exact HEAD — every commit has to already be on dev.
|
|
8
|
+
#
|
|
9
|
+
# Versioning is dynamic (setuptools_scm), so the version is supplied here and
|
|
10
|
+
# only stamps the changelog heading; the real release version is the git tag
|
|
11
|
+
# created when the GitHub release is published.
|
|
12
|
+
|
|
13
|
+
on:
|
|
14
|
+
workflow_dispatch:
|
|
15
|
+
inputs:
|
|
16
|
+
version:
|
|
17
|
+
description: "Release version, no leading v (e.g. 0.1.0a2)"
|
|
18
|
+
required: true
|
|
19
|
+
type: string
|
|
20
|
+
|
|
21
|
+
permissions:
|
|
22
|
+
contents: write
|
|
23
|
+
pull-requests: write
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
prep:
|
|
27
|
+
name: Build changelog and open PR
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
# FF_MERGE_TOKEN (PAT, in the `ff_merge` environment) acts as a real user, so
|
|
30
|
+
# it can open the PR — the GITHUB_TOKEN is blocked by the repo's "Allow GitHub
|
|
31
|
+
# Actions to create and approve pull requests" policy being off.
|
|
32
|
+
environment: ff_merge
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v6
|
|
35
|
+
with:
|
|
36
|
+
ref: dev
|
|
37
|
+
fetch-depth: 0
|
|
38
|
+
token: ${{ secrets.FF_MERGE_TOKEN }}
|
|
39
|
+
|
|
40
|
+
- name: Set up Python
|
|
41
|
+
uses: actions/setup-python@v6
|
|
42
|
+
with:
|
|
43
|
+
python-version: "3.12"
|
|
44
|
+
|
|
45
|
+
- name: Install towncrier
|
|
46
|
+
run: pip install towncrier
|
|
47
|
+
|
|
48
|
+
- name: Validate version
|
|
49
|
+
env:
|
|
50
|
+
VERSION: ${{ inputs.version }}
|
|
51
|
+
run: |
|
|
52
|
+
if ! printf '%s' "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+([ab]|rc)?[0-9]*$'; then
|
|
53
|
+
echo "::error::'$VERSION' is not a valid PEP 440 version (expected e.g. 0.1.0 or 0.1.0a2)."
|
|
54
|
+
exit 1
|
|
55
|
+
fi
|
|
56
|
+
if [ -z "$(ls -A changelog.d/*.md 2>/dev/null | grep -v '/README.md')" ]; then
|
|
57
|
+
echo "::error::No towncrier fragments in changelog.d/ — nothing to release."
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
- name: Build changelog
|
|
62
|
+
env:
|
|
63
|
+
VERSION: ${{ inputs.version }}
|
|
64
|
+
run: towncrier build --yes --version "$VERSION"
|
|
65
|
+
|
|
66
|
+
- name: Open release-prep PR
|
|
67
|
+
env:
|
|
68
|
+
VERSION: ${{ inputs.version }}
|
|
69
|
+
GH_TOKEN: ${{ secrets.FF_MERGE_TOKEN }}
|
|
70
|
+
run: |
|
|
71
|
+
BRANCH="release-prep/v${VERSION}"
|
|
72
|
+
git config user.name "github-actions[bot]"
|
|
73
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
74
|
+
git checkout -b "$BRANCH"
|
|
75
|
+
git add -A
|
|
76
|
+
git commit -m "docs(changelog): assemble notes for v${VERSION}"
|
|
77
|
+
git push -u origin "$BRANCH"
|
|
78
|
+
gh pr create \
|
|
79
|
+
--base dev \
|
|
80
|
+
--head "$BRANCH" \
|
|
81
|
+
--title "docs(changelog): release v${VERSION}" \
|
|
82
|
+
--label skip-changelog \
|
|
83
|
+
--body "Auto-generated by the release-prep workflow. Builds \`CHANGELOG.md\` from the towncrier fragments and removes them. Merge into \`dev\`, then open the dev -> main PR."
|