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
4 changes: 3 additions & 1 deletion .agents/skills/python-codestyle/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,9 @@ form that the fleet baseline expects. Its header names the pip-form and lint-onl
`dependsOn`-only. Neither chains with `&&`, so the mirror runs the same on any task shell.

CI runs these same commands as the authoritative backstop in every directory the hub validator's
`python-directories` input declares. A repo declaring none has its root gated only where it tracks
`python-directories` input declares. Its `uvx` ruff runs at the version the hub's `python-tool-pins`
action pins rather than at `@latest`. So a ruff release can make a local run and CI disagree until
the caller's hub pin moves to a release carrying the new version. A repo declaring none has its root gated only where it tracks
a root `pyproject.toml`, per `WORKFLOW.md` D1.2. That undeclared root is gated more loosely. CI
reads its type-checker configuration only from the `[tool.mypy]` or `[tool.pyright]` section of its
`pyproject.toml`. It skips the type check where neither is there,
Expand Down
22 changes: 13 additions & 9 deletions .agents/skills/python-codestyle/references/profiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,25 @@ Tool configuration alone marks the lint-only one:
pytest from it as `.venv/bin/python -m pytest`. A directory declared in the hub validator's
`python-directories` input runs ruff and the type checker from that environment where it
installs them. It otherwise runs them through `uvx`, with the checker pointed at that
environment. The undeclared default root runs `uvx ruff@latest` and a bare
`uvx <checker>@latest`, with nothing installed.
environment. The undeclared default root runs ruff through `uvx` at the pinned version the
lint-only profile below names, and a bare `uvx <checker>@latest`, with nothing installed.
- **Scripts** (the `lint-only` profile): stdlib-only utility scripts (e.g. a
Python tooling subtree of a `csharp` app). Run the tools with `uvx` (no project install,
no lockfile). The `pyproject.toml` carries only tool config (`[tool.ruff]`, `[tool.mypy]`, and
an optional `[tool.pyright]` editor block). There is no `[project]`, no `[build-system]`, and no
`uv.lock` (that metadata would misrepresent it as a shippable package). mypy is the type-check
gate (there is no first-party package for pyright strict to anchor on). A `[tool.pyright]`
block in standard mode keeps Pylance quiet in the editor, the same mypy-gate/pyright-editor
split the build profile uses. There is no lockfile, and a `uvx <tool>@<ver>` pin in a `run:`
step is not something Dependabot tracks. So CI runs `uvx ruff@latest` / `uvx mypy@latest` rather
than a manual pin that would silently go stale. The fleet rule is to pin only what Dependabot
auto-updates (SHA-pinned actions, package deps) and otherwise run latest. So the VS Code tasks,
README, and CI all run the unpinned latest here. `.py` files follow the repo's LF line-ending
default (per GOVERNANCE.md's "Line Endings" section). There is no pytest suite, and `unittest` is
split the build profile uses. There is no lockfile, and a `uvx <tool>@<ver>` literal in a `run:`
step is not something Dependabot tracks. The fleet rule is to pin only what Dependabot
auto-updates (SHA-pinned actions, package deps) and otherwise run latest. A manual pin would
silently go stale. So CI runs ruff at the version the hub validator's
`python-tool-pins` action reads from the `requirements.txt` beside it, which the hub's
Dependabot pip ecosystem bumps. A caller reaches that version at the hub commit its workflow
pins, so a ruff release reaches its lint only through a hub release. CI runs `uvx mypy@latest`,
and a carrier's VS Code tasks and README run the unpinned latest of every tool. The hub's own
tasks and hook take that `requirements.txt` as a requirement instead. `.py` files follow the
repo's LF line-ending default (per GOVERNANCE.md's "Line Endings" section). There is no pytest suite, and `unittest` is
the runner instead. The directory still owes tests, written with the standard
library's `unittest` so they need no test runner installed. Each is a `test_<script>.py`
under a `tests/` directory beside the scripts it exercises and their `pyproject.toml`
Expand All @@ -72,7 +76,7 @@ Tool configuration alone marks the lint-only one:
owes the same gates whatever its profile: lint, format, a type check, a test suite, and a
coverage report to Codecov. The hub validator runs them in each directory the caller declares
in its `python-directories` input, which the registry's `pythonDirectories` mirrors. From inside
a lint-only directory it runs `uvx ruff@latest check`, `uvx ruff@latest format --check`,
a lint-only directory it runs `uvx ruff@<pin> check`, `uvx ruff@<pin> format --check`,
`uvx mypy@latest`, and `uvx coverage@latest run -m unittest discover -s tests`. It adds
`--with-requirements tests/requirements.txt` to that last where the directory carries one. It then
writes `coverage.xml` and uploads it best-effort per `WORKFLOW.md` D1.6, with no threshold
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1361c682200cf1c1
0976adafd9b56fe6
4 changes: 3 additions & 1 deletion .claude-plugin/fleet-skills/skills/python-codestyle/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,9 @@ form that the fleet baseline expects. Its header names the pip-form and lint-onl
`dependsOn`-only. Neither chains with `&&`, so the mirror runs the same on any task shell.

CI runs these same commands as the authoritative backstop in every directory the hub validator's
`python-directories` input declares. A repo declaring none has its root gated only where it tracks
`python-directories` input declares. Its `uvx` ruff runs at the version the hub's `python-tool-pins`
action pins rather than at `@latest`. So a ruff release can make a local run and CI disagree until
the caller's hub pin moves to a release carrying the new version. A repo declaring none has its root gated only where it tracks
a root `pyproject.toml`, per `WORKFLOW.md` D1.2. That undeclared root is gated more loosely. CI
reads its type-checker configuration only from the `[tool.mypy]` or `[tool.pyright]` section of its
`pyproject.toml`. It skips the type check where neither is there,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,25 @@ Tool configuration alone marks the lint-only one:
pytest from it as `.venv/bin/python -m pytest`. A directory declared in the hub validator's
`python-directories` input runs ruff and the type checker from that environment where it
installs them. It otherwise runs them through `uvx`, with the checker pointed at that
environment. The undeclared default root runs `uvx ruff@latest` and a bare
`uvx <checker>@latest`, with nothing installed.
environment. The undeclared default root runs ruff through `uvx` at the pinned version the
lint-only profile below names, and a bare `uvx <checker>@latest`, with nothing installed.
- **Scripts** (the `lint-only` profile): stdlib-only utility scripts (e.g. a
Python tooling subtree of a `csharp` app). Run the tools with `uvx` (no project install,
no lockfile). The `pyproject.toml` carries only tool config (`[tool.ruff]`, `[tool.mypy]`, and
an optional `[tool.pyright]` editor block). There is no `[project]`, no `[build-system]`, and no
`uv.lock` (that metadata would misrepresent it as a shippable package). mypy is the type-check
gate (there is no first-party package for pyright strict to anchor on). A `[tool.pyright]`
block in standard mode keeps Pylance quiet in the editor, the same mypy-gate/pyright-editor
split the build profile uses. There is no lockfile, and a `uvx <tool>@<ver>` pin in a `run:`
step is not something Dependabot tracks. So CI runs `uvx ruff@latest` / `uvx mypy@latest` rather
than a manual pin that would silently go stale. The fleet rule is to pin only what Dependabot
auto-updates (SHA-pinned actions, package deps) and otherwise run latest. So the VS Code tasks,
README, and CI all run the unpinned latest here. `.py` files follow the repo's LF line-ending
default (per GOVERNANCE.md's "Line Endings" section). There is no pytest suite, and `unittest` is
split the build profile uses. There is no lockfile, and a `uvx <tool>@<ver>` literal in a `run:`
step is not something Dependabot tracks. The fleet rule is to pin only what Dependabot
auto-updates (SHA-pinned actions, package deps) and otherwise run latest. A manual pin would
silently go stale. So CI runs ruff at the version the hub validator's
`python-tool-pins` action reads from the `requirements.txt` beside it, which the hub's
Dependabot pip ecosystem bumps. A caller reaches that version at the hub commit its workflow
pins, so a ruff release reaches its lint only through a hub release. CI runs `uvx mypy@latest`,
and a carrier's VS Code tasks and README run the unpinned latest of every tool. The hub's own
tasks and hook take that `requirements.txt` as a requirement instead. `.py` files follow the
repo's LF line-ending default (per GOVERNANCE.md's "Line Endings" section). There is no pytest suite, and `unittest` is
the runner instead. The directory still owes tests, written with the standard
library's `unittest` so they need no test runner installed. Each is a `test_<script>.py`
under a `tests/` directory beside the scripts it exercises and their `pyproject.toml`
Expand All @@ -72,7 +76,7 @@ Tool configuration alone marks the lint-only one:
owes the same gates whatever its profile: lint, format, a type check, a test suite, and a
coverage report to Codecov. The hub validator runs them in each directory the caller declares
in its `python-directories` input, which the registry's `pythonDirectories` mirrors. From inside
a lint-only directory it runs `uvx ruff@latest check`, `uvx ruff@latest format --check`,
a lint-only directory it runs `uvx ruff@<pin> check`, `uvx ruff@<pin> format --check`,
`uvx mypy@latest`, and `uvx coverage@latest run -m unittest discover -s tests`. It adds
`--with-requirements tests/requirements.txt` to that last where the directory carries one. It then
writes `coverage.xml` and uploads it best-effort per `WORKFLOW.md` D1.6, with no threshold
Expand Down
25 changes: 25 additions & 0 deletions .github/actions/python-tool-pins/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Python tool pins
description: Read the Python tool versions the validator runs through uvx from the requirements.txt beside this action.

outputs:
ruff:
description: The ruff version requirements.txt pins, for example '0.17.3'.
value: ${{ steps.read.outputs.ruff }}

runs:
using: composite
steps:
# The manifest is read from the action's own path, so a caller reaching this action at a hub pin runs that pin's version.
# A version read from the caller's checkout would be whatever the caller carries, or nothing.
- name: Read tool pins step
id: read
shell: bash
run: |
set -Eeuo pipefail
manifest="$GITHUB_ACTION_PATH/requirements.txt"
version=$(sed -n 's/^ruff==\([0-9][0-9A-Za-z.]*\)[[:space:]]*$/\1/p' "$manifest")
if [ -z "$version" ] || [ "$version" != "${version%%$'\n'*}" ]; then
echo "::error::$manifest must pin ruff on exactly one 'ruff==<version>' line."
exit 1
fi
echo "ruff=$version" >> "$GITHUB_OUTPUT"
3 changes: 3 additions & 0 deletions .github/actions/python-tool-pins/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# The ruff version the validator's uvx calls run, read by action.yml beside this file.
# It is a manifest rather than a literal in a run: line, since Dependabot's pip ecosystem bumps it.
ruff==0.17.3
15 changes: 14 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
#
# The github-actions ecosystem is the only one this repo ships.
# The github-actions ecosystem covers every workflow and composite action this repo ships.
# The pip pair covers the ruff version the validator runs, pinned in .github/actions/python-tool-pins/requirements.txt.
# Both main and develop are targeted so each stays current independently of the develop to main release cadence.
# The merge-bot auto-merges each base with its per-base method.
# See catalog/snippets/configs/dependabot.yml for the multi-ecosystem reference, covering nuget, uv, and pip, that a code-shipping repo uses.
Expand Down Expand Up @@ -30,3 +31,15 @@ updates:
actions-deps:
patterns:
- "*"

- package-ecosystem: "pip"
target-branch: "main"
directory: "/.github/actions/python-tool-pins"
schedule:
interval: "daily"

- package-ecosystem: "pip"
target-branch: "develop"
directory: "/.github/actions/python-tool-pins"
schedule:
interval: "daily"
4 changes: 3 additions & 1 deletion .github/skills/python-codestyle/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,9 @@ form that the fleet baseline expects. Its header names the pip-form and lint-onl
`dependsOn`-only. Neither chains with `&&`, so the mirror runs the same on any task shell.

CI runs these same commands as the authoritative backstop in every directory the hub validator's
`python-directories` input declares. A repo declaring none has its root gated only where it tracks
`python-directories` input declares. Its `uvx` ruff runs at the version the hub's `python-tool-pins`
action pins rather than at `@latest`. So a ruff release can make a local run and CI disagree until
the caller's hub pin moves to a release carrying the new version. A repo declaring none has its root gated only where it tracks
a root `pyproject.toml`, per `WORKFLOW.md` D1.2. That undeclared root is gated more loosely. CI
reads its type-checker configuration only from the `[tool.mypy]` or `[tool.pyright]` section of its
`pyproject.toml`. It skips the type check where neither is there,
Expand Down
22 changes: 13 additions & 9 deletions .github/skills/python-codestyle/references/profiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,25 @@ Tool configuration alone marks the lint-only one:
pytest from it as `.venv/bin/python -m pytest`. A directory declared in the hub validator's
`python-directories` input runs ruff and the type checker from that environment where it
installs them. It otherwise runs them through `uvx`, with the checker pointed at that
environment. The undeclared default root runs `uvx ruff@latest` and a bare
`uvx <checker>@latest`, with nothing installed.
environment. The undeclared default root runs ruff through `uvx` at the pinned version the
lint-only profile below names, and a bare `uvx <checker>@latest`, with nothing installed.
- **Scripts** (the `lint-only` profile): stdlib-only utility scripts (e.g. a
Python tooling subtree of a `csharp` app). Run the tools with `uvx` (no project install,
no lockfile). The `pyproject.toml` carries only tool config (`[tool.ruff]`, `[tool.mypy]`, and
an optional `[tool.pyright]` editor block). There is no `[project]`, no `[build-system]`, and no
`uv.lock` (that metadata would misrepresent it as a shippable package). mypy is the type-check
gate (there is no first-party package for pyright strict to anchor on). A `[tool.pyright]`
block in standard mode keeps Pylance quiet in the editor, the same mypy-gate/pyright-editor
split the build profile uses. There is no lockfile, and a `uvx <tool>@<ver>` pin in a `run:`
step is not something Dependabot tracks. So CI runs `uvx ruff@latest` / `uvx mypy@latest` rather
than a manual pin that would silently go stale. The fleet rule is to pin only what Dependabot
auto-updates (SHA-pinned actions, package deps) and otherwise run latest. So the VS Code tasks,
README, and CI all run the unpinned latest here. `.py` files follow the repo's LF line-ending
default (per GOVERNANCE.md's "Line Endings" section). There is no pytest suite, and `unittest` is
split the build profile uses. There is no lockfile, and a `uvx <tool>@<ver>` literal in a `run:`
step is not something Dependabot tracks. The fleet rule is to pin only what Dependabot
auto-updates (SHA-pinned actions, package deps) and otherwise run latest. A manual pin would
silently go stale. So CI runs ruff at the version the hub validator's
`python-tool-pins` action reads from the `requirements.txt` beside it, which the hub's
Dependabot pip ecosystem bumps. A caller reaches that version at the hub commit its workflow
pins, so a ruff release reaches its lint only through a hub release. CI runs `uvx mypy@latest`,
and a carrier's VS Code tasks and README run the unpinned latest of every tool. The hub's own
tasks and hook take that `requirements.txt` as a requirement instead. `.py` files follow the
repo's LF line-ending default (per GOVERNANCE.md's "Line Endings" section). There is no pytest suite, and `unittest` is
the runner instead. The directory still owes tests, written with the standard
library's `unittest` so they need no test runner installed. Each is a `test_<script>.py`
under a `tests/` directory beside the scripts it exercises and their `pyproject.toml`
Expand All @@ -72,7 +76,7 @@ Tool configuration alone marks the lint-only one:
owes the same gates whatever its profile: lint, format, a type check, a test suite, and a
coverage report to Codecov. The hub validator runs them in each directory the caller declares
in its `python-directories` input, which the registry's `pythonDirectories` mirrors. From inside
a lint-only directory it runs `uvx ruff@latest check`, `uvx ruff@latest format --check`,
a lint-only directory it runs `uvx ruff@<pin> check`, `uvx ruff@<pin> format --check`,
`uvx mypy@latest`, and `uvx coverage@latest run -m unittest discover -s tests`. It adds
`--with-requirements tests/requirements.txt` to that last where the directory carries one. It then
writes `coverage.xml` and uploads it best-effort per `WORKFLOW.md` D1.6, with no threshold
Expand Down
19 changes: 14 additions & 5 deletions .github/workflows/validate-task.yml
Original file line number Diff line number Diff line change
Expand Up @@ -298,9 +298,16 @@ jobs:
directories: ${{ inputs.python-directories }}
report-uncovered: 'true'

# @latest rather than a version pin, since a `uvx <tool>@<ver>` pin is nothing Dependabot tracks and would silently go stale.
# The action SHA above is Dependabot-tracked.
# This is the one place the fleet's uvx tools are pinned or floated, decided here.
# The ruff version is the one the requirements.txt beside this action pins, which Dependabot's pip ecosystem bumps.
# The action resolves at this workflow's own commit, so a caller at a hub pin runs that pin's ruff rather than its own tree's.
- name: Read Python tool pins step
id: pins
if: steps.python.outputs.any == 'true'
uses: $/.github/actions/python-tool-pins

# The other uvx tools run @latest, since a `uvx <tool>@<ver>` literal in a run: line is nothing Dependabot tracks and would silently go stale.
# The setup-uv SHA here is Dependabot-tracked.
# This step and the one above are the one place the fleet's uvx tools are pinned or floated, decided here.
- name: Setup uv step
if: steps.python.outputs.any == 'true' || hashFiles('.github/actions/**/action.yml', '.github/actions/**/action.yaml') != ''
uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
Expand Down Expand Up @@ -356,6 +363,7 @@ jobs:
env:
PYTHON_PROJECTS: ${{ steps.python.outputs.projects }}
DECLARED: ${{ steps.python.outputs.declared }}
RUFF_VERSION: ${{ steps.pins.outputs.ruff }}
run: |
set -Eeuo pipefail
while IFS=$'\t' read -r kind dir; do
Expand All @@ -366,7 +374,7 @@ jobs:
elif [ "$kind" = pip ] && [ "$DECLARED" = "true" ] && "$dir/.venv/bin/python" -c 'import importlib.util, sys; sys.exit(importlib.util.find_spec("ruff") is None)' < /dev/null; then
(cd "$dir" && .venv/bin/python -m ruff check .) < /dev/null
else
(cd "$dir" && uvx ruff@latest check .) < /dev/null
(cd "$dir" && uvx "ruff@$RUFF_VERSION" check .) < /dev/null
fi
done <<< "$PYTHON_PROJECTS"

Expand All @@ -375,6 +383,7 @@ jobs:
env:
PYTHON_PROJECTS: ${{ steps.python.outputs.projects }}
DECLARED: ${{ steps.python.outputs.declared }}
RUFF_VERSION: ${{ steps.pins.outputs.ruff }}
run: |
set -Eeuo pipefail
while IFS=$'\t' read -r kind dir; do
Expand All @@ -385,7 +394,7 @@ jobs:
elif [ "$kind" = pip ] && [ "$DECLARED" = "true" ] && "$dir/.venv/bin/python" -c 'import importlib.util, sys; sys.exit(importlib.util.find_spec("ruff") is None)' < /dev/null; then
(cd "$dir" && .venv/bin/python -m ruff format --check .) < /dev/null
else
(cd "$dir" && uvx ruff@latest format --check .) < /dev/null
(cd "$dir" && uvx "ruff@$RUFF_VERSION" format --check .) < /dev/null
fi
done <<< "$PYTHON_PROJECTS"

Expand Down
Loading
Loading