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 .agents/skills/final-release-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ In final-candidate mode, when the caller provides a dedicated checkout or worktr

- Resolve and record the checkout root, current branch, `HEAD`, and clean status before auditing. Do not switch to a different checkout that happens to share the same Git object database.
- Require `TARGET=HEAD` to resolve to the checked-out commit. Treat detached HEAD, a mismatched release branch, uncommitted release-owned files, or unrelated changed paths as candidate inconsistency.
- Read `pyproject.toml`, `uv.lock`, and `tests/fixtures/released_api_contract.json` from that checkout. Verify the intended version, editable `openai-agents` lock entry, contract baseline, and contract `baseline_commit` against the release branch and commit parent.
- Read `pyproject.toml`, `uv.lock`, `.release-please-manifest.json`, `src/agents/version.py`, and `tests/fixtures/released_api_contract.json` from that checkout. Verify the intended version, editable `openai-agents` lock entry, root Release Please manifest version, literal source fallback, contract baseline, and contract `baseline_commit` against the release branch and commit parent.
- Inspect the exact commit diff and confirm that the materialized release commit owns only its expected release manifest when the invoking workflow defines one.
- Keep the checkout path as local evidence for the caller, but do not put local paths into copy-ready release text.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Compare the diff with the released BASE contract. Use `minor` as the minimum for
- a breaking change to a non-beta public API, protocol, configuration, environment, or durable serialized boundary;
- a major user-facing feature addition that warrants a minor release under repository policy.

Use `patch` otherwise. For a final candidate, verify the intended version against the branch name, `pyproject.toml`, `uv.lock`, and built package metadata when relevant. For planning mode, do not interpret unchanged version metadata as a declared patch candidate.
Use `patch` otherwise. For a final candidate, verify the intended version against the branch name, `pyproject.toml`, `uv.lock`, `.release-please-manifest.json`, the literal fallback in `src/agents/version.py`, and built package metadata when relevant. For planning mode, do not interpret unchanged version metadata as a declared patch candidate.

Capture:

Expand Down
24 changes: 13 additions & 11 deletions .agents/skills/release-candidate-prep/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ Use this skill only when the user explicitly invokes `$release-candidate-prep` a

## Non-negotiable boundaries

- Treat explicit invocation as authorization to fetch `origin/main`, create one dedicated detached release worktree, run branch-free release-readiness gates there, create or replace the local `release/v<version>` in that worktree only after those gates pass, update the three release-owned files, and create one local commit. If the branch already exists locally or remotely, the required final local state is still exact current `origin/main` plus only the new release commit; an existing local branch may be replaced only when it is not checked out in another worktree.
- Treat explicit invocation as authorization to fetch `origin/main`, create one dedicated detached release worktree, run branch-free release-readiness gates there, create or replace the local `release/v<version>` in that worktree only after those gates pass, update the five release-owned files, and create one local commit. If the branch already exists locally or remotely, the required final local state is still exact current `origin/main` plus only the new release commit; an existing local branch may be replaced only when it is not checked out in another worktree.
- Keep the user's source checkout on its existing clean `main` commit. Do not fast-forward it, switch its branch, or materialize release files there. Leave the dedicated release worktree in place for green handoff, blocked review, or recoverable failure.
- Never push, open or edit a pull request, add labels or milestones, create a release, or otherwise mutate GitHub. Never run `gh`.
- Own exactly `pyproject.toml`, `uv.lock`, and `tests/fixtures/released_api_contract.json`. Runtime, documentation, workflow, or other repository changes must land on `main` before release preparation.
- Own exactly `pyproject.toml`, `uv.lock`, `.release-please-manifest.json`, `src/agents/version.py`, and `tests/fixtures/released_api_contract.json`. Runtime, documentation, workflow, or other repository changes must land on `main` before release preparation.
- Do not stash, delete, overwrite or remove an existing worktree, or work around unrelated local changes. Fail before branch creation when the initial checkout is dirty or is not on `main`, the dedicated worktree is not clean and detached at refreshed `origin/main`, an existing local release branch is checked out in another worktree, the prospective packaged-contract gate fails after the allowed dependency-bootstrap recovery, the planning review blocks, or `origin/main` advances after those gates run.
- Treat `$final-release-review` as the controlling release checker, not only as a report generator. Its planning gate must be green before branch creation, and its final-candidate gate must inspect the materialized worktree and be green before PR-ready handoff. Any candidate content, commit, or base change invalidates the previous green result.
- Remove inherited `OPENAI_API_KEY` from every child command. Release preparation does not require a live OpenAI API request.
Expand Down Expand Up @@ -89,10 +89,10 @@ The helper must complete all of these operations or fail with an actionable erro
1. Repeat the source-root, clean `main`, version, registered-worktree, detached-HEAD, and release-branch replaceability checks.
2. Refresh `origin/main` again without moving the source checkout.
3. Require refreshed `origin/main` and `<release-worktree>` HEAD to equal `<preflight-base>`. If `origin/main` advanced, retain the old detached worktree and rerun preflight plus both readiness gates in a new exact-base worktree.
4. Keep the worktree detached while updating the single project version declaration in `pyproject.toml`.
4. Keep the worktree detached while updating the single project version declaration in `pyproject.toml`, the root version in `.release-please-manifest.json`, and the literal `__version__` fallback in `src/agents/version.py`.
5. Run `make sync` with `UV_DEFAULT_INDEX=https://pypi.org/simple`.
6. Run `make update-released-api-contract VERSION=<version>` and then `make check-released-api-contract VERSION=<version>`.
7. Require exactly the three release-owned paths to be modified in `<release-worktree>`, leave them unstaged and uncommitted, and confirm that the source checkout remains unchanged.
7. Require exactly the five release-owned paths to be modified in `<release-worktree>`, leave them unstaged and uncommitted, and confirm that the source checkout remains unchanged.
8. Only after those candidate checks pass, create or reset the local `release/v<version>` inside `<release-worktree>` to exact `<preflight-base>` while preserving the validated unstaged manifest. Do not retain commits or content from an older local or remote candidate. This delayed replacement must leave an existing local branch unchanged when candidate generation fails.

If the helper fails after branch creation, preserve its local branch, dedicated worktree, and working-tree evidence. Report the failing command and state rather than guessing whether a partial run is safe to resume. Never remove the worktree as automatic cleanup.
Expand All @@ -104,37 +104,37 @@ Run the remaining commands from `<release-worktree>`. Inspect all release-owned
```bash
git status --short
git diff --check
git diff -- pyproject.toml uv.lock tests/fixtures/released_api_contract.json
git diff -- pyproject.toml uv.lock .release-please-manifest.json src/agents/version.py tests/fixtures/released_api_contract.json
```

Confirm all of the following:

- `pyproject.toml` and the editable `openai-agents` entry in `uv.lock` declare the requested version.
- `pyproject.toml`, the editable `openai-agents` entry in `uv.lock`, the root entry in `.release-please-manifest.json`, and the source fallback in `src/agents/version.py` declare the requested version.
- The API contract baseline is `v<version>` and its `baseline_commit` is the exact `origin/main` source commit on which the release branch is based.
- The generated contract preserves the previous release and freezes intended new exports and signatures.
- Any intended `public_properties`, `canonical_imports`, or `public_modules` policy additions have been reviewed explicitly; the updater deliberately does not infer them.
- No path outside the three-file release manifest is changed, staged, or untracked.
- No path outside the five-file release manifest is changed, staged, or untracked.

Stage only the manifest and create exactly one local commit:

```bash
git add pyproject.toml uv.lock tests/fixtures/released_api_contract.json
git add pyproject.toml uv.lock .release-please-manifest.json src/agents/version.py tests/fixtures/released_api_contract.json
git commit -m "release: <version>"
```

Do not amend unrelated content into the commit.

## 6. Run the final-candidate release review

Invoke `$final-release-review` from `<release-worktree>` in final-candidate mode with the release commit as `TARGET=HEAD`. This invocation is a release checker: it must inspect the complete candidate diff and the actual checked-out `release/v<version>` contents, including `pyproject.toml`, the editable `openai-agents` entry in `uv.lock`, and `tests/fixtures/released_api_contract.json`. The branch, package metadata, lockfile, contract baseline, contract `baseline_commit`, and intended version must agree.
Invoke `$final-release-review` from `<release-worktree>` in final-candidate mode with the release commit as `TARGET=HEAD`. This invocation is a release checker: it must inspect the complete candidate diff and the actual checked-out `release/v<version>` contents, including `pyproject.toml`, the editable `openai-agents` entry in `uv.lock`, `.release-please-manifest.json`, `src/agents/version.py`, and `tests/fixtures/released_api_contract.json`. The branch, package metadata, lockfile, Release Please manifest, source fallback, contract baseline, contract `baseline_commit`, and intended version must agree.

If the review is blocked, stop. Return its unblock checklist, retain the local branch, commit, and worktree for follow-up, and do not present the candidate as PR-ready. A report body does not authorize continuation when the release call is blocked. After any fix, regenerate the API contract when the public surface may have changed, restore a single release commit, and rerun the complete final-candidate review.

The earlier planning review proves that the source commit was ready before branch creation. This final-candidate review remains required because it verifies the materialized branch, version metadata, lockfile, and frozen contract together. Treat its green release call as the handoff gate, then reuse its complete report as the release pull request description; do not substitute the planning report.

## 7. Recheck main freshness

After a green review, fetch `origin main` again without credentials from `<release-worktree>` and compare it with the release commit's parent. If they differ, the candidate is stale. First verify that the branch is clean, has exactly one local commit, and that the commit changes only the three-file release manifest. Rebase that commit onto the new `origin/main` so Git detects any conflicting release metadata. After a clean rebase, move the local release branch back to `origin/main` with a mixed reset, which preserves the rebased release tree as unstaged task-owned changes. Restore all three release-owned files (`pyproject.toml`, `uv.lock`, and `tests/fixtures/released_api_contract.json`) from `origin/main`, run `make sync`, and require the worktree to be clean at the new base. Run `make check-prospective-released-api-contract` only in that internally consistent base state, where the installed project version and frozen contract baseline agree. Then update `pyproject.toml` to `<version>`, run `make sync`, run `make update-released-api-contract VERSION=<version>` and `make check-released-api-contract VERSION=<version>`, review the exact manifest again, and recreate the single `release: <version>` commit. The base and candidate content changed, so the previous green check is invalid: rerun `$final-release-review` from the worktree and require a new green release call. Repeat until the reviewed local branch is exactly one commit ahead of current `origin/main` and that commit changes only the three-file release manifest.
After a green review, fetch `origin main` again without credentials from `<release-worktree>` and compare it with the release commit's parent. If they differ, the candidate is stale. First verify that the branch is clean, has exactly one local commit, and that the commit changes only the five-file release manifest. Rebase that commit onto the new `origin/main` so Git detects any conflicting release metadata. After a clean rebase, move the local release branch back to `origin/main` with a mixed reset, which preserves the rebased release tree as unstaged task-owned changes. Restore all five release-owned files (`pyproject.toml`, `uv.lock`, `.release-please-manifest.json`, `src/agents/version.py`, and `tests/fixtures/released_api_contract.json`) from `origin/main`, run `make sync`, and require the worktree to be clean at the new base. Run `make check-prospective-released-api-contract` only in that internally consistent base state, where the installed project version and frozen contract baseline agree. Then update `pyproject.toml`, the root entry in `.release-please-manifest.json`, and the literal `__version__` fallback in `src/agents/version.py` to `<version>`, run `make sync`, run `make update-released-api-contract VERSION=<version>` and `make check-released-api-contract VERSION=<version>`, review the exact manifest again, and recreate the single `release: <version>` commit. The base and candidate content changed, so the previous green check is invalid: rerun `$final-release-review` from the worktree and require a new green release call. Repeat until the reviewed local branch is exactly one commit ahead of current `origin/main` and that commit changes only the five-file release manifest.

If replay conflicts or another path changes, stop with recoverable evidence. Do not force a resolution that expands the release commit beyond its manifest.

Expand Down Expand Up @@ -164,7 +164,9 @@ Release <version>

Apply the repository's GitHub paste-readiness rules to the report. Use native `#123` references for this repository and `owner/repo#123` for another repository. Keep the required compare URL. Do not include local paths, Codex citations, operational diagnostics, or app directives inside the copy-ready description.

Also report the dedicated worktree path, local branch, commit SHA, parent `origin/main` commit, and the exact three-file manifest outside the copy-ready block. State explicitly that the source checkout was left unchanged, nothing was pushed, and no pull request was created. Leave the worktree in place for the user's handoff.
Also report the dedicated worktree path, local branch, commit SHA, parent `origin/main` commit, and the exact five-file manifest outside the copy-ready block. State explicitly that the source checkout was left unchanged, nothing was pushed, and no pull request was created. Leave the worktree in place for the user's handoff.

Include the [standalone manual release procedure](../../../.github/RELEASING.md#standalone-manual-release) in the handoff: before merging, an authorized maintainer pauses Release Please, waits for its queued/running jobs, and closes any superseded bot release PR. Release Please resumes only after the manual candidate's tag and GitHub Release exist. This skill does not perform those GitHub operations.

If `release/v<version>` already exists on `origin`, inspect its exact current commit with credential-free `git ls-remote --heads origin release/v<version>` immediately before handoff and record it as `<observed-remote-release-commit>`. State explicitly that the local branch has replaced the old candidate and now contains exact current `origin/main` plus only the new `release: <version>` commit. Because this skill never mutates GitHub, provide the user with the exact `git push --force-with-lease=refs/heads/release/v<version>:<observed-remote-release-commit> origin release/v<version>` command to replace the remote branch themselves; never run it. A normal push or an unspecified lease is insufficient for this replacement case. If the remote branch changes after inspection, the explicit lease must reject the push instead of overwriting unseen work.

Expand Down
33 changes: 30 additions & 3 deletions .agents/skills/release-candidate-prep/scripts/prepare.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@
VERSION_PATTERN = re.compile(r"\d+\.\d+(?:\.\d+)*(?:[A-Za-z0-9.-]+)?\Z")
COMMIT_PATTERN = re.compile(r"[0-9a-f]{40}\Z")
PROJECT_VERSION_PATTERN = re.compile(r'(?m)^version\s*=\s*"[^"]+"')
SOURCE_VERSION_PATTERN = re.compile(r'(?m)^([ \t]*__version__[ \t]*=[ \t]*)"([^"]+)"[ \t]*$')
RELEASE_PATHS = frozenset(
{
".release-please-manifest.json",
"pyproject.toml",
"src/agents/version.py",
"tests/fixtures/released_api_contract.json",
"uv.lock",
}
Expand Down Expand Up @@ -168,12 +171,28 @@ def replace_project_version_text(text: str, version: str) -> str:


def replace_project_version(repo: Path, version: str) -> None:
"""Update pyproject.toml while preserving all unrelated text."""
"""Update package, source fallback, and manifest versions without changing dependencies."""

path = repo / "pyproject.toml"
text = path.read_text(encoding="utf-8")
path.write_text(replace_project_version_text(text, version), encoding="utf-8")

manifest_path = repo / ".release-please-manifest.json"
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
manifest["."] = version
manifest_path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")

source_path = repo / "src/agents/version.py"
source_text = source_path.read_text(encoding="utf-8")
updated, count = SOURCE_VERSION_PATTERN.subn(
lambda match: f'{match[1]}"{version}"', source_text
)
if count != 1:
raise ReleasePreparationError(
"Expected exactly one literal __version__ fallback in src/agents/version.py."
)
source_path.write_text(updated, encoding="utf-8")


def _current_branch(repo: Path) -> str:
result = git(repo, "symbolic-ref", "--quiet", "--short", "HEAD", check=False)
Expand Down Expand Up @@ -357,6 +376,14 @@ def _validate_prepared_files(repo: Path, version: str, base_commit: str) -> tupl
raise ReleasePreparationError("pyproject.toml does not contain the requested version.")
if _locked_project_version(repo) != version:
raise ReleasePreparationError("uv.lock does not contain the requested project version.")
manifest = json.loads((repo / ".release-please-manifest.json").read_text(encoding="utf-8"))
if manifest.get(".") != version:
raise ReleasePreparationError("The Release Please manifest does not match the version.")
source_versions = SOURCE_VERSION_PATTERN.findall(
(repo / "src/agents/version.py").read_text(encoding="utf-8")
)
if len(source_versions) != 1 or source_versions[0][1] != version:
raise ReleasePreparationError("The source version fallback does not match the version.")

contract = json.loads(
(repo / "tests/fixtures/released_api_contract.json").read_text(encoding="utf-8")
Expand Down Expand Up @@ -434,7 +461,7 @@ def materialize(
expected_source_head: str,
worktree: Path,
) -> PreparedCandidate:
"""Create the three-file candidate in the reviewed isolated worktree."""
"""Create the five-file candidate in the reviewed isolated worktree."""

expected_base = validate_commit(expected_base)
expected_source_head = validate_commit(expected_source_head)
Expand Down Expand Up @@ -592,7 +619,7 @@ def main() -> int:
print("Changed paths:")
for path in candidate.changed_paths:
print(f"- {path}")
print("Review the diff before staging the three release-owned files.")
print("Review the diff before staging the five release-owned files.")
return 0


Expand Down
Loading
Loading