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
58 changes: 42 additions & 16 deletions python/.github/skills/agent-framework-py-release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ For release work, derive the live tier map at release time from `python/PACKAGE_

## Inputs to confirm before bumping

1. **The changeset**: explicit commits/PRs the release covers, OR derive from `git log ${LAST_RELEASED_TAG}..origin/main -- python/`.
1. **The changeset**: explicit commits/PRs the release covers, OR derive from `git log ${LAST_RELEASED_TAG}..${RELEASE_BASE} -- python/`.
2. **Per-package CHANGELOG entries**: which packages will get a line in the new release section. This list IS the bump list.
3. **Per-released-package semver bump**: for each released-tier package that has a CHANGELOG entry, decide PATCH / MINOR / MAJOR.
4. **Date stamp** (only if any alpha/beta is being bumped): default from the `python-package-management`
Expand All @@ -55,12 +55,20 @@ If the user states target versions or a date explicitly, use exactly what they s
git fetch origin main --tags --quiet
git fetch upstream main --tags --quiet 2>/dev/null || true
git status

# Fork clones use upstream/main as the authoritative release base; direct clones use origin/main.
if git show-ref --verify --quiet refs/remotes/upstream/main; then
RELEASE_BASE=upstream/main
else
RELEASE_BASE=origin/main
fi
git log -1 --oneline "$RELEASE_BASE"
```

If the user already has a `bump-py-ver-release-*` branch checked out, use it. Otherwise:

```bash
git checkout -b bump-py-ver-release-YYMMDD origin/main
git checkout -b bump-py-ver-release-YYMMDD "$RELEASE_BASE"
```

### 2. Build the live tier map
Expand All @@ -84,20 +92,20 @@ echo "Compare base: $LAST_RELEASED_TAG"
List commits and packages touched:

```bash
git log --oneline ${LAST_RELEASED_TAG}..origin/main -- python/ ':!python/CHANGELOG.md'
git log --oneline ${LAST_RELEASED_TAG}..${RELEASE_BASE} -- python/ ':!python/CHANGELOG.md'

# Per-commit package footprint
for sha in $(git log --format='%H' ${LAST_RELEASED_TAG}..origin/main -- python/); do
for sha in $(git log --format='%H' ${LAST_RELEASED_TAG}..${RELEASE_BASE} -- python/); do
echo "--- $(git show -s --format='%h %s' $sha) ---"
git show --name-only --format='' $sha | grep '^python/packages/' | \
sed 's|^python/packages/||' | awk -F/ '{print $1}' | sort -u
done
```

If the release ultimately tags from `upstream/main` but `origin/main` is behind, also run:
When both remotes exist, record whether the fork is behind the authoritative base:

```bash
git log --oneline ${LAST_RELEASED_TAG}..upstream/main -- python/ ':!python/CHANGELOG.md'
git rev-list --left-right --count origin/main...upstream/main
```

If user provides an explicit commit/PR list, treat THAT as authoritative.
Expand All @@ -108,14 +116,14 @@ Aggregate the per-commit footprint into a single union across the whole range. T

```bash
# Union of all touched package directories across the range
git log --name-only --format='' ${LAST_RELEASED_TAG}..origin/main -- python/packages/ \
git log --name-only --format='' ${LAST_RELEASED_TAG}..${RELEASE_BASE} -- python/packages/ \
| grep '^python/packages/' \
| sed 's|^python/packages/||' \
| awk -F/ '{print $1}' \
| sort -u

# Root-level files (drive a root agent-framework entry if substantive)
git log --name-only --format='' ${LAST_RELEASED_TAG}..origin/main \
git log --name-only --format='' ${LAST_RELEASED_TAG}..${RELEASE_BASE} \
-- python/pyproject.toml python/agent_framework_meta/ python/README.md \
2>/dev/null | grep -v '^$' | sort -u
```
Expand Down Expand Up @@ -175,13 +183,13 @@ Before moving on, prove that every ship-affecting touched package has at least o

```bash
# 1. Touched ship-affecting packages and root package files (from step 3a)
TOUCHED_PACKAGES=$(git log --name-only --format='' ${LAST_RELEASED_TAG}..origin/main -- python/packages/ \
TOUCHED_PACKAGES=$(git log --name-only --format='' ${LAST_RELEASED_TAG}..${RELEASE_BASE} -- python/packages/ \
| grep '^python/packages/' \
| sed 's|^python/packages/||' \
| awk -F/ '{print $1}' \
| sort -u)

ROOT_TOUCHED=$(git log --name-only --format='' ${LAST_RELEASED_TAG}..origin/main \
ROOT_TOUCHED=$(git log --name-only --format='' ${LAST_RELEASED_TAG}..${RELEASE_BASE} \
-- python/pyproject.toml python/agent_framework_meta/ python/README.md \
2>/dev/null | grep -v '^$' | sort -u)

Expand Down Expand Up @@ -279,7 +287,7 @@ Spot-check with `grep '^version' python/pyproject.toml python/packages/*/pyproje
Only relevant when `core` itself bumped this cycle. Two policies, pick one explicitly with the user:

- **Conservative (default)**: raise `agent-framework-core>=X.Y.Z` to the new core version on every non-core package that is ALSO bumping this cycle. Leaves packages-not-bumped at their existing floor.
- **Strict per-upstream-doc**: only raise the floor on packages that actually consume a new core API introduced in the bump. This requires per-package code inspection. Use only when the user is comfortable letting `validate-dependency-bounds-test` (lower-resolution pass) catch any mistakes.
- **Strict per-upstream-doc**: only raise the floor on packages that actually consume a new core API introduced in the bump. This requires per-package code inspection because release probes use the co-released local core and cannot prove compatibility with an older published core floor.

When raising a core floor, replace only the `>=OLD` half of the bound you intend to change:

Expand All @@ -294,12 +302,29 @@ If `core` did not bump this cycle, do not touch floors.
### 7. Validate

```bash
cd python && uv run poe validate-dependency-bounds-test
cd python && uv run poe validate-python-release --base-ref "$RELEASE_BASE"
```

Must exit 0. This is the safety net for selective bumping: the lower-resolution pass catches floors set too low for code that depends on new APIs, and the upper pass catches caps that exclude installable versions. If it fails, the output names the offending bound — fix and re-run before committing. This step also regenerates `uv.lock` to match new bounds.
Use the same freshly fetched main ref that the release branch was based on (`upstream/main` above; use `origin/main`
when that is the authoritative release base). Must exit 0. This task first regenerates `uv.lock`, then discovers the
package `pyproject.toml` files changed from that base and runs their published runtime dependencies and
non-development extras through lock-independent `lowest-direct` and `highest` import probes. The probes run in
parallel, derive the minimum supported Python minor from each package's internal editable closure, and share a hard
300-second deadline. Use `--python` only when the release requires an explicit interpreter override.

This is the release safety net for selective bumping: the lower probe catches unresolvable or unimportable external
floors, internal constraints that reject co-released package versions, and the upper probe catches caps that exclude
an installable package set. The JSON report records the concrete versions resolved in both scenarios. It does not
replace the package-by-package code inspection required by the strict core-floor policy. If it fails, fix the named
package/bound and re-run before committing.

Do not substitute the workspace-wide `validate-dependency-bounds-test` command here. That command runs every
package's full tests and Pyright in separate isolated environments and is intentionally reserved for CI or an
explicit dependency-range audit. If the release itself changes an external dependency range, also run
`validate-dependency-bounds-project --mode both --package <pkg> --dependency <name>` for that dependency.

If only prereleases changed (no `core` bump, no floor changes), this validation is still required — `uv.lock` regeneration alone justifies the run.
If only prereleases changed (no `core` bump, no floor changes), release validation is still required because the
lockfile and both ends of each changed package's published dependency metadata must remain installable.

### 8. Commit (expect hook retry)

Expand Down Expand Up @@ -349,13 +374,14 @@ The push output includes a `Create a pull request for '<branch>' on GitHub by vi
do not infer a local timezone from the user's current shell.
- **`Co-Authored-By` trailer.** Never add it. Rewrite/amend if it slipped in.
- **Stale inventory in this skill.** Always read `python/PACKAGE_STATUS.md` for the live tier map. Do not trust a hardcoded list.
- **Divergent origin vs upstream.** If the release tags from `upstream/main` but `origin/main` is behind, check both — warn if they differ and offer to sync.
- **Divergent origin vs upstream.** In fork clones, use freshly fetched `upstream/main` consistently for branch creation, changeset discovery, and release validation. A stale `origin/main` must never become the implicit compare base.
- **`--pre` README cleanup on promotion.** When a package is promoted to `released` in this cycle, grep for `pip install agent-framework-<pkg> --pre` in READMEs and drop the `--pre` flag.
- **RC counter inflation.** Do not increment `1.0.0rcN` without a CHANGELOG entry for that package. The counter tracks iterations, not calendar.

## References

- Package lifecycle and versioning source of truth: `python/.github/skills/python-package-management/SKILL.md`
- Lifecycle source of truth: `python/PACKAGE_STATUS.md`
- Validator: `python/scripts/dependencies/validate_dependency_bounds.py` (runs `lowest-direct` and `highest` resolution smoke tests; catches floors/caps that don't match the code)
- Release validator: `python/scripts/dependencies/validate_dependency_bounds.py --mode release` (changed-package,
lock-independent `lowest-direct` and `highest` import probes under a five-minute deadline)
- Poe task definitions: `python/pyproject.toml` `[tool.poe.tasks]`
19 changes: 16 additions & 3 deletions python/.github/skills/python-package-management/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,13 @@ uv lock --upgrade-package <dependency-name> && uv run poe install
# Refresh exact development dependency-group pins, lockfile, and validation in one run
uv run poe upgrade-dev-dependencies

# First, run workspace-wide lower/upper compatibility gates
# Release cuts: refresh uv.lock and probe changed packages at both bound extremes.
# The release probe has a shared five-minute deadline.
uv run poe validate-python-release --base-ref upstream/main

# Exhaustive test+typing matrix (slow; use for deliberate dependency-range work or CI)
uv run poe validate-dependency-bounds-test
# Defaults to --package "*"; pass a package to scope test mode
# Defaults to --package "*"; scope locally whenever possible.
uv run poe validate-dependency-bounds-test --package core

# Then expand bounds for one dependency in the target package
Expand All @@ -66,7 +70,16 @@ uv run poe add-dependency-and-validate-bounds --package core --dependency "<depe
- Prerelease (`dev`/`a`/`b`/`rc`) and `<1.0` dependencies should use hard bounds with an explicit upper cap (avoid open-ended ranges).
- For `<1.0` dependencies, prefer the broadest validated range the package can really support. That may be a patch line, a minor line, or multiple minor lines when checks/tests show the broader lane is compatible.
- Prefer supporting multiple majors when practical; if APIs diverge across supported majors, use version-conditional imports/paths.
- For dependency changes, run workspace-wide bound gates first, then `validate-dependency-bounds-project --mode both` for the target package/dependency to keep minimum and maximum constraints current. The same task can also drive repo-wide upper-bound automation by using `--package "*"` and omitting `--dependency`.
- For release-only version, lifecycle, pin, and internal-floor edits, use `validate-python-release`. It refreshes
`uv.lock`, finds changed package metadata relative to the selected main ref, and runs the changed packages'
published runtime dependencies and non-development extras through lock-independent `lowest-direct` and `highest`
import probes on the minimum Python minor supported by each package's internal editable closure. The probes run
concurrently under one 300-second deadline; pass `--python` only when an explicit interpreter override is needed.
- For deliberate external dependency-range changes, use
`validate-dependency-bounds-project --mode both` for the target package/dependency to find and validate the actual
minimum and maximum constraints. Scope the exhaustive `validate-dependency-bounds-test` matrix to affected
packages during local iteration; reserve the workspace-wide form for CI or an intentional full audit. The same
project task can drive repo-wide upper-bound automation by using `--package "*"` and omitting `--dependency`.
- Prefer targeted lock updates with `uv lock --upgrade-package <dependency-name>` to reduce `uv.lock` merge conflicts.
- Use `add-dependency-and-validate-bounds` for package-scoped dependency additions plus bound validation in one command.
- Keep shared tooling and source/type-check support in the root or package `dev` group. Put package-specific test
Expand Down
24 changes: 23 additions & 1 deletion python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -395,10 +395,32 @@ args = [
]

[tool.poe.tasks.validate-dependency-bounds-test]
help = "Run workspace dependency-bound validation in test mode, optionally scoped with -P/--package short names such as `core`."
help = "Run the exhaustive workspace dependency-bound test+typing matrix, optionally scoped with -P/--package short names such as `core`."
shell = "python -m scripts.dependencies.validate_dependency_bounds --mode test --package \"$project\""
args = [{ name = "project", default = "*", options = ["-P", "--package"] }]

[tool.poe.tasks.validate-python-release]
help = "Refresh uv.lock, then run lower/upper import probes for changed package metadata on each package closure's minimum Python."
executor = "simple"
shell = """
command=(
python -m scripts.dependencies.validate_dependency_bounds
--mode release
--base-ref "${base_ref}"
--release-timeout-seconds "${timeout}"
)
if [ -n "${python}" ]; then
command+=(--python "${python}")
fi
"${command[@]}"
"""
interpreter = "bash"
args = [
{ name = "base_ref", options = ["-B", "--base-ref"] },
{ name = "python", default = "", options = ["--python"] },
{ name = "timeout", default = "300", options = ["--timeout-seconds"] },
]

[tool.poe.tasks.validate-dependency-bounds-project]
help = "Validate lower and upper dependency bounds for a -P/--package workspace package, optionally narrowed with -M/--mode and -D/--dependency."
shell = """
Expand Down
21 changes: 18 additions & 3 deletions python/scripts/dependencies/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,19 @@ Run the commands below from the `python/` directory.

- `validate_dependency_bounds.py`
- Main entrypoint for dependency-bound workflows.
- Supports `test`, `lower`, `upper`, and `both` modes.
- `test` runs workspace-wide smoke validation at the lower and upper ends of the currently allowed ranges.
- Supports `release`, `test`, `lower`, `upper`, and `both` modes.
- `release` refreshes `uv.lock`, then runs changed packages through fast lock-independent lower/upper import probes.
- `test` runs the exhaustive workspace test+typing compatibility matrix.
- `lower`, `upper`, and `both` dispatch to the lower/upper optimizer implementations for one package.

- `_dependency_bounds_release_impl.py`
- Discovers package metadata changed from the selected release base.
- Resolves published runtime dependencies and non-development extras independently of `uv.lock` with both
`lowest-direct` and `highest` strategies.
- Derives the minimum supported Python minor from each changed package's internal editable dependency closure.
- Imports each changed package and records resolved dependency versions in a JSON report.
- Runs probes concurrently under one five-minute deadline.

- `upgrade_dev_dependencies.py`
- Refreshes exact dev dependency pins across the root `pyproject.toml` and package `pyproject.toml` files.
- Reuses the same version-selection logic as the upper-bound tooling so direct dev-tooling refreshes and dependency-range expansion stay consistent.
Expand Down Expand Up @@ -45,14 +54,18 @@ These are the normal user-facing entrypoints:
```bash
uv run poe upgrade-dev-dependency-pins
uv run poe upgrade-dev-dependencies
uv run poe validate-python-release --base-ref upstream/main
uv run poe validate-dependency-bounds-test
uv run poe validate-dependency-bounds-test --package core
uv run poe validate-dependency-bounds-project --mode both --package core --dependency "<dependency-name>"
```

- `upgrade-dev-dependency-pins` only refreshes exact dev pins in `pyproject.toml` files.
- `upgrade-dev-dependencies` refreshes dev pins (using task above), runs `uv lock --upgrade`, reinstalls from the frozen lockfile, then runs `check`, `typing`, and `test`.
- `validate-dependency-bounds-test` runs the repo-wide lower/upper smoke gate.
- `validate-python-release` is the bounded release gate: it refreshes `uv.lock`, finds changed package metadata,
and probes both dependency-bound extremes without reusing the lockfile.
- `validate-dependency-bounds-test` runs the exhaustive package test+typing matrix and is intentionally not part of
the routine release path.
- `validate-dependency-bounds-project` is the single package-scoped task; use `--mode lower`, `--mode upper`, or `--mode both` for the target package/dependency pair. Its `--package` argument defaults to `*`, and `--dependency` is optional, so automation can also use it for repo-wide upper-bound runs.

### GitHub Actions workflows
Expand All @@ -76,6 +89,7 @@ These are useful for debugging or targeted manual runs:

```bash
python -m scripts.dependencies.upgrade_dev_dependencies --dry-run --version-source lock
python -m scripts.dependencies.validate_dependency_bounds --mode release --base-ref upstream/main --dry-run
python -m scripts.dependencies.validate_dependency_bounds --mode test --package core --dry-run
python -m scripts.dependencies.validate_dependency_bounds --mode both --package core --dependencies openai --dry-run
python -m scripts.dependencies._dependency_bounds_lower_impl --packages core --dependencies openai --dry-run
Expand All @@ -89,6 +103,7 @@ Use the direct lower/upper implementation modules mainly for debugging or develo
The validators write JSON reports into this folder:

- `dependency-bounds-test-results.json`
- `dependency-bounds-release-results.json`
- `dependency-lower-bound-results.json`
- `dependency-range-results.json`

Expand Down
Loading
Loading