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
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
"metadata": {
"description": "The Pipelex plugin for Claude Code and Codex: build and run AI methods from your agent.",
"version": "0.16.0"
"version": "0.17.0"
},
"plugins": [
{
Expand Down
9 changes: 5 additions & 4 deletions .claude/skills/release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ The procedure is the workspace release play, [`docs/workspace/releasing.md`](../

## What ships

The repository itself is the artifact, and the merge to `main` publishes it. On Claude and Codex, install is a marketplace add against the GitHub repo — the README's quick start gives `claude plugin marketplace add Pipelex/pipelex-plugins` and `codex plugin marketplace add Pipelex/pipelex-plugins`, neither carrying a ref, so what is served is the repo's default branch, which is `main` (`origin/HEAD` points at `origin/main`). So when the merge lands, an install or a marketplace refresh hands users the `pipelex/` and `pipelex-codex/` trees exactly as the release branch left them, each carrying the new version in its own generated `plugin.json`. Mistral Vibe is served by no marketplace — the install page, `docs/install.md`, has the user point `skill_paths` at a local checkout of `pipelex-vibe/` — so a Vibe user takes the release by pulling `main` themselves, and the tree they get names no version at all (see **Particulars**).
The repository itself is the artifact, and the merge to `main` publishes it. On Claude and Codex, install is a marketplace add against the GitHub repo — the README's quick start gives `claude plugin marketplace add Pipelex/pipelex-plugins` and `codex plugin marketplace add Pipelex/pipelex-plugins`, neither carrying a ref, so what is served is the repo's default branch, which is `main` (`origin/HEAD` points at `origin/main`). So when the merge lands, an install or a marketplace refresh hands users the `pipelex/` and `pipelex-codex/` trees exactly as the release branch left them, each carrying the new version in its own generated `plugin.json`. Mistral Vibe is served by no marketplace — the install page, `docs/install.md`, has the user clone the repository and link its `pipelex-vibe/` into Vibe's plugins directory — so a Vibe user takes the release by pulling `main` into that clone themselves, and the tree they get names the new version in its generated `pipelex-vibe/plugin.json` (see **Particulars**).

The same merge tags the release. `.github/workflows/release-tag.yml` runs on the push to `main`: it reads `targets/prod.toml`'s `[plugin].version`, and when that push raised the version and the tag does not exist yet, it creates and pushes the annotated tag `vX.Y.Z` on the merge commit. A push that leaves the version alone or lowers it tags nothing, even with the tag missing, so no later commit or revert can take a release's tag. A missing `## [X.Y.Z] - ` heading in `CHANGELOG.md` is a warning in that run rather than a refusal, since the merge has already published the version: a missing entry is stopped before the merge or not at all, by this skill reading the `Changelog entry` check (see **CI on the release pull request**), since no ruleset requires it. The tag is the pin an outside target renders the skills at (`docs/development.md`, "Rendering for an outside target"). Never create it by hand ahead of the merge: the workflow tags the merge commit, and a release whose tag already sits at another commit fails its run until that tag is deleted and the run re-run. No GitHub Release is made. The landing reads the run and the tag, and prints the rest of this block for the session to run:

Expand All @@ -32,6 +32,7 @@ git -C <main> log origin/main -3 --oneline
git -C <main> show origin/main:targets/prod.toml | grep '^version' # X.Y.Z
git -C <main> show origin/main:.claude-plugin/marketplace.json | grep '"version"' # the same number
git -C <main> show origin/main:pipelex/.claude-plugin/plugin.json | grep '"version"'
git -C <main> show origin/main:pipelex-vibe/plugin.json | grep '"version"'
```

A run still `queued` or `in_progress` has no conclusion and has pushed no tag yet, which reads exactly like a failed publish: wait for it (`gh run watch <databaseId> --repo Pipelex/pipelex-plugins`) before reading the tag. When it failed, read its log. A transient failure is re-run from the Actions page: a re-run repeats the same push, so it tags the merge commit even after later pushes to `main`, which never tag a version they did not raise. A re-run executes the workflow as the merge commit holds it, though, so it cannot cure a defect in `release-tag.yml` itself, and GitHub allows one only for 30 days. In either case the version is already published on `main`, so cut the tag by hand at the merge commit, as the play allows for a publish whose tag is missing — `git -C <main> tag -a vX.Y.Z -m vX.Y.Z <merge SHA> && git -C <main> push origin vX.Y.Z` — then re-run the run once more, and fix the workflow on `dev` for the next release. The landing needs that run to succeed as well as the tag, and a re-run that finds the tag on its own commit succeeds before it reaches the tagging step. When it still fails, because the defect sits before that check, or when the re-run window has passed, the landing stays `release-unverified` whatever is tagged: verify the release by hand from the block above, and say in the landing's report that it was verified by hand and why. A run that failed because `vX.Y.Z` already sits at another commit names that commit: read why before deleting the tag and re-running.
Expand All @@ -43,7 +44,7 @@ The number to read as *the current version* — the one the play's pre-flight ch
- **Every `targets/*.toml` but `defaults.toml`** — the `[plugin].version`. Enumerate the directory (`ls targets/*.toml`) instead of trusting a written list, so a target added since this page was written is never silently missed. Every target takes the **same** string: `scripts/check.py`'s `check_matched_target_versions` fails the release when they drift, with `Target versions must be in lockstep`.
- **`.claude-plugin/marketplace.json`** — `metadata.version`, set to that same number. What the check enforces is a floor rather than an equality: it fails when `metadata.version` is lower than the highest Claude target version (`metadata.version '…' lags behind Claude target version '…'`), so a forgotten bump is caught while a marketplace number that has run ahead is not.
- **The lock: none.** `uv.lock` locks `pipelex-plugins-tools`, the repo's internal tooling package, and its `pyproject.toml` version is not the plugin version — the file says so in its own comment: "Internal tooling version — not tied to the plugin version in plugin.json/marketplace.json. This package is not published; bump only when the tooling itself changes meaningfully." A release therefore touches neither `pyproject.toml` nor `uv.lock`, and there is no lock step to run.
- **Also stamped, by `make build` and never by hand:** `pipelex/.claude-plugin/plugin.json` and `pipelex-codex/.codex-plugin/plugin.json`. `scripts/gen_skill_docs.py` overlays each target's `[plugin]` name, description and version onto its platform's `plugin-base.json`, so those manifests only catch up with the TOMLs once the build has run. Run `make build` immediately after the version edits; it is what makes the second pass of the gates green.
- **Also stamped, by `make build` and never by hand:** `pipelex/.claude-plugin/plugin.json`, `pipelex-codex/.codex-plugin/plugin.json` and `pipelex-vibe/plugin.json`. `scripts/gen_skill_docs.py` overlays each target's `[plugin]` name, description and version onto its platform's `plugin-base.json`, so those manifests only catch up with the TOMLs once the build has run. Run `make build` immediately after the version edits; it is what makes the second pass of the gates green.

## Gates

Expand All @@ -56,7 +57,7 @@ Run inside the worktree, in this order, before the commit. Every one is blocking

## The release commit

Every `targets/*.toml` the enumeration turned up — `prod.toml`, `codex.toml` and `mistral-vibe.toml` today — plus `.claude-plugin/marketplace.json`, `CHANGELOG.md`, the two generated manifests `pipelex/.claude-plugin/plugin.json` and `pipelex-codex/.codex-plugin/plugin.json`, and anything else `make build` or `make agent-check` rewrote. Staged by name. A version-only release usually rewrites just those two manifests, which is exactly what the previous release commits carried, but stage what the build actually changed rather than that expectation.
Every `targets/*.toml` the enumeration turned up — `prod.toml`, `codex.toml` and `mistral-vibe.toml` today — plus `.claude-plugin/marketplace.json`, `CHANGELOG.md`, the three generated manifests `pipelex/.claude-plugin/plugin.json`, `pipelex-codex/.codex-plugin/plugin.json` and `pipelex-vibe/plugin.json`, and anything else `make build` or `make agent-check` rewrote. Staged by name. A version-only release usually rewrites just those three manifests, which is exactly what the previous release commits carried, but stage what the build actually changed rather than that expectation.

## CI on the release pull request

Expand All @@ -75,7 +76,7 @@ What is still *only* this skill's job: CI cannot tell whether the vendored hook

- **The changelog headings carry no `v`.** Every entry in `CHANGELOG.md` reads `## [X.Y.Z] - YYYY-MM-DD` — `## [0.5.0] - 2026-08-01` — which is where the repo departs from the play's default `## [vX.Y.Z]`. The `v` still appears in the branch name, the commit subject, the pull request title and the tag. `Changelog entry` now enforces the no-`v` form on the pull request into `main` — it greps for `## [X.Y.Z] - ` anchored at the start of a line — but only for the version being shipped, so keeping the rest of the file consistent with itself is still this skill's job.
- **No pre-release form.** `scripts/check.py` parses a version as `tuple(int(part) for part in version.split("."))`, so anything that is not dotted integers fails `make check` with `invalid numeric version`. Ship a plain `X.Y.Z`.
- **The Vibe target has no manifest to stamp.** `targets/mistral-vibe.toml` builds `pipelex-vibe/`, which ships skills and hooks only; a plugin manifest is emitted for the Claude and Codex platforms alone. There is no `pipelex-vibe/**/plugin.json` to edit, and `make check` fails outright if a `.claude-plugin/` or `.codex-plugin/` directory turns up in that output. The Vibe version lives in its TOML and nowhere else.
- **The Vibe manifest sits at the root of its output.** `targets/mistral-vibe.toml` builds `pipelex-vibe/` as a native Agent Plugins 1.0 plugin, whose manifest is `pipelex-vibe/plugin.json` rather than a file under a dot-directory: the build overlays the target's version onto `.vibe-plugin/plugin-base.json`, and `check_target_plugin_versions` holds it to the TOML like the other two. `make check` fails outright if a `.claude-plugin/` directory turns up in that output, since Vibe refuses a plugin with two manifests.
- **The Codex marketplace specs carry no version field.** `packaging/codex-marketplace.json` is canonical and `.agents/plugins/marketplace.json` is its byte-for-byte discovery copy, kept in step by the build. Neither has a `version` key, so there is nothing in either to bump.
- **A release does not refresh the vendored hook bundle; it refuses a stale one.** `templates/hooks/assets/check.mjs` is built in the `js/` directory of `pipelex-sdk` and copied in by `make vendor-hook` (from `SDK_JS_DIR`, defaulting to `../pipelex-sdk/js`), and no build step runs it. The first gate is what notices a bundle that is behind, and the re-vendor it asks for reaches `dev` on its own branch before the release is cut, never inside the release commit, which carries the version files and what the build restamps. `make check`, in the second gate and in `Checks`, separately refuses a bundle whose provenance names an unreleased engine or no SDK commit.
- **The `codex-use-local`, `codex-use-official`, `codex-refresh` and `codex-status` targets are dogfooding, not release steps.** They re-point a local Codex install at this checkout or back at the published marketplace, changing the developer's machine and never the repo, so none of them belongs in a release run.
2 changes: 1 addition & 1 deletion .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:

- uses: actions/setup-python@v5
with:
python-version: "3.13"
python-version-file: .python-version

- name: Install uv (with cache)
uses: astral-sh/setup-uv@v7
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/recipes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ on:
- "Makefile"
- "pyproject.toml"
- "uv.lock"
- ".python-version"

permissions:
contents: read
Expand Down Expand Up @@ -65,7 +66,7 @@ jobs:

- uses: actions/setup-python@v5
with:
python-version: "3.13"
python-version-file: .python-version

- name: Install uv (with cache)
uses: astral-sh/setup-uv@v7
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:

- uses: actions/setup-python@v5
with:
python-version: "3.13"
python-version-file: .python-version

# `tests/unit/test_hook_corpus.py` runs the vendored hook bundle, built for
# node22, over the MTHDS Test Corpus. It fails under `CI` when no `node` is
Expand Down
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.13
Loading
Loading