From 3dd170cf33a5f5cff77e0636759337a6685b0f61 Mon Sep 17 00:00:00 2001 From: v Date: Thu, 23 Apr 2026 22:21:30 +0300 Subject: [PATCH] =?UTF-8?q?rename:=20cacp=20=E2=86=92=20cacp-protocol=20(P?= =?UTF-8?q?yPI=20conflict)=20+=20add=20release.yml=20workflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The unprefixed `cacp` slot on PyPI is occupied by an unrelated project (Classification Algorithms Comparison Pipeline by Sylwester Czmil, v1.0.4). To publish the reference parser without colliding: - Distribution name: `cacp-protocol` - Import name: `cacp_protocol` Pure mechanical rename — no behavioral change. 42 tests pass. Files renamed: - src/cacp/{__init__,models,parser}.py → src/cacp_protocol/... - All imports updated: `from cacp.X` → `from cacp_protocol.X`, `from cacp import` → `from cacp_protocol import` `pyproject.toml` updates `name = "cacp-protocol"`. README install + usage examples updated; added a "Naming" callout explaining the collision so future readers don't wonder. Added `.github/workflows/release.yml`: - Triggers on `v*` tag push - Verifies pyproject version matches the tag - Builds sdist + wheel via `python -m build` - Publishes via `pypa/gh-action-pypi-publish` using PyPI trusted-publisher (OIDC, no token needed once configured on PyPI) - Fallback path documented in the workflow comments — operator can swap to `password: \${{ secrets.PYPI_API_TOKEN }}` if trusted-publisher isn't viable Operator next steps to ship v0.1.0: 1. Merge this PR 2. (one-time) On pypi.org: register `cacp-protocol` project (it's currently unclaimed, HTTP 404 on /pypi/cacp-protocol/json) AND add a trusted-publisher entry pointing at owner=zenprocess, repo=cacp, workflow=release.yml, environment=pypi 3. Tag `git tag v0.1.0 && git push --tags` — workflow auto-publishes Unblocks switchyard CACP consolidation epic (#1015), spec 110 chain (#1017 A/B telemetry already shipped in switchyard v5.4.10). --- .github/workflows/release.yml | 70 +++++++++++++++++++ .gitignore | 1 + cacp-python/README.md | 10 ++- cacp-python/pyproject.toml | 2 +- .../src/{cacp => cacp_protocol}/__init__.py | 4 +- .../src/{cacp => cacp_protocol}/models.py | 0 .../src/{cacp => cacp_protocol}/parser.py | 2 +- cacp-python/tests/test_conformance.py | 2 +- cacp-python/tests/test_roundtrip.py | 2 +- 9 files changed, 84 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore rename cacp-python/src/{cacp => cacp_protocol}/__init__.py (82%) rename cacp-python/src/{cacp => cacp_protocol}/models.py (100%) rename cacp-python/src/{cacp => cacp_protocol}/parser.py (99%) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..357434a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,70 @@ +name: release + +# Triggers PyPI publish when a tag matching `v*` is pushed. +# Uses PyPI trusted-publisher (OIDC) so no secrets need to live in +# this repo. Setup, one-time, on PyPI side: +# pypi.org → manage cacp-protocol → publishing → add trusted publisher +# owner: zenprocess +# repository: cacp +# workflow: release.yml +# environment: pypi +# +# Fallback: if trusted-publisher is not configured, the upload step +# fails with a loud "trusted publisher not registered" error. Add +# `PYPI_API_TOKEN` as a repo secret and replace the `with:` block on +# the publish step with `password: ${{ secrets.PYPI_API_TOKEN }}` to +# unblock without OIDC. + +on: + push: + tags: + - "v*" + +permissions: + contents: read + +jobs: + build: + name: Build sdist + wheel + runs-on: ubuntu-latest + defaults: + run: + working-directory: cacp-python + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install build tooling + run: python -m pip install --upgrade pip build + - name: Build distributions + run: python -m build + - name: Verify version matches the tag + run: | + set -eu + DIST_VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])") + TAG_VERSION="${GITHUB_REF_NAME#v}" + if [ "$DIST_VERSION" != "$TAG_VERSION" ]; then + echo "::error::pyproject version $DIST_VERSION does not match tag v$TAG_VERSION" + exit 1 + fi + - uses: actions/upload-artifact@v4 + with: + name: dist + path: cacp-python/dist/ + + publish: + name: Publish to PyPI + needs: build + runs-on: ubuntu-latest + environment: pypi + permissions: + id-token: write # required for trusted-publisher OIDC + steps: + - uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: dist/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/cacp-python/README.md b/cacp-python/README.md index 4561a33..9cbc6a8 100644 --- a/cacp-python/README.md +++ b/cacp-python/README.md @@ -1,14 +1,18 @@ -# cacp — Reference Python parser for CACP +# cacp-protocol — Reference Python parser for CACP Canonical Python reference implementation of [CACP (Compressed Agent Communication Protocol)](https://github.com/zenprocess/cacp). The spec lives in the parent [`README.md`](../README.md); this package implements the parser against it. +> **Naming**: distributed on PyPI as `cacp-protocol` (the unprefixed +> `cacp` slot was already taken by an unrelated project — Classification +> Algorithms Comparison Pipeline). Imports as `cacp_protocol`. + ## Install ``` -pip install cacp +pip install cacp-protocol ``` Zero runtime dependencies — pure stdlib. @@ -16,7 +20,7 @@ Zero runtime dependencies — pure stdlib. ## Usage ```python -from cacp import parse +from cacp_protocol import parse text = open("agent_response.txt").read() response = parse(text) diff --git a/cacp-python/pyproject.toml b/cacp-python/pyproject.toml index f9f6e85..b78cb1f 100644 --- a/cacp-python/pyproject.toml +++ b/cacp-python/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "cacp" +name = "cacp-protocol" version = "0.1.0" description = "Reference Python parser for CACP (Compressed Agent Communication Protocol)" readme = "README.md" diff --git a/cacp-python/src/cacp/__init__.py b/cacp-python/src/cacp_protocol/__init__.py similarity index 82% rename from cacp-python/src/cacp/__init__.py rename to cacp-python/src/cacp_protocol/__init__.py index ea098cf..1823555 100644 --- a/cacp-python/src/cacp/__init__.py +++ b/cacp-python/src/cacp_protocol/__init__.py @@ -3,8 +3,8 @@ See the canonical spec at https://github.com/zenprocess/cacp. """ -from cacp.parser import parse -from cacp.models import ( +from cacp_protocol.parser import parse +from cacp_protocol.models import ( CACPResponse, CANONICAL_STATUS_VALUES, CANONICAL_TESTS_BUILD_VALUES, diff --git a/cacp-python/src/cacp/models.py b/cacp-python/src/cacp_protocol/models.py similarity index 100% rename from cacp-python/src/cacp/models.py rename to cacp-python/src/cacp_protocol/models.py diff --git a/cacp-python/src/cacp/parser.py b/cacp-python/src/cacp_protocol/parser.py similarity index 99% rename from cacp-python/src/cacp/parser.py rename to cacp-python/src/cacp_protocol/parser.py index aed1d4c..53460fe 100644 --- a/cacp-python/src/cacp/parser.py +++ b/cacp-python/src/cacp_protocol/parser.py @@ -17,7 +17,7 @@ import re -from cacp.models import ( +from cacp_protocol.models import ( CACPResponse, CANONICAL_STATUS_VALUES, CANONICAL_TESTS_BUILD_VALUES, diff --git a/cacp-python/tests/test_conformance.py b/cacp-python/tests/test_conformance.py index a0953a9..a6fa59b 100644 --- a/cacp-python/tests/test_conformance.py +++ b/cacp-python/tests/test_conformance.py @@ -9,7 +9,7 @@ import pytest -from cacp import ( +from cacp_protocol import ( CANONICAL_STATUS_VALUES, CANONICAL_TESTS_BUILD_VALUES, parse, diff --git a/cacp-python/tests/test_roundtrip.py b/cacp-python/tests/test_roundtrip.py index 722527f..68ce0a5 100644 --- a/cacp-python/tests/test_roundtrip.py +++ b/cacp-python/tests/test_roundtrip.py @@ -7,7 +7,7 @@ from __future__ import annotations -from cacp import parse +from cacp_protocol import parse # Verbatim copy of the response example in the parent README.md (§"Response