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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/auto-tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,16 @@ jobs:
set -euo pipefail
TAG="v${{ steps.final.outputs.version }}"
ARGS=("$TAG" --title "$TAG" --generate-notes)
# The release's section of docs/changelog.md is the release notes,
# with GitHub's generated pull-request list appended (--generate-notes
# appends to --notes-file). The changelog workflow refuses a pull
# request into main without that section, so the fallback to generated
# notes alone only covers a release that bypassed the pull request.
if uv run python scripts/changelog_section.py "${{ steps.final.outputs.version }}" > release-notes.md; then
ARGS+=(--notes-file release-notes.md)
else
echo "::warning::docs/changelog.md has no section for ${{ steps.final.outputs.version }}; using generated notes only."
fi
if [ "${{ steps.final.outputs.prerelease }}" = "true" ]; then
ARGS+=(--prerelease)
else
Expand Down
65 changes: 65 additions & 0 deletions .github/workflows/changelog.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Changelog

# docs/changelog.md is the project's changelog (.rules/changelog.md).
#
# - A pull request into dev that changes package code (pamica/ outside its
# tests, validate_implementations.py, pyproject.toml) must also change
# docs/changelog.md, adding its entry under "## Unreleased". A change with no
# user-visible effect carries the `skip-changelog` label instead.
# - A pull request into main is a release: the merged tree must carry the
# release's "## X.Y.Z - YYYY-MM-DD" section, which auto-tag.yml publishes as
# the GitHub release notes, and no leftover "## Unreleased" section.
#
# The version bots (auto-bump-dev.yml, sync-dev.yml) push to dev directly and
# open no pull requests, so this check never gates them.
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]

concurrency:
group: changelog-${{ github.ref }}
cancel-in-progress: true

jobs:
changelog:
name: Changelog
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Entry for a change into dev
if: github.base_ref != 'main'
env:
BASE: ${{ github.event.pull_request.base.sha }}
HEAD: ${{ github.event.pull_request.head.sha }}
SKIP: ${{ contains(github.event.pull_request.labels.*.name, 'skip-changelog') }}
run: |
set -euo pipefail
changed=$(git diff --name-only "$BASE...$HEAD")
code=$(grep -E '^(pamica/|validate_implementations\.py$|pyproject\.toml$)' <<<"$changed" | grep -v '^pamica/tests/' || true)
if [ -z "$code" ]; then
echo "No package code changed; no entry required."
elif grep -qx 'docs/changelog.md' <<<"$changed"; then
echo "docs/changelog.md is updated."
elif [ "$SKIP" = "true" ]; then
echo "The skip-changelog label is set."
else
echo "::error::This pull request changes package code but not docs/changelog.md. Add an entry under '## Unreleased' (.rules/changelog.md), or add the skip-changelog label when the change has no user-visible effect."
printf 'Changed code:\n%s\n' "$code"
exit 1
fi

- name: Release section for a release into main
if: github.base_ref == 'main'
run: |
set -euo pipefail
version=$(grep -m1 '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/')
release="${version%.dev*}"
python3 scripts/changelog_section.py "$release" --check
if grep -q '^## Unreleased' docs/changelog.md; then
echo "::error::docs/changelog.md still has an '## Unreleased' section. Release prep renames it to '## $release - YYYY-MM-DD'; changes merged to dev afterward belong in that section or in the next release."
exit 1
fi
echo "docs/changelog.md carries the $release section."
54 changes: 54 additions & 0 deletions .rules/changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Changelog Standards

## One changelog
- **`docs/changelog.md` is the changelog.** It is published at
<https://eeglab.org/pAMICA/changelog/>, linked from PyPI (`[project.urls]`),
and its release sections are the GitHub release notes.
- **The root `CHANGELOG.md` only points to it.** Never add entries there.

## Every user-visible change gets an entry, in the same pull request
- A pull request into `dev` that changes user-visible behavior adds its entry under `## Unreleased`.
User-visible behavior covers results, defaults, parameters, errors, saved formats, supported data, dependencies and installation.
- **`.github/workflows/changelog.yml` enforces it for package code:**
`pamica/` outside its tests, `validate_implementations.py` and `pyproject.toml`.
- **The `skip-changelog` label is for changes with no user-visible effect:**
a refactor with byte-identical behavior, or a test-only or tooling-only change.
When in doubt, write the entry.
- CI, documentation and paper changes may add entries (under `### Continuous integration` or `### Documentation`); the check does not require them.

## What an entry says
- **Lead with the effect on the user,** as a bold one-line summary.
Then say what changed and why, with the issue or pull-request number.
Then say what a user has to do, for example refit, pass a setting explicitly, or re-export.
- **Changes to default results go into the release's opening warning** as well
(the `!!! warning` block at the top of the section), so a user comparing against an earlier version sees them first.
- **Deliberate divergences from the Fortran reference** also get a row in `docs/guides/amica-differences.md`.
- **Keep one coherent account.**
- File each entry under the existing topic subheadings of `## Unreleased` (`### Fitting follows the reference`, `### Persistence and exports`, ...) rather than one section per pull request.
- When a later change supersedes an earlier entry in the same release, edit that entry rather than appending a contradiction.
- **Quote measured figures with their conditions:** data, iterations, backend and the reference build.
- **Style:**
- American English. No em-dashes. Semantic line breaks. Define an abbreviation on first use.
- Honest, nuanced wording: state what was measured and what it implies, without negation-contrast pairs ("X, not Y") or candor announcements ("plainly").
- **Links:** relative links to docs pages are fine, inline or reference-style.
`scripts/changelog_section.py` rewrites them to site URLs for the release notes and leaves code spans and fenced code untouched.

## Format
- Release headings are `## X.Y.Z - YYYY-MM-DD`, newest first, with at most one `## Unreleased` section above them.
The date is the release's publication date in UTC, the date GitHub and PyPI show.
- `pamica/tests/test_changelog.py` enforces the format and runs the release-notes extractor on the real file.

## At release time
1. **Release-prep pull request into `dev`:**
- Rename `## Unreleased` to `## X.Y.Z - YYYY-MM-DD` (the planned release date).
- Re-read the section as one release note.
- For a minor or major release, set the version with `uv run python scripts/sync_version.py sync X.Y.0.dev0` and `uv lock`. Patch releases need no version change; the chain strips `.devN`.
Avoid the commit subject prefix "Bump version to", which the CI guards reserve for the bot.
2. **Merge `dev` into `main`** with a regular merge commit (`.rules/git.md`).
The changelog check on that pull request requires the release's section and no leftover `## Unreleased`.
3. **`auto-tag.yml` publishes the section as the GitHub release notes,** with GitHub's generated pull-request list appended.
4. **After the release,** the next change into `dev` starts a fresh `## Unreleased` section above the release.

## Refreshing an existing release's notes
`gh release edit vX.Y.Z --notes-file <(uv run python scripts/changelog_section.py X.Y.Z)`.
Editing a release fires only the `edited` event, which neither `publish.yml` nor `release-binaries.yml` listens to.
15 changes: 15 additions & 0 deletions .rules/ci_cd.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,21 @@ recording:
is a first estimate pending an actual run. GitHub auto-disables a schedule
trigger after 60 days of repo inactivity; re-enable via the Actions tab or
`gh workflow enable weekly-macos-slow.yml` if Sunday runs stop appearing.
- **Code-path filter:** `ci.yml`'s Python jobs run only when a change touches
code. Markdown, the docs site, the paper (with the rebuilt `paper.pdf`),
citation metadata and the non-Python records under `.context/` skip them;
Python files under `.context/` and the test-loaded
`.context/issue-27/ensemble.npz` still run them. `typos.yml` runs on every
change. `draft-pdf.yml` commits the rebuilt PDF back on `dev` and `main`
only, rebasing onto the branch head first because `auto-bump-dev.yml` can
push while it builds.
- **`changelog.yml`** -- on every pull request: into `dev`, a change to
package code (`pamica/` outside its tests, `validate_implementations.py`,
`pyproject.toml`) must also change `docs/changelog.md`, unless the PR carries
the `skip-changelog` label; into `main` (a release), the merged tree must
carry the release's `## X.Y.Z - YYYY-MM-DD` section and no `## Unreleased`.
`auto-tag.yml` publishes that section as the GitHub release notes, through
`scripts/changelog_section.py` (`.rules/changelog.md`).
- **`release-binaries.yml`**, **`publish.yml`**, **`auto-tag.yml`**,
**`auto-bump-dev.yml`**, **`sync-dev.yml`**, **`docs.yml`**, **`typos.yml`**,
**`draft-pdf.yml`** each own one concern (native-binary release assets, PyPI
Expand Down
1 change: 1 addition & 0 deletions .rules/code_review.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ When the `pr-review-toolkit` plugin is available, use it after creating PRs to c
### Before Committing
- [ ] Code compiles/runs without warnings
- [ ] Tests pass (real tests, no mocks)
- [ ] A user-visible change has its entry under `## Unreleased` in `docs/changelog.md` (`.rules/changelog.md`)
- [ ] No debug code left (print statements, TODO hacks)
- [ ] No sensitive data in code or logs

Expand Down
4 changes: 4 additions & 0 deletions .rules/git.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@

## Versioning (automated)
Versions are managed by CI; do not hand-edit `pyproject.toml` to bump.
The one manual step is choosing a minor or major release: release prep sets
`X.Y.0.dev0` with `scripts/sync_version.py` in a pull request into `dev`, and
renames the changelog's `## Unreleased` section to the release
(`.rules/changelog.md`).

| Event | Version effect | Workflow |
|---|---|---|
Expand Down
7 changes: 5 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,9 @@ update from a merged state seeded through `load_comp_list` matches it to float64
5. **Test:** Real data only (sample EEG + Fortran binary); see `.rules/testing.md`.
6. **Document failures:** Log dead ends in `.context/scratch_history.md`.
7. **Commit:** Atomic, <50 chars, no emojis, no AI attribution.
8. **PR + review:** Run `/review-pr` and address all findings (`.rules/code_review.md`).
9. **Merge:** CI green first (see below), then **squash merge** (`gh pr merge <n> --squash --delete-branch`).
8. **Changelog:** A user-visible change adds its entry under `## Unreleased` in `docs/changelog.md`, in the same PR (`.rules/changelog.md`; the `Changelog` check enforces it for package code).
9. **PR + review:** Run `/review-pr` and address all findings (`.rules/code_review.md`).
10. **Merge:** CI green first (see below), then **squash merge** (`gh pr merge <n> --squash --delete-branch`).

## [CRITICAL] Core Principles
- **NO MOCKS:** Validate against real sample data and the Fortran binary, never fabricated data. Details: `.rules/testing.md`.
Expand All @@ -242,6 +243,7 @@ update from a merged state seeded through `load_comp_list` matches it to float64
- `.rules/backend_parity.md` - No one-off backends; shared decisions, cross-backend tests
- `.rules/python.md` - UV, ruff, ty
- `.rules/git.md` - Commit/branch conventions
- `.rules/changelog.md` - Changelog entries, format, and the release-notes mechanism
- `.rules/code_review.md` - PR review toolkit and checklist
- `.rules/ci_cd.md` - GitHub Actions setup
- `.rules/documentation.md` - Docs conventions
Expand All @@ -259,6 +261,7 @@ update from a merged state seeded through `load_comp_list` matches it to float64

## Project Docs (top-level)
- `README.md` - Overview and quick start
- `CHANGELOG.md` - Pointer to the changelog, `docs/changelog.md`

---
Remember: parity with the Fortran reference is the definition of done. Check `.rules/` for detailed guidance.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Changelog

pamica's changelog is [`docs/changelog.md`](docs/changelog.md),
published at <https://eeglab.org/pAMICA/changelog/>.
It lists the user-visible changes in every release since 0.1.0, newest first,
with the changes merged since the last release under **Unreleased**.
Each release's section is also its note on the
[GitHub releases page](https://github.com/sccn/pAMICA/releases).
42 changes: 30 additions & 12 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Changelog

Release notes are also published on the
User-visible changes to pamica, newest first.
Changes merged to `dev` since the last release collect under **Unreleased**;
at release time that heading becomes the version and its date.
Each release's section is also its note on the
[GitHub releases page](https://github.com/sccn/pAMICA/releases).

## Unreleased
Expand Down Expand Up @@ -664,8 +667,23 @@ and every backend's fitting follows the Fortran reference more closely.
Python files under `.context/` still run the full CI.
- **The rebuilt `paper.pdf` is committed back on `dev` and `main` only.**
Feature branches build it as an artifact, so a PR's head is never a bot commit whose checks wait for approval.
The commit rebases onto the branch head before pushing, because the version bump can land while the PDF builds.
- **A changelog check runs on every pull request.**
A change to package code into `dev` must add its entry to this changelog, or carry the `skip-changelog` label when it has no user-visible effect.
A release into `main` must carry its dated section.
- **Release notes come from this changelog.**
`auto-tag.yml` publishes the release's section, extracted by `scripts/changelog_section.py` with its links made absolute, and appends GitHub's generated pull-request list.
The notes of the earlier releases were refreshed the same way.

## 0.3.3
### Project

- **A root `CHANGELOG.md` points to this changelog,**
and the package metadata links it and the documentation site, so PyPI shows both.
- **Release headings carry their dates** (`## X.Y.Z - YYYY-MM-DD`): each release's publication date in UTC, or its tag date where no GitHub release exists;
`pamica/tests/test_changelog.py` checks the format.
- **`.rules/changelog.md` records the practice:** what an entry says, when the `skip-changelog` label applies, and the release-prep steps.

## 0.3.3 - 2026-09-01

MLX fitting parity (convergence stops, component sharing, Newton, all five
source-density families), best-of-N restarts on every backend, the
Expand Down Expand Up @@ -1043,7 +1061,7 @@ external tester (#221).
bundled sample reproduces its previous `comp_list` and log-likelihood bit for
bit.

## 0.3.2
## 0.3.2 - 2026-08-16

Rank-deficient input support across every backend, a much faster default block
size, and a reproducible Fortran reference for parity runs.
Expand Down Expand Up @@ -1103,7 +1121,7 @@ size, and a reproducible Fortran reference for parity runs.
(missing these keys) still load, falling back to the Fortran-faithful
defaults.

## 0.3.1
## 0.3.1 - 2026-07-19

Rho-rate schedule fixes across all backends and a reproducible-seed option in the
native binary build.
Expand All @@ -1126,7 +1144,7 @@ native binary build.
components, not a dynamics bug (identical init gives matching results); the
optional init-robustness enhancement is tracked in #198.

## 0.3.0
## 0.3.0 - 2026-07-18

MNE-Python compatibility layer (epic #139), additive: the scikit-learn-style
`AMICA` API and the byte-identical EEGLAB I/O are unchanged.
Expand Down Expand Up @@ -1180,7 +1198,7 @@ MNE-Python compatibility layer (epic #139), additive: the scikit-learn-style
`AMICA.mir`/`pmi` (#133); the results match the array API exactly (phase 4,
#143).

## 0.2.2
## 0.2.2 - 2026-07-18

GitHub repository rename to pAMICA and a `__version__` fix.

Expand All @@ -1197,7 +1215,7 @@ GitHub repository rename to pAMICA and a `__version__` fix.
snippets are updated to match. GitHub redirects the old repo URLs, and the
package/import name stays lowercase `pamica` (#184).

## 0.2.1
## 0.2.1 - 2026-07-18

PyPI publishing, release-metadata sync, the pAMICA display title, and
native-engine documentation.
Expand All @@ -1218,7 +1236,7 @@ native-engine documentation.
backend on any platform, not only through the bundled macOS `amica15mac`
fixture (#147 phase 5, #179).

## 0.2.0
## 0.2.0 - 2026-07-18

Package rename to align with the reserved PyPI name.

Expand All @@ -1229,7 +1247,7 @@ Package rename to align with the reserved PyPI name.
domain (`eeglab.org/pyAMICA`), and the release-asset repository are unchanged
(#176).

## 0.1.3
## 0.1.3 - 2026-07-18

Native Fortran run engine, separation-quality metrics, LLt output parity, and
the `loadmodout` byte-order fix.
Expand Down Expand Up @@ -1309,7 +1327,7 @@ the `loadmodout` byte-order fix.
`rho0=1.5`). Affected `numpy_impl.viz.plot_pdf_fits`; the fit path was never
affected, as it uses its own log-space implementation (#136).

## 0.1.2
## 0.1.2 - 2026-07-14

Outlier-rejection parity in the NumPy backend, repo-wide type-checking, and the
full validation-evidence documentation.
Expand All @@ -1328,7 +1346,7 @@ full validation-evidence documentation.
(cross-backend equivalence matrix and IC topomaps), the EEGLAB drop-in
round-trip, and the other validated behaviors (#108).

## 0.1.1
## 0.1.1 - 2026-07-13

Validation-methodology and correctness fixes since 0.1.0.

Expand All @@ -1347,7 +1365,7 @@ Validation-methodology and correctness fixes since 0.1.0.
- Corrected a stale float32-speedup claim and added a funding acknowledgment
(#114).

## 0.1.0
## 0.1.0 - 2026-07-11

First public release.

Expand Down
Loading
Loading