Skip to content

Commit 4cd8cd7

Browse files
authored
Merge pull request #515 from Integration-Automation/release/1.0.0
Release 1.0.0; the publish job releases a hand-set version as written
2 parents 462a2a6 + 257f8c5 commit 4cd8cd7

13 files changed

Lines changed: 252 additions & 44 deletions

File tree

‎.github/workflows/stable.yml‎

Lines changed: 11 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,10 @@ jobs:
123123

124124
publish:
125125
needs: test
126-
# Releases ride on merges to main: this job bumps the patch version,
127-
# uploads to PyPI with the PYPI_API_TOKEN secret, then pushes the bump
128-
# commit (marked [skip ci]) and its v* tag. The guard is what keeps it
126+
# Releases ride on merges to main: this job picks the version
127+
# (scripts/stable_release.py: the next patch, or the untagged version
128+
# pyproject.toml declares), uploads to PyPI with the PYPI_API_TOKEN
129+
# secret, then pushes the bump commit (marked [skip ci]) and its v* tag. The guard is what keeps it
129130
# off pull requests and the nightly schedule, which the same workflow
130131
# also triggers on — without it every PR would cut a release.
131132
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
@@ -149,33 +150,12 @@ jobs:
149150
- name: Install the hash-locked build tooling
150151
run: "python -m pip install --require-hashes --only-binary :all: -r .github/requirements/publish.txt"
151152

152-
- name: Bump patch version in pyproject.toml
153+
# One patch above the declared version when that one is already tagged
154+
# (every ordinary merge); the declared version itself when it is not,
155+
# which is how a minor or major release is made. See the script.
156+
- name: Choose the version to release
153157
id: bump
154-
run: |
155-
python <<'PY'
156-
import os
157-
import re
158-
import pathlib
159-
160-
path = pathlib.Path("pyproject.toml")
161-
text = path.read_text(encoding="utf-8")
162-
match = re.search(r'^version\s*=\s*"(\d+)\.(\d+)\.(\d+)"', text, flags=re.M)
163-
if not match:
164-
raise SystemExit("version line not found in pyproject.toml")
165-
major, minor, patch = (int(part) for part in match.groups())
166-
new_version = f"{major}.{minor}.{patch + 1}"
167-
text = re.sub(
168-
r'^version\s*=\s*"\d+\.\d+\.\d+"',
169-
f'version = "{new_version}"',
170-
text,
171-
count=1,
172-
flags=re.M,
173-
)
174-
path.write_text(text, encoding="utf-8")
175-
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as fh:
176-
fh.write(f"new_version={new_version}\n")
177-
print(f"Bumped to {new_version}")
178-
PY
158+
run: python scripts/stable_release.py
179159

180160
# --no-isolation: the backend is the setuptools the lock pins, not the
181161
# newest one downloaded into a fresh build environment.
@@ -193,7 +173,8 @@ jobs:
193173
git config user.name "github-actions[bot]"
194174
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
195175
git add pyproject.toml
196-
git commit -m "Bump version to ${{ steps.bump.outputs.new_version }} [skip ci]"
176+
# A version set by hand is already what main declares: nothing to commit.
177+
git diff --cached --quiet || git commit -m "Bump version to ${{ steps.bump.outputs.new_version }} [skip ci]"
197178
git tag "v${{ steps.bump.outputs.new_version }}"
198179
git pull --rebase origin main
199180
git push origin main

‎CHANGELOG.md‎

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
This file records user-visible compatibility changes. Detailed development
44
notes are recorded in `docs/updates/` (index: `docs/updates/README.md`).
55

6-
The format follows Keep a Changelog. Until 1.0, breaking changes are permitted
7-
only when documented here with a migration path.
6+
The format follows Keep a Changelog and, from 1.0.0, semantic versioning as
7+
`docs/API_LIFECYCLE.md` scopes it: the stable surface is `je_auto_control.api`,
8+
and a breaking change to it needs a deprecation here first. Beta and
9+
experimental surfaces change with a release note in this file.
810

911
New entries go under `## Unreleased`. The version bump on `main` is automated
1012
and does not touch this file, so after a release tag appears, move the entries
@@ -13,6 +15,21 @@ it shipped into a `## [x.y.z] - date` section of their own; the tag's
1315

1416
## Unreleased
1517

18+
## [1.0.0] - 2026-10-09
19+
20+
The first release under semantic versioning. `docs/API_LIFECYCLE.md` says what
21+
the number promises: the stable surface is `je_auto_control.api` and the rows
22+
`docs/CAPABILITY_MATRIX.md` marks stable; everything else keeps its beta or
23+
experimental label. No name is removed or renamed by this release itself. This
24+
section also holds what 0.0.223 to 0.0.234 shipped, which was never moved out
25+
of `Unreleased`.
26+
27+
- The package classifier is `Development Status :: 5 - Production/Stable`
28+
(was `2 - Pre-Alpha`).
29+
- Releases: a merge to `main` still releases the next patch; a minor or major
30+
version is released by declaring it in `pyproject.toml`
31+
(`scripts/stable_release.py`).
32+
1633
### Added
1734

1835
- Action journal: `start_action_journal` / `stop_action_journal` /

‎CLAUDE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,4 +221,4 @@ Workspace rule shared by every repository under `D:\Codes` (full text: `D:\Codes
221221
- JSON action command names use the `AC_` prefix (e.g. `AC_click_mouse`); MCP tools use `ac_`.
222222
- Platform backends are named `{platform}_{function}.py` (e.g. `win32_ctype_mouse_control.py`).
223223
- Virtual key mappings live in `core/utils/*_vk.py` per platform.
224-
- Two PyPI packages, both published by CI: a push to `main` releases `je_auto_control` (the `publish` job of `stable.yml`), and a push to `dev` that passes the headless suite and changes what the package ships releases `je_auto_control_dev` (the `publish-dev` job of `dev.yml`, `scripts/dev_release.py`). Never bump a version by hand; the version in `dev.toml` is only a floor. `dev.toml` must declare what `pyproject.toml` declares, so a change to dependencies, extras, scripts, entry points or `[tool.setuptools]` goes into both files (`test/unit_test/headless/test_dev_toml_parity.py` fails otherwise). Both publish jobs hold the PyPI token, so they install only the hash-locked `.github/requirements/publish.txt` and build with `python -m build --no-isolation`: a new tool, or a higher floor in `build-system.requires`, needs the lock regenerated with the command at the top of `.github/requirements/publish.in` (`test/unit_test/headless/test_publish_tooling_lock.py` fails otherwise).
224+
- Two PyPI packages, both published by CI: a push to `main` releases `je_auto_control` (the `publish` job of `stable.yml`), and a push to `dev` that passes the headless suite and changes what the package ships releases `je_auto_control_dev` (the `publish-dev` job of `dev.yml`, `scripts/dev_release.py`). Never bump a patch version by hand: `scripts/stable_release.py` releases one patch above the version `pyproject.toml` declares whenever that version already has a `v*` tag. A minor or major release is the one exception — set the new version in `pyproject.toml` in the pull request that ships it, and the job releases that untagged version as written (`test/unit_test/headless/test_stable_release.py`). The version in `dev.toml` is only a floor. `dev.toml` must declare what `pyproject.toml` declares, so a change to dependencies, extras, scripts, entry points or `[tool.setuptools]` goes into both files (`test/unit_test/headless/test_dev_toml_parity.py` fails otherwise). Both publish jobs hold the PyPI token, so they install only the hash-locked `.github/requirements/publish.txt` and build with `python -m build --no-isolation`: a new tool, or a higher floor in `build-system.requires`, needs the lock regenerated with the command at the top of `.github/requirements/publish.in` (`test/unit_test/headless/test_publish_tooling_lock.py` fails otherwise).

‎architecture.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ entry points → execution core (`utils/executor/`) → headless capabilities (`
3636
| `examples/`, `benchmarks/` | Runnable example scripts; latency smoke benchmark. |
3737
| `docker/`, `k8s/helm/`, `ci_templates/` | Container images and backend verification harnesses, Helm chart, GitLab CI template. |
3838
| `browser-extension/`, `AutoControl/`, `exe/`, `autocontrol_driver/` | Manifest v3 companion extension, project-template sample, packaged GUI launcher, driver build script. |
39-
| `scripts/` | Release tooling, not shipped: `dev_release.py` numbers and gates the dev-channel package (§3). |
39+
| `scripts/` | Release tooling, not shipped: `dev_release.py` numbers and gates the dev-channel package, `stable_release.py` picks the version a push to `main` releases (§3). |
4040

4141
## 3. Entry points and public interfaces
4242

@@ -53,7 +53,7 @@ entry points → execution core (`utils/executor/`) → headless capabilities (`
5353
| LSP | `autocontrol-lsp` → `autocontrol_lsp.server.server:run`; `python -m autocontrol_lsp.server` | Command list is read from the live executor. |
5454
| GUI | `start_autocontrol_gui()` in `gui/__init__.py`; `exe/start_autocontrol_gui.py` | Needs `pip install je_auto_control[gui]`; PySide6 is imported only under `gui/`. |
5555
| Action lint | `python -m je_auto_control.utils.action_lint` | Used by `.github/workflows/action-json-lint.yml`. |
56-
| PyPI packages | `je_auto_control` (stable), `je_auto_control_dev` (dev channel) | Both ship the same `je_auto_control` import package. Stable: a push to `main` runs the `publish` job of `stable.yml`, which bumps `pyproject.toml`, uploads and tags. Dev: the `publish-dev` job of `dev.yml` runs after the headless suite on a push to `dev`, builds from `dev.toml` and uploads when the commit is still the tip of `dev` and the wheel differs from the newest published one; `scripts/dev_release.py` takes the version from PyPI (newest release plus one patch), so nothing is committed back. `dev.toml` declares what `pyproject.toml` declares (`test_dev_toml_parity.py`). Both jobs hold the PyPI token and install nothing but the hash-locked `.github/requirements/publish.txt` (`build`, `twine` and the `setuptools` backend, generated from `publish.in` beside it), then build with `python -m build --no-isolation`, so the backend is the locked one (`test_publish_tooling_lock.py`). |
56+
| PyPI packages | `je_auto_control` (stable), `je_auto_control_dev` (dev channel) | Both ship the same `je_auto_control` import package. Stable: a push to `main` runs the `publish` job of `stable.yml`, which takes the version from `scripts/stable_release.py` (one patch above the declared version when that one is tagged; the declared version itself when it is not, which is how a minor or major release such as 1.0.0 is made), uploads and tags. Dev: the `publish-dev` job of `dev.yml` runs after the headless suite on a push to `dev`, builds from `dev.toml` and uploads when the commit is still the tip of `dev` and the wheel differs from the newest published one; `scripts/dev_release.py` takes the version from PyPI (newest release plus one patch), so nothing is committed back. `dev.toml` declares what `pyproject.toml` declares (`test_dev_toml_parity.py`). Both jobs hold the PyPI token and install nothing but the hash-locked `.github/requirements/publish.txt` (`build`, `twine` and the `setuptools` backend, generated from `publish.in` beside it), then build with `python -m build --no-isolation`, so the backend is the locked one (`test_publish_tooling_lock.py`). |
5757

5858
## 4. Main flows
5959

‎architecture_explore.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
> 擷取每個模組的 docstring 與頂層公開名稱;統計數字取自實際檔案,非估算。
77
> 指令數與公開 API 數以 `executor.known_commands()` 與 `je_auto_control.__all__` 在工作樹上實測取得。
88
>
9-
> **掃描時間**:2026-10-09 **版本**:`pyproject.toml` version `0.0.233` **分支**:`feat/decisions-followup`
9+
> **掃描時間**:2026-10-09 **版本**:`pyproject.toml` version `1.0.0` **分支**:`release/1.0.0`
1010
1111
---
1212

@@ -1015,6 +1015,7 @@ GUI 是**選用 extra**(`pip install je_auto_control[gui]`,PySide6),且
10151015
| `docker/` | Dockerfile ×8 + compose + 9 支驗證/伺服器腳本 | 無頭容器(`Dockerfile`)、帶 XFCE 桌面的容器(`Dockerfile.xfce`),以及四個**驗證用**映像:`Dockerfile.wayland`(sway headless,擷取路徑 + `libei_verify.py` 對真的 libei.so 解析符號)、`Dockerfile.eis`(`eis_server.py` 用 ctypes 綁 libeis 起一個真的 EIS server,`eis_verify.py` 把 libei sender 對著它跑完整握手與發送)、`Dockerfile.portal`(`portal_server.py` 自己佔住 `org.freedesktop.portal.Desktop`,真的 `dbus-daemon` + 真的 liboeffis 跑完 RemoteDesktop 交握)、`Dockerfile.ydotool`(真的 uinput 裝置,`ydotool_verify.py` 直接讀回 `/dev/input/eventN`)、`Dockerfile.seat`(`headless,libinput` + builtin seat,合成器真的吃下 ydotool 裝置,`seat_verify.py` 從 `grim -c` 的像素讀回游標落點)、`Dockerfile.x11`(真的 Xvfb + openbox,`x11_verify.py` 用 `xev` 把注入的事件從真的客戶端讀回(含 `synthetic NO`,這是 XTest 跟 `XSendEvent` 的差別),另用 ImageMagick `import` 做獨立擷取對照,跑兩種螢幕版面)。全部接在 `.github/workflows/docker.yml`。 |
10161016
| `k8s/helm/` | Helm chart | Kubernetes 部署。 |
10171017
| `ci_templates/.gitlab-ci.yml` | — | 供使用者專案複製的 GitLab CI 範本。 |
1018+
| `scripts/stable_release.py` | 只用標準函式庫的發佈輔助腳本 | stable 通道(PyPI 的 `je_auto_control`)這次要發哪一版:`pyproject.toml` 宣告的版本已經有 `v*` tag 就加一個 patch(一般的合併),還沒有 tag 就照寫的發(minor/major 版在 PR 裡手動寫進 `pyproject.toml`),但不得低於最新的 tag。把版本寫回 `pyproject.toml` 並輸出 `new_version` 給 `stable.yml` 的 `publish` job。 |
10181019
| `scripts/dev_release.py` | 只用標準函式庫的發佈輔助腳本 | dev 通道(PyPI 的 `je_auto_control_dev`)的版本與比對:`prepare` 把 `dev.toml` 寫成 `pyproject.toml`,版本取 PyPI 最新一版加一個 patch(`dev.toml` 的版本只是下限,不回寫 repo);`changed dist` 把建好的 wheel 跟 PyPI 最新一版逐檔比對,內容沒變就不上傳。只給 `dev.yml` 的 `publish-dev` job 與拋棄式的 checkout 用,因為 `prepare` 會蓋掉 `pyproject.toml`。 |
10191020
| `docs/` | Sphinx(`API`/`Eng`/`Zh`/`getting_started`) | Read the Docs 文件。 |
10201021
| `architecture_diagram/` | drawio + png | 既有的架構圖原始檔。 |
@@ -1042,7 +1043,7 @@ GUI 是**選用 extra**(`pip install je_auto_control[gui]`,PySide6),且
10421043

10431044
| 檔案 | 用途 |
10441045
| --- | --- |
1045-
| `stable.yml` | 每次 push/PR 到 `main` 與每日排程跑 Windows 五版本的示範腳本;合併到 main 後版本遞增並上傳 PyPI(使用 `PYPI_API_TOKEN`)。`publish` job 只安裝雜湊鎖定的 `.github/requirements/publish.txt`(`build`、`twine` 與建置後端 `setuptools`,由同目錄的 `publish.in` 用 `uv pip compile` 產生),並以 `python -m build --no-isolation` 建置,所以建置後端也是鎖定的那一版。 |
1046+
| `stable.yml` | 每次 push/PR 到 `main` 與每日排程跑 Windows 五版本的示範腳本;合併到 main 後由 `scripts/stable_release.py` 決定版本(`pyproject.toml` 宣告的版本已有 tag 就加一個 patch,還沒有 tag 就照寫的發,minor/major 版就是這樣發的)並上傳 PyPI(使用 `PYPI_API_TOKEN`)。`publish` job 只安裝雜湊鎖定的 `.github/requirements/publish.txt`(`build`、`twine` 與建置後端 `setuptools`,由同目錄的 `publish.in` 用 `uv pip compile` 產生),並以 `python -m build --no-isolation` 建置,所以建置後端也是鎖定的那一版。 |
10461047
| `dev.yml` | 每次 push/PR 到 `dev` 跑 headless pytest(四格:Windows 的 3.10 與 3.14、Ubuntu 3.14、macOS 3.10,都是 `quality.yml` 九格裡的格子,不量 coverage)。push 通過後由 `publish-dev` job 用 `dev.toml` 建置並上傳 `je_auto_control_dev`(同一個 `PYPI_API_TOKEN`);只有這個 commit 仍是 `dev` 的最新一筆、而且 wheel 內容跟 PyPI 最新一版不同時才上傳,版本由 `scripts/dev_release.py` 向 PyPI 查,不回寫 repo。`publish-dev` 跟 `stable.yml` 的 `publish` 一樣只安裝 `.github/requirements/publish.txt` 並以 `python -m build --no-isolation` 建置;`test_publish_tooling_lock.py` 守住這兩個 job 的安裝行、建置指令,以及鎖定版本滿足 `pyproject.toml`/`dev.toml` 的 `build-system.requires`。 |
10471048
| `release.yml` | 發佈流程(上傳步驟目前關閉)。 |
10481049
| `quality.yml` | ruff、bandit、dependency review、九格矩陣的 headless pytest(含 coverage 地板)與 mypy。 |

‎dev.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ classifiers = [
3636
"Programming Language :: Python :: 3.12",
3737
"Programming Language :: Python :: 3.13",
3838
"Programming Language :: Python :: 3.14",
39-
"Development Status :: 2 - Pre-Alpha",
39+
"Development Status :: 5 - Production/Stable",
4040
"Environment :: Win32 (MS Windows)",
4141
"Environment :: MacOS X",
4242
"Environment :: X11 Applications",

‎docs/API_LIFECYCLE.md‎

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,32 @@ available for compatibility but is not expanded with new integrations.
1212
replacement. Use `je_auto_control.utils.deprecation.deprecated`.
1313
- Breaking changes and migrations are recorded in `CHANGELOG.md`.
1414

15-
The project remains pre-1.0. A 1.0 release requires passing stable capability
16-
tests on every claimed platform, documented recovery/diagnostic behavior, and
17-
no unresolved critical security advisories.
15+
## What 1.0.0 promises
16+
17+
1.0.0 (2026-10-09) is the first release under semantic versioning. The
18+
promise covers the **stable** surface only: `je_auto_control.api`, and the
19+
capabilities `docs/CAPABILITY_MATRIX.md` marks stable. From here a breaking
20+
change to that surface needs a new major version and the deprecation window
21+
above. Beta and experimental surfaces keep the rules above and may change in a
22+
minor release with a release note.
23+
24+
The bar for 1.0 was: stable capability tests passing on every platform the
25+
matrix claims, documented recovery and diagnostic behavior, and no unresolved
26+
critical security advisory. As released:
27+
28+
- The stable rows of the matrix are run by CI on Windows, Linux X11 and Linux
29+
Wayland. On macOS the mouse / keyboard / screenshot row is still
30+
"implementation": the stable-API suite passes on the macOS runner, but no
31+
job drives a real pointer there. Treat macOS input as beta until that row
32+
says CI.
33+
- Recovery and diagnostics are `create_failure_bundle`, the trace and report
34+
writers and the action journal, described in the README and `docs/source`.
35+
- GitHub reported no open Dependabot alert and no open repository security
36+
advisory on the day of the release.
37+
38+
What has not been verified on real hardware (a Mac, a phone, a Wayland desktop
39+
session, USB devices) is listed in `Progress.md`; none of it is in the stable
40+
surface.
1841

1942
## Formats and wire versions
2043

‎docs/source/conf.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
project = 'AutoControl'
1414
copyright = '2020 ~ Now, JE-Chen' # noqa: A001 # pylint: disable=redefined-builtin # reason: Sphinx-required name
1515
author = 'JE-Chen'
16-
release = '0.0.179'
16+
release = '1.0.0'
1717

1818
# -- General configuration ---------------------------------------------------
1919

0 commit comments

Comments
 (0)