Compare commits

..

15 Commits

Author SHA1 Message Date
f995f89a21 chore(claude): add hooks, skills, and agents for Claude Code (#496)
* chore(claude): add hooks, skills, and agents for Claude Code

Add auto-formatting hooks (ruff, shfmt, prettier, actionlint),
rules.yml edit blocker, 5 skills (/release, /test-action,
/new-action, /validate, /check-pins), and 2 subagents
(action-validator, test-coverage-reviewer). Update CLAUDE.md
with hook documentation.

* fix(claude): add tool availability guards and fix skill docs

Add jq availability checks to hook scripts (block-rules-yml.sh,
post-edit-write.sh) and wrap actionlint call in command -v guard,
consistent with project rules #2 and #10. Fix validate skill to
reflect actual make all pipeline order and note that make test
runs separately.

* fix(claude): correct skill docs per PR review feedback

Fix validate skill description to say "precommit" instead of "test",
and fix check-pins SHA guidance to use origin/main instead of HEAD.

* feat(tools): add SHA-pinning enforcement to check-version-refs

The check-version-refs script previously only displayed existing
SHA-pinned refs but silently skipped non-SHA references. Add a
validation pass that detects and reports any ivuorinen/actions/*
references not using a 40-char hex SHA, exiting 1 on violations.

* fix(tools): fix temp file leak in check-version-refs.sh

Write find output directly to $violations_file instead of
$violations_file.all so the EXIT trap covers cleanup on all
exit paths, not just the happy path.
2026-03-08 04:22:02 +02:00
renovate[bot]
242ecca8f0 chore(deps): update pre-commit hook astral-sh/ruff-pre-commit (v0.15.4 → v0.15.5) (#495)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-07 17:07:03 +02:00
renovate[bot]
97105fc2a9 chore(deps): lock file maintenance (#494)
* chore(deps): update actions/setup-node action (v6.2.0 → v6.3.0)

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* chore(deps): lock file maintenance

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

---------

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 11:17:12 +02:00
renovate[bot]
e9deac2a01 chore(deps)!: update docker/metadata-action (v5.10.0 → v6.0.0) (#493)
* chore(deps): update actions/setup-node action (v6.2.0 → v6.3.0)

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* chore(deps)!: update docker/metadata-action (v5.10.0 → v6.0.0)

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

---------

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 11:16:38 +02:00
renovate[bot]
480c06b83b chore(deps): update actions/setup-node action (v6.2.0 → v6.3.0) (#491)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 11:16:07 +02:00
renovate[bot]
07ce8df887 chore(deps)!: update docker/build-push-action (v6.19.2 → v7.0.0) (#492)
* chore(deps): update actions/setup-node action (v6.2.0 → v6.3.0)

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* chore(deps)!: update docker/build-push-action (v6.19.2 → v7.0.0)

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

---------

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 11:15:36 +02:00
renovate[bot]
0f29424163 chore(deps): update actions/setup-dotnet action (v5.1.0 → v5.2.0) (#490)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 11:15:00 +02:00
renovate[bot]
ce81e51641 chore(deps): update raven-actions/actionlint action (v2.1.1 → v2.1.2) (#488)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 11:14:39 +02:00
renovate[bot]
261323db29 chore(deps): update actions/dependency-review-action action (v4.8.3 → v4.9.0) (#489)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 11:14:15 +02:00
renovate[bot]
c679f0c863 chore(deps): update pre-commit hook astral-sh/uv-pre-commit (0.10.7 → 0.10.8) (#486)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 02:51:06 +02:00
renovate[bot]
128b2f1713 chore(deps): update oven-sh/setup-bun action (v2.1.2 → v2.1.3) (#485)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 02:50:33 +02:00
renovate[bot]
08393e8063 chore(deps): update github/codeql-action action (v4.32.4 → v4.32.6) (#484)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 02:49:32 +02:00
renovate[bot]
ed9f205433 chore(deps): update aquasecurity/trivy-action action (0.34.1 → 0.34.2) (#483)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 02:49:00 +02:00
Copilot
ae4ad9ec80 fix: harden workflow permissions with deny-all top-level and least-privilege job scopes (#482) 2026-03-06 02:44:56 +02:00
renovate[bot]
455267f892 chore(deps): update pre-commit hook bridgecrewio/checkov (3.2.506 → 3.2.507) (#487)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 02:41:33 +02:00
46 changed files with 547 additions and 118 deletions

View File

@@ -0,0 +1,30 @@
You review action.yml files against the repository's critical prevention rules.
Check each action.yml file for these violations:
1. All external action refs are SHA-pinned (not @main/@v1)
2. All internal action refs use `ivuorinen/actions/name@SHA` format
3. Shell scripts use `set -eu` (POSIX, not bash)
4. Steps with referenced outputs have `id:` fields
5. Tool availability checked before use (`command -v`)
6. Variables properly quoted (`"$var"`)
7. `$GITHUB_OUTPUT` uses `printf`, not `echo`
8. No nested `${{ }}` in quoted YAML strings
9. Token inputs use `${{ github.token }}` default
10. Fallbacks provided for tools not on all runners
Run `actionlint` on each file. Report violations with file path, line, and fix suggestion.
To find all action.yml files:
```bash
find . -name "action.yml" -not -path "./.git/*"
```
For each file, read it and check against all 10 rules. Then run:
```bash
actionlint <file>
```
Output a summary table of violations found, grouped by action.

View File

@@ -0,0 +1,33 @@
You review test coverage for GitHub Actions in this monorepo.
For each action:
1. Read the action.yml to understand inputs, outputs, and steps
2. Read the corresponding test files in `_tests/unit/<action-name>/`
3. Check if all inputs have validation tests
4. Check if error paths are tested (missing required inputs, invalid values)
5. Check if shell scripts have edge case tests (spaces in paths, empty strings, special chars)
6. Report coverage gaps with specific test suggestions
To find all actions and their tests:
```bash
ls -d */action.yml | sed 's|/action.yml||'
ls -d _tests/unit/*/
```
Compare the two lists to find actions without any tests.
For each action with tests, check coverage of:
- All required inputs validated
- All optional inputs with defaults tested
- Error conditions (missing inputs, invalid formats)
- Edge cases in shell logic (empty strings, special characters, spaces in paths)
- Output values verified
Output a coverage report with:
- Actions with no tests (critical)
- Actions with partial coverage (list missing test cases)
- Actions with good coverage (brief confirmation)

View File

@@ -0,0 +1,21 @@
#!/bin/sh
set -eu
# Read JSON input from stdin to get the file path
if ! command -v jq >/dev/null 2>&1; then
echo "Error: jq is required but not found" >&2
exit 1
fi
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.filePath // empty')
if [ -z "$FILE_PATH" ]; then
exit 0
fi
case "$FILE_PATH" in
*/rules.yml)
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"rules.yml files are auto-generated. Run make update-validators instead."}}'
;;
esac

View File

@@ -0,0 +1,46 @@
#!/bin/sh
set -eu
# Read JSON input from stdin to get the file path
if ! command -v jq >/dev/null 2>&1; then
echo "Error: jq is required but not found" >&2
exit 1
fi
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.filePath // empty')
if [ -z "$FILE_PATH" ]; then
exit 0
fi
case "$FILE_PATH" in
*/rules.yml)
# rules.yml should not be reached here (blocked by PreToolUse),
# but skip formatting just in case
exit 0
;;
*.py)
ruff format --quiet "$FILE_PATH" 2>/dev/null || true
ruff check --fix --quiet "$FILE_PATH" 2>/dev/null || true
;;
*.sh)
shfmt -w "$FILE_PATH" 2>/dev/null || true
shellcheck "$FILE_PATH" 2>&1 || true
;;
*.yml | *.yaml | *.json)
npx prettier --write "$FILE_PATH" 2>/dev/null || true
;;
*.md)
npx prettier --write "$FILE_PATH" 2>/dev/null || true
;;
esac
# Run actionlint on action.yml files
case "$FILE_PATH" in
*/action.yml)
if command -v actionlint >/dev/null 2>&1; then
actionlint "$FILE_PATH" 2>&1 || true
fi
;;
esac

26
.claude/settings.json Normal file
View File

@@ -0,0 +1,26 @@
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rules-yml.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/post-edit-write.sh"
}
]
}
]
}
}

View File

@@ -0,0 +1,40 @@
---
name: check-pins
description: Verify all action references are properly SHA-pinned
disable-model-invocation: true
---
# Check SHA-Pinned Action References
## 1. Check version references
```bash
make check-version-refs
```
This verifies that all `ivuorinen/actions/*` references in `action.yml` files use SHA-pinned commits.
## 2. Check local references
```bash
make check-local-refs
```
This verifies that test workflows use `./action-name` format (local references are allowed in tests).
## 3. Interpret results
**Violations to fix:**
- `@main` or `@v*` references in `action.yml` files must be replaced with full SHA commits
- `./action-name` in `action.yml` (non-test) files must use `ivuorinen/actions/action-name@<SHA>`
- External actions must be pinned to SHA commits, not version tags
**How to get the SHA for pinning:**
```bash
# After pushing, get the SHA of the latest commit on the remote
git rev-parse origin/main
```
Use a SHA that exists on the remote. Local-only commits won't resolve when the action is used externally.

View File

@@ -0,0 +1,60 @@
---
name: new-action
description: Scaffold a new GitHub Action with all required files
disable-model-invocation: true
---
# Scaffold a New GitHub Action
## 1. Gather information
Ask the user for:
- **Action name** (kebab-case, e.g. `my-new-action`)
- **Description** (one line)
- **Category** (setup, linting, testing, build, publishing, repository, utility)
- **Inputs** (name, description, required, default for each)
- **What it does** (shell commands, composite steps, etc.)
## 2. Create directory and action.yml
Create `<action-name>/action.yml` following the existing action patterns:
- Use `composite` runs type
- Include `set -eu` in shell scripts (POSIX sh, not bash)
- Use `${{ github.token }}` for token defaults
- Pin all external action references to SHA commits
- Pin internal action references using `ivuorinen/actions/action-name@<SHA>`
- Add `id:` to steps whose outputs are referenced
## 3. Generate validation rules
```bash
make update-validators
```
This generates `<action-name>/rules.yml` from the action's inputs.
## 4. Generate test scaffolding
```bash
make generate-tests
```
## 5. Generate README
```bash
make docs
```
## 6. Run validation
```bash
make all
```
Fix any issues before considering the action complete.
## 7. Update repository overview
Remind the user to update the Serena memory `repository_overview` if they use Serena.

View File

@@ -0,0 +1,57 @@
---
name: release
description: Create a new CalVer release with validation checks
disable-model-invocation: true
---
# Release Workflow
Follow these steps to create a new CalVer release:
## 1. Pre-flight checks
Run the full validation pipeline:
```bash
make all
```
If any step fails, fix the issues before proceeding.
## 2. Check version references
Verify all action references are properly pinned:
```bash
make check-version-refs
make check-local-refs
```
## 3. Prepare the release
Run release preparation (updates version references):
```bash
make release-prep
```
Review the changes with `git diff`.
## 4. Confirm with user
Ask the user to confirm:
- The version number (defaults to `vYYYY.MM.DD` based on today's date)
- That all changes look correct
## 5. Create the release
```bash
make release VERSION=vYYYY.MM.DD
```
Replace `vYYYY.MM.DD` with the confirmed version.
## 6. Verify
Show the user the created tag and any output from the release process.

View File

@@ -0,0 +1,34 @@
---
name: test-action
description: Run tests for a specific GitHub Action by name
disable-model-invocation: true
---
# Test a Specific Action
## 1. Identify the action
Ask the user which action to test if not already specified.
List available actions if needed:
```bash
ls -d */action.yml | sed 's|/action.yml||'
```
## 2. Run tests
```bash
make test-action ACTION=<action-name>
```
## 3. Display results
Show the test output. If tests fail, read the relevant test files in `_tests/unit/<action-name>/` and the action's `action.yml` to help diagnose the issue.
## 4. Coverage (optional)
If the user wants coverage information:
```bash
make test-coverage
```

View File

@@ -0,0 +1,51 @@
---
name: validate
description: Run full validation pipeline (docs, format, lint, precommit)
disable-model-invocation: true
---
# Full Validation Pipeline
Run the complete validation pipeline:
```bash
make all
```
This runs in order: `install-tools` -> `update-validators` -> `docs` -> `update-catalog` -> `format` -> `lint` -> `precommit`
**Note:** `make test` must be run separately.
## If validation fails
### Formatting issues
```bash
make format
```
Then re-run `make all`.
### Linting issues
- **actionlint**: Check action.yml syntax, step IDs, expression usage
- **shellcheck**: POSIX compliance, quoting, variable usage
- **ruff**: Python style and errors
- **markdownlint**: Markdown formatting
- **prettier**: YAML/JSON/MD formatting
### Test failures
```bash
make test
```
Read the failing test output and fix the underlying action or test.
### Documentation drift
```bash
make docs
```
Regenerates READMEs from action.yml files.

View File

@@ -31,7 +31,7 @@ runs:
run: uv sync --frozen run: uv sync --frozen
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with: with:
node-version: '24' node-version: '24'
cache: npm cache: npm

View File

@@ -17,10 +17,7 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
permissions: permissions: {}
contents: read
actions: read
pull-requests: read
jobs: jobs:
analyze: analyze:
@@ -29,6 +26,9 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
permissions: permissions:
contents: read
actions: read
pull-requests: read
security-events: write security-events: write
statuses: write statuses: write
issues: write issues: write

View File

@@ -23,15 +23,16 @@ on:
default: 'latest' default: 'latest'
type: string type: string
permissions: permissions: {}
contents: read
packages: write
jobs: jobs:
build-and-push: build-and-push:
name: Build and Push Testing Image name: Build and Push Testing Image
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 20 timeout-minutes: 20
permissions:
contents: read
packages: write
steps: steps:
- name: Checkout repository - name: Checkout repository
@@ -49,7 +50,7 @@ jobs:
- name: Extract metadata - name: Extract metadata
id: meta id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
with: with:
images: ghcr.io/${{ github.repository_owner }}/actions images: ghcr.io/${{ github.repository_owner }}/actions
tags: | tags: |
@@ -59,7 +60,7 @@ jobs:
type=raw,value=${{ github.event.inputs.tag }},enable=${{ github.event.inputs.tag != '' }} type=raw,value=${{ github.event.inputs.tag }},enable=${{ github.event.inputs.tag != '' }}
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
with: with:
context: _tools/docker-testing-tools context: _tools/docker-testing-tools
file: _tools/docker-testing-tools/Dockerfile file: _tools/docker-testing-tools/Dockerfile

View File

@@ -13,17 +13,16 @@ on:
- cron: '30 1 * * 0' # Run at 1:30 AM UTC every Sunday - cron: '30 1 * * 0' # Run at 1:30 AM UTC every Sunday
merge_group: merge_group:
permissions: permissions: {}
actions: read
contents: read
jobs: jobs:
analyze: analyze:
name: Analyze (${{ matrix.language }}) name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
security-events: write actions: read
contents: read contents: read
security-events: write
strategy: strategy:
fail-fast: false fail-fast: false

View File

@@ -4,14 +4,15 @@ name: 'Dependency Review'
on: on:
- pull_request - pull_request
permissions: permissions: {}
contents: read
jobs: jobs:
dependency-review: dependency-review:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
steps: steps:
- name: 'Checkout Repository' - name: 'Checkout Repository'
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
- name: 'Dependency Review' - name: 'Dependency Review'
uses: actions/dependency-review-action@05fe4576374b728f0c523d6a13d64c25081e0803 # v4.8.3 uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0

View File

@@ -5,8 +5,7 @@ on:
schedule: schedule:
- cron: '3 2 1 * *' - cron: '3 2 1 * *'
permissions: permissions: {}
contents: read
jobs: jobs:
build: build:

View File

@@ -6,7 +6,7 @@ on:
schedule: schedule:
- cron: '0 21 * * *' # 00:00 at Europe/Helsinki - cron: '0 21 * * *' # 00:00 at Europe/Helsinki
permissions: read-all permissions: {}
jobs: jobs:
new-daily-release: new-daily-release:

View File

@@ -37,9 +37,7 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
permissions: permissions: {}
contents: read
packages: read # Required for private dependencies
jobs: jobs:
megalinter: megalinter:
@@ -74,7 +72,7 @@ jobs:
- name: Upload SARIF Report - name: Upload SARIF Report
if: always() && hashFiles('megalinter-reports/sarif/*.sarif') if: always() && hashFiles('megalinter-reports/sarif/*.sarif')
uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
with: with:
sarif_file: megalinter-reports/sarif sarif_file: megalinter-reports/sarif
category: megalinter category: megalinter

View File

@@ -7,8 +7,7 @@ on:
tags: tags:
- 'v*' - 'v*'
permissions: permissions: {}
contents: read
jobs: jobs:
release: release:

View File

@@ -18,11 +18,7 @@ on:
- '**/*.yaml' - '**/*.yaml'
- '.github/workflows/**' - '.github/workflows/**'
permissions: permissions: {}
contents: read
pull-requests: write
issues: write
actions: read
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }} group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
@@ -32,6 +28,11 @@ jobs:
security-analysis: security-analysis:
name: Security Analysis name: Security Analysis
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: write
actions: read
steps: steps:
- name: Checkout PR - name: Checkout PR

View File

@@ -8,10 +8,7 @@ on:
workflow_call: workflow_call:
workflow_dispatch: workflow_dispatch:
permissions: permissions: {}
contents: read
packages: read
statuses: read
jobs: jobs:
stale: stale:

View File

@@ -22,7 +22,7 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
permissions: read-all permissions: {}
jobs: jobs:
labels: labels:
@@ -31,6 +31,7 @@ jobs:
timeout-minutes: 10 timeout-minutes: 10
permissions: permissions:
contents: read
issues: write issues: write
steps: steps:

View File

@@ -73,7 +73,7 @@ jobs:
if: always() if: always()
- name: Upload SARIF file - name: Upload SARIF file
uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
if: always() && hashFiles('_tests/reports/test-results.sarif') != '' if: always() && hashFiles('_tests/reports/test-results.sarif') != ''
with: with:
sarif_file: _tests/reports/test-results.sarif sarif_file: _tests/reports/test-results.sarif

View File

@@ -12,15 +12,16 @@ on:
required: false required: false
type: string type: string
permissions: permissions: {}
contents: write
pull-requests: write
issues: write
jobs: jobs:
check-and-update: check-and-update:
name: Check Version References name: Check Version References
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
steps: steps:
- name: Checkout Repository - name: Checkout Repository

View File

@@ -14,7 +14,7 @@ repos:
types: [markdown, python, yaml] types: [markdown, python, yaml]
files: ^(docs/.*|README\.md|CONTRIBUTING\.md|CHANGELOG\.md|.*\.py|.*\.ya?ml)$ files: ^(docs/.*|README\.md|CONTRIBUTING\.md|CHANGELOG\.md|.*\.py|.*\.ya?ml)$
- repo: https://github.com/astral-sh/uv-pre-commit - repo: https://github.com/astral-sh/uv-pre-commit
rev: 0.10.7 rev: 0.10.8
hooks: hooks:
- id: uv-lock - id: uv-lock
- id: uv-sync - id: uv-sync
@@ -55,7 +55,7 @@ repos:
- id: yamllint - id: yamllint
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.4 rev: v0.15.5
hooks: hooks:
# Run the linter with auto-fix # Run the linter with auto-fix
- id: ruff-check - id: ruff-check
@@ -84,7 +84,7 @@ repos:
args: ['-shellcheck='] args: ['-shellcheck=']
- repo: https://github.com/bridgecrewio/checkov.git - repo: https://github.com/bridgecrewio/checkov.git
rev: '3.2.506' rev: '3.2.507'
hooks: hooks:
- id: checkov - id: checkov
args: args:

View File

@@ -25,12 +25,22 @@
### Folders ### Folders
- `.serena/` Internal config (do not edit) - `.serena/` Internal config (do not edit)
- `.claude/hooks/` Claude Code hook scripts (auto-format, lint, block rules.yml edits)
- `.claude/skills/` Claude Code skills (`/release`, `/test-action`, `/new-action`, `/validate`, `/check-pins`)
- `.claude/agents/` Claude Code subagents (action-validator, test-coverage-reviewer)
- `.github/` Workflows/templates - `.github/` Workflows/templates
- `_tests/` ShellSpec tests - `_tests/` ShellSpec tests
- `_tools/` Helper tools - `_tools/` Helper tools
- `validate-inputs/` Python validation system + tests - `validate-inputs/` Python validation system + tests
- `*/rules.yml` Auto-generated validation rules - `*/rules.yml` Auto-generated validation rules
### Claude Code Hooks
**Auto-formatting**: PostToolUse hooks auto-format files on Edit/Write (ruff for .py, shfmt for .sh, prettier for .yml/.yaml/.json/.md, actionlint for action.yml)
**Blocked edits**: PreToolUse hook blocks direct edits to `rules.yml` (auto-generated, use `make update-validators`)
**Hook schema**: `matcher` is a regex string matching tool names (e.g. `"Edit|Write"`), not an object. File filtering done in hook scripts via stdin JSON (`jq -r '.tool_input.file_path'`)
**Reference**: `$CLAUDE_PROJECT_DIR` for project-relative paths in hook commands
### Memory System ### Memory System
**Location**: `.serena/memories/` (9 consolidated memories for context) **Location**: `.serena/memories/` (9 consolidated memories for context)

View File

@@ -210,7 +210,7 @@ bump-major-version: ## Replace one major version with another (usage: make bump-
@sh _tools/bump-major-version.sh "$(OLD)" "$(NEW)" @sh _tools/bump-major-version.sh "$(OLD)" "$(NEW)"
@echo "$(GREEN)✅ Major version bumped$(RESET)" @echo "$(GREEN)✅ Major version bumped$(RESET)"
check-version-refs: ## List all current SHA-pinned action references check-version-refs: ## Verify all action references are SHA-pinned
@echo "$(BLUE)🔍 Checking action references...$(RESET)" @echo "$(BLUE)🔍 Checking action references...$(RESET)"
@sh _tools/check-version-refs.sh @sh _tools/check-version-refs.sh

View File

@@ -23,19 +23,43 @@ for tool in find grep sed printf sort cut tr wc; do
fi fi
done done
# --- Validation pass: detect non-SHA-pinned references ---
violations_file=$(safe_mktemp)
trap 'rm -f "$violations_file"' EXIT
find . -maxdepth 2 -name "action.yml" -path "*/action.yml" \
! -path "./_*" ! -path "./.github/*" \
-exec grep -nE '^\s+uses:\s+ivuorinen/actions/' {} /dev/null \; \
>"$violations_file"
violations_found=false
while IFS= read -r match; do
if ! printf '%s\n' "$match" | grep -qE '@[0-9a-f]{40}'; then
if [ "$violations_found" = false ]; then
msg_error "Non-SHA-pinned action references found:"
violations_found=true
fi
printf ' %s\n' "$match" >&2
fi
done <"$violations_file"
if [ "$violations_found" = true ]; then
rm -f "$violations_file"
exit 1
fi
rm -f "$violations_file"
printf '%b' "${BLUE}Current SHA-pinned action references:${NC}\n" printf '%b' "${BLUE}Current SHA-pinned action references:${NC}\n"
printf '\n' printf '\n'
# Create temp files for processing # Create temp files for processing
temp_file=$(safe_mktemp) temp_file=$(safe_mktemp)
trap 'rm -f "$temp_file"' EXIT
temp_input=$(safe_mktemp) temp_input=$(safe_mktemp)
trap 'rm -f "$temp_file" "$temp_input"' EXIT trap 'rm -f "$temp_file" "$temp_input"' EXIT
# Find all action references and collect SHA|action pairs # Find all action references and collect SHA|action pairs
# Use input redirection to avoid subshell issues with pipeline # Use input redirection to avoid subshell issues with pipeline
find . -maxdepth 2 -name "action.yml" -path "*/action.yml" ! -path "./_*" ! -path "./.github/*" -exec grep -h "uses: ivuorinen/actions/" {} \; > "$temp_input" find . -maxdepth 2 -name "action.yml" -path "*/action.yml" ! -path "./_*" ! -path "./.github/*" -exec grep -h "uses: ivuorinen/actions/" {} \; >"$temp_input"
while IFS= read -r line; do while IFS= read -r line; do
# Extract action name and SHA using sed # Extract action name and SHA using sed
@@ -43,9 +67,9 @@ while IFS= read -r line; do
sha=$(echo "$line" | sed -n 's|.*@\([a-f0-9]\{40\}\).*|\1|p') sha=$(echo "$line" | sed -n 's|.*@\([a-f0-9]\{40\}\).*|\1|p')
if [ -n "$action" ] && [ -n "$sha" ]; then if [ -n "$action" ] && [ -n "$sha" ]; then
printf '%s\n' "$sha|$action" >> "$temp_file" printf '%s\n' "$sha|$action" >>"$temp_file"
fi fi
done < "$temp_input" done <"$temp_input"
# Check if we found any references # Check if we found any references
if [ ! -s "$temp_file" ]; then if [ ! -s "$temp_file" ]; then
@@ -54,7 +78,7 @@ if [ ! -s "$temp_file" ]; then
fi fi
# Sort by SHA and group # Sort by SHA and group
sort "$temp_file" | uniq > "${temp_file}.sorted" sort "$temp_file" | uniq >"${temp_file}.sorted"
mv "${temp_file}.sorted" "$temp_file" mv "${temp_file}.sorted" "$temp_file"
# Count unique SHAs # Count unique SHAs
@@ -95,7 +119,7 @@ while IFS='|' read -r sha action; do
# Add to current SHA group # Add to current SHA group
actions_list="$actions_list, $action" actions_list="$actions_list, $action"
fi fi
done < "$temp_file" done <"$temp_file"
# Print last SHA group # Print last SHA group
if [ -n "$current_sha" ]; then if [ -n "$current_sha" ]; then

View File

@@ -130,6 +130,6 @@ runs:
- name: Upload SARIF Report - name: Upload SARIF Report
if: steps.check-files.outputs.files_found == 'true' if: steps.check-files.outputs.files_found == 'true'
uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
with: with:
sarif_file: ansible-lint.sarif sarif_file: ansible-lint.sarif

View File

@@ -181,7 +181,7 @@ runs:
echo "Detected package manager: $package_manager" echo "Detected package manager: $package_manager"
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with: with:
node-version: '24' node-version: '24'
@@ -212,7 +212,7 @@ runs:
- name: Setup Bun - name: Setup Bun
if: steps.detect-pm.outputs.package-manager == 'bun' if: steps.detect-pm.outputs.package-manager == 'bun'
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2 uses: oven-sh/setup-bun@ecf28ddc73e819eb6fa29df6b34ef8921c743461 # v2.1.3
with: with:
bun-version: latest bun-version: latest
@@ -331,7 +331,7 @@ runs:
- name: Upload SARIF Report - name: Upload SARIF Report
if: inputs.mode == 'check' && always() if: inputs.mode == 'check' && always()
uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
with: with:
sarif_file: biome-report.sarif sarif_file: biome-report.sarif

View File

@@ -186,7 +186,7 @@ runs:
echo "Using build mode: $build_mode" echo "Using build mode: $build_mode"
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
with: with:
languages: ${{ inputs.language }} languages: ${{ inputs.language }}
queries: ${{ inputs.queries }} queries: ${{ inputs.queries }}
@@ -199,12 +199,12 @@ runs:
threads: ${{ inputs.threads }} threads: ${{ inputs.threads }}
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 uses: github/codeql-action/autobuild@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
if: ${{ steps.set-build-mode.outputs.build-mode == 'autobuild' }} if: ${{ steps.set-build-mode.outputs.build-mode == 'autobuild' }}
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
id: analysis id: analysis
uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
with: with:
category: ${{ steps.set-category.outputs.category }} category: ${{ steps.set-category.outputs.category }}
upload: ${{ inputs.upload-results }} upload: ${{ inputs.upload-results }}

View File

@@ -148,7 +148,7 @@ runs:
echo "Final detected .NET version: $detected_version" >&2 echo "Final detected .NET version: $detected_version" >&2
- name: Setup .NET SDK - name: Setup .NET SDK
uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0 uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with: with:
dotnet-version: ${{ steps.detect-dotnet-version.outputs.detected-version }} dotnet-version: ${{ steps.detect-dotnet-version.outputs.detected-version }}
cache: true cache: true

View File

@@ -164,7 +164,7 @@ runs:
echo "Final detected .NET version: $detected_version" >&2 echo "Final detected .NET version: $detected_version" >&2
- name: Setup .NET SDK - name: Setup .NET SDK
uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0 uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with: with:
dotnet-version: ${{ steps.detect-dotnet-version.outputs.detected-version }} dotnet-version: ${{ steps.detect-dotnet-version.outputs.detected-version }}
cache: true cache: true
@@ -206,6 +206,6 @@ runs:
fi fi
- name: Upload SARIF Report - name: Upload SARIF Report
uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
with: with:
sarif_file: dotnet-format.sarif sarif_file: dotnet-format.sarif

View File

@@ -162,7 +162,7 @@ runs:
echo "Final detected .NET version: $detected_version" >&2 echo "Final detected .NET version: $detected_version" >&2
- name: Setup .NET SDK - name: Setup .NET SDK
uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0 uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with: with:
dotnet-version: ${{ inputs.dotnet-version || steps.detect-dotnet-version.outputs.detected-version }} dotnet-version: ${{ inputs.dotnet-version || steps.detect-dotnet-version.outputs.detected-version }}
cache: true cache: true

View File

@@ -536,7 +536,7 @@ runs:
- name: Scan Image for Vulnerabilities - name: Scan Image for Vulnerabilities
id: scan id: scan
if: inputs.scan-image == 'true' && inputs.dry-run != 'true' if: inputs.scan-image == 'true' && inputs.dry-run != 'true'
uses: aquasecurity/trivy-action@e368e328979b113139d6f9068e03accaed98a518 # 0.34.1 uses: aquasecurity/trivy-action@97e0b3872f55f89b95b2f65b3dbab56962816478 # 0.34.2
with: with:
scan-type: 'image' scan-type: 'image'
image-ref: ${{ steps.image-name.outputs.name }}:${{ inputs.tag }} image-ref: ${{ steps.image-name.outputs.name }}:${{ inputs.tag }}

View File

@@ -280,7 +280,7 @@ runs:
- name: Build and Push Docker Image - name: Build and Push Docker Image
id: build id: build
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
with: with:
context: ${{ inputs.context }} context: ${{ inputs.context }}
file: ${{ inputs.dockerfile }} file: ${{ inputs.dockerfile }}

View File

@@ -288,7 +288,7 @@ runs:
echo "Detected package manager: $package_manager" echo "Detected package manager: $package_manager"
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with: with:
node-version: '24' node-version: '24'
@@ -319,7 +319,7 @@ runs:
- name: Setup Bun - name: Setup Bun
if: steps.detect-pm.outputs.package-manager == 'bun' if: steps.detect-pm.outputs.package-manager == 'bun'
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2 uses: oven-sh/setup-bun@ecf28ddc73e819eb6fa29df6b34ef8921c743461 # v2.1.3
with: with:
bun-version: latest bun-version: latest
@@ -457,7 +457,7 @@ runs:
- name: Upload SARIF Report - name: Upload SARIF Report
if: inputs.mode == 'check' && inputs.report-format == 'sarif' && always() if: inputs.mode == 'check' && inputs.report-format == 'sarif' && always()
uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
with: with:
sarif_file: ${{ inputs.working-directory }}/eslint-results.sarif sarif_file: ${{ inputs.working-directory }}/eslint-results.sarif

View File

@@ -414,7 +414,7 @@ runs:
- name: Upload Lint Results - name: Upload Lint Results
if: always() && inputs.report-format == 'sarif' if: always() && inputs.report-format == 'sarif'
uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
with: with:
sarif_file: ${{ inputs.working-directory }}/reports/golangci-lint.sarif sarif_file: ${{ inputs.working-directory }}/reports/golangci-lint.sarif
category: golangci-lint category: golangci-lint

View File

@@ -121,7 +121,7 @@ runs:
echo "Detected package manager: $package_manager" echo "Detected package manager: $package_manager"
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with: with:
node-version: '24' node-version: '24'
@@ -152,7 +152,7 @@ runs:
- name: Setup Bun - name: Setup Bun
if: steps.detect-pm.outputs.package-manager == 'bun' if: steps.detect-pm.outputs.package-manager == 'bun'
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2 uses: oven-sh/setup-bun@ecf28ddc73e819eb6fa29df6b34ef8921c743461 # v2.1.3
with: with:
bun-version: latest bun-version: latest

View File

@@ -118,7 +118,7 @@ runs:
- name: Setup Node.js - name: Setup Node.js
if: steps.detect-node.outputs.found == 'true' if: steps.detect-node.outputs.found == 'true'
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with: with:
node-version: '24' node-version: '24'
@@ -156,7 +156,7 @@ runs:
- name: Setup Bun - name: Setup Bun
if: steps.detect-node.outputs.found == 'true' && steps.detect-pm.outputs.package-manager == 'bun' if: steps.detect-node.outputs.found == 'true' && steps.detect-pm.outputs.package-manager == 'bun'
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2 uses: oven-sh/setup-bun@ecf28ddc73e819eb6fa29df6b34ef8921c743461 # v2.1.3
with: with:
bun-version: latest bun-version: latest

View File

@@ -274,7 +274,7 @@ runs:
echo "Detected package manager: $package_manager" echo "Detected package manager: $package_manager"
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with: with:
node-version: '24' node-version: '24'
@@ -305,7 +305,7 @@ runs:
- name: Setup Bun - name: Setup Bun
if: steps.detect-pm.outputs.package-manager == 'bun' if: steps.detect-pm.outputs.package-manager == 'bun'
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2 uses: oven-sh/setup-bun@ecf28ddc73e819eb6fa29df6b34ef8921c743461 # v2.1.3
with: with:
bun-version: latest bun-version: latest

View File

@@ -370,7 +370,7 @@ runs:
- name: Upload SARIF Report - name: Upload SARIF Report
if: steps.check-files.outputs.result == 'found' if: steps.check-files.outputs.result == 'found'
uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
with: with:
sarif_file: ${{ inputs.working-directory }}/reports/flake8.sarif sarif_file: ${{ inputs.working-directory }}/reports/flake8.sarif
category: 'python-lint' category: 'python-lint'

View File

@@ -99,7 +99,7 @@ runs:
- name: Run actionlint - name: Run actionlint
if: steps.check-configs.outputs.run_actionlint == 'true' if: steps.check-configs.outputs.run_actionlint == 'true'
uses: raven-actions/actionlint@e01d1ea33dd6a5ed517d95b4c0c357560ac6f518 # v2.1.1 uses: raven-actions/actionlint@205b530c5d9fa8f44ae9ed59f341a0db994aa6f8 # v2.1.2
with: with:
cache: true cache: true
fail-on-error: true fail-on-error: true
@@ -161,14 +161,14 @@ runs:
- name: Upload Trivy results - name: Upload Trivy results
if: steps.verify-sarif.outputs.has_trivy == 'true' if: steps.verify-sarif.outputs.has_trivy == 'true'
uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
with: with:
sarif_file: 'trivy-results.sarif' sarif_file: 'trivy-results.sarif'
category: 'trivy' category: 'trivy'
- name: Upload Gitleaks results - name: Upload Gitleaks results
if: steps.verify-sarif.outputs.has_gitleaks == 'true' if: steps.verify-sarif.outputs.has_gitleaks == 'true'
uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
with: with:
sarif_file: 'gitleaks-report.sarif' sarif_file: 'gitleaks-report.sarif'
category: 'gitleaks' category: 'gitleaks'

View File

@@ -256,7 +256,7 @@ runs:
- name: Upload SARIF Report - name: Upload SARIF Report
if: steps.check-files.outputs.found == 'true' && inputs.format == 'sarif' if: steps.check-files.outputs.found == 'true' && inputs.format == 'sarif'
uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
with: with:
sarif_file: ${{ env.VALIDATED_WORKING_DIR }}/reports/tflint.sarif sarif_file: ${{ env.VALIDATED_WORKING_DIR }}/reports/tflint.sarif
category: terraform-lint category: terraform-lint

38
uv.lock generated
View File

@@ -299,27 +299,27 @@ wheels = [
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.15.4" version = "0.15.5"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/da/31/d6e536cdebb6568ae75a7f00e4b4819ae0ad2640c3604c305a0428680b0c/ruff-0.15.4.tar.gz", hash = "sha256:3412195319e42d634470cc97aa9803d07e9d5c9223b99bcb1518f0c725f26ae1", size = 4569550, upload-time = "2026-02-26T20:04:14.959Z" } sdist = { url = "https://files.pythonhosted.org/packages/77/9b/840e0039e65fcf12758adf684d2289024d6140cde9268cc59887dc55189c/ruff-0.15.5.tar.gz", hash = "sha256:7c3601d3b6d76dce18c5c824fc8d06f4eef33d6df0c21ec7799510cde0f159a2", size = 4574214, upload-time = "2026-03-05T20:06:34.946Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/f2/82/c11a03cfec3a4d26a0ea1e571f0f44be5993b923f905eeddfc397c13d360/ruff-0.15.4-py3-none-linux_armv6l.whl", hash = "sha256:a1810931c41606c686bae8b5b9a8072adac2f611bb433c0ba476acba17a332e0", size = 10453333, upload-time = "2026-02-26T20:04:20.093Z" }, { url = "https://files.pythonhosted.org/packages/47/20/5369c3ce21588c708bcbe517a8fbe1a8dfdb5dfd5137e14790b1da71612c/ruff-0.15.5-py3-none-linux_armv6l.whl", hash = "sha256:4ae44c42281f42e3b06b988e442d344a5b9b72450ff3c892e30d11b29a96a57c", size = 10478185, upload-time = "2026-03-05T20:06:29.093Z" },
{ url = "https://files.pythonhosted.org/packages/ce/5d/6a1f271f6e31dffb31855996493641edc3eef8077b883eaf007a2f1c2976/ruff-0.15.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5a1632c66672b8b4d3e1d1782859e98d6e0b4e70829530666644286600a33992", size = 10853356, upload-time = "2026-02-26T20:04:05.808Z" }, { url = "https://files.pythonhosted.org/packages/44/ed/e81dd668547da281e5dce710cf0bc60193f8d3d43833e8241d006720e42b/ruff-0.15.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6edd3792d408ebcf61adabc01822da687579a1a023f297618ac27a5b51ef0080", size = 10859201, upload-time = "2026-03-05T20:06:32.632Z" },
{ url = "https://files.pythonhosted.org/packages/b1/d8/0fab9f8842b83b1a9c2bf81b85063f65e93fb512e60effa95b0be49bfc54/ruff-0.15.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a4386ba2cd6c0f4ff75252845906acc7c7c8e1ac567b7bc3d373686ac8c222ba", size = 10187434, upload-time = "2026-02-26T20:03:54.656Z" }, { url = "https://files.pythonhosted.org/packages/c4/8f/533075f00aaf19b07c5cd6aa6e5d89424b06b3b3f4583bfa9c640a079059/ruff-0.15.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:89f463f7c8205a9f8dea9d658d59eff49db05f88f89cc3047fb1a02d9f344010", size = 10184752, upload-time = "2026-03-05T20:06:40.312Z" },
{ url = "https://files.pythonhosted.org/packages/85/cc/cc220fd9394eff5db8d94dec199eec56dd6c9f3651d8869d024867a91030/ruff-0.15.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2496488bdfd3732747558b6f95ae427ff066d1fcd054daf75f5a50674411e75", size = 10535456, upload-time = "2026-02-26T20:03:52.738Z" }, { url = "https://files.pythonhosted.org/packages/66/0e/ba49e2c3fa0395b3152bad634c7432f7edfc509c133b8f4529053ff024fb/ruff-0.15.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba786a8295c6574c1116704cf0b9e6563de3432ac888d8f83685654fe528fd65", size = 10534857, upload-time = "2026-03-05T20:06:19.581Z" },
{ url = "https://files.pythonhosted.org/packages/fa/0f/bced38fa5cf24373ec767713c8e4cadc90247f3863605fb030e597878661/ruff-0.15.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f1c4893841ff2d54cbda1b2860fa3260173df5ddd7b95d370186f8a5e66a4ac", size = 10287772, upload-time = "2026-02-26T20:04:08.138Z" }, { url = "https://files.pythonhosted.org/packages/59/71/39234440f27a226475a0659561adb0d784b4d247dfe7f43ffc12dd02e288/ruff-0.15.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd4b801e57955fe9f02b31d20375ab3a5c4415f2e5105b79fb94cf2642c91440", size = 10309120, upload-time = "2026-03-05T20:06:00.435Z" },
{ url = "https://files.pythonhosted.org/packages/2b/90/58a1802d84fed15f8f281925b21ab3cecd813bde52a8ca033a4de8ab0e7a/ruff-0.15.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:820b8766bd65503b6c30aaa6331e8ef3a6e564f7999c844e9a547c40179e440a", size = 11049051, upload-time = "2026-02-26T20:04:03.53Z" }, { url = "https://files.pythonhosted.org/packages/f5/87/4140aa86a93df032156982b726f4952aaec4a883bb98cb6ef73c347da253/ruff-0.15.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391f7c73388f3d8c11b794dbbc2959a5b5afe66642c142a6effa90b45f6f5204", size = 11047428, upload-time = "2026-03-05T20:05:51.867Z" },
{ url = "https://files.pythonhosted.org/packages/d2/ac/b7ad36703c35f3866584564dc15f12f91cb1a26a897dc2fd13d7cb3ae1af/ruff-0.15.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9fb74bab47139c1751f900f857fa503987253c3ef89129b24ed375e72873e85", size = 11890494, upload-time = "2026-02-26T20:04:10.497Z" }, { url = "https://files.pythonhosted.org/packages/5a/f7/4953e7e3287676f78fbe85e3a0ca414c5ca81237b7575bdadc00229ac240/ruff-0.15.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dc18f30302e379fe1e998548b0f5e9f4dff907f52f73ad6da419ea9c19d66c8", size = 11914251, upload-time = "2026-03-05T20:06:22.887Z" },
{ url = "https://files.pythonhosted.org/packages/93/3d/3eb2f47a39a8b0da99faf9c54d3eb24720add1e886a5309d4d1be73a6380/ruff-0.15.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f80c98765949c518142b3a50a5db89343aa90f2c2bf7799de9986498ae6176db", size = 11326221, upload-time = "2026-02-26T20:04:12.84Z" }, { url = "https://files.pythonhosted.org/packages/77/46/0f7c865c10cf896ccf5a939c3e84e1cfaeed608ff5249584799a74d33835/ruff-0.15.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc6e7f90087e2d27f98dc34ed1b3ab7c8f0d273cc5431415454e22c0bd2a681", size = 11333801, upload-time = "2026-03-05T20:05:57.168Z" },
{ url = "https://files.pythonhosted.org/packages/ff/90/bf134f4c1e5243e62690e09d63c55df948a74084c8ac3e48a88468314da6/ruff-0.15.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451a2e224151729b3b6c9ffb36aed9091b2996fe4bdbd11f47e27d8f2e8888ec", size = 11168459, upload-time = "2026-02-26T20:04:00.969Z" }, { url = "https://files.pythonhosted.org/packages/d3/01/a10fe54b653061585e655f5286c2662ebddb68831ed3eaebfb0eb08c0a16/ruff-0.15.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1cb7169f53c1ddb06e71a9aebd7e98fc0fea936b39afb36d8e86d36ecc2636a", size = 11206821, upload-time = "2026-03-05T20:06:03.441Z" },
{ url = "https://files.pythonhosted.org/packages/b5/e5/a64d27688789b06b5d55162aafc32059bb8c989c61a5139a36e1368285eb/ruff-0.15.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a8f157f2e583c513c4f5f896163a93198297371f34c04220daf40d133fdd4f7f", size = 11104366, upload-time = "2026-02-26T20:03:48.099Z" }, { url = "https://files.pythonhosted.org/packages/7a/0d/2132ceaf20c5e8699aa83da2706ecb5c5dcdf78b453f77edca7fb70f8a93/ruff-0.15.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9b037924500a31ee17389b5c8c4d88874cc6ea8e42f12e9c61a3d754ff72f1ca", size = 11133326, upload-time = "2026-03-05T20:06:25.655Z" },
{ url = "https://files.pythonhosted.org/packages/f1/f6/32d1dcb66a2559763fc3027bdd65836cad9eb09d90f2ed6a63d8e9252b02/ruff-0.15.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:917cc68503357021f541e69b35361c99387cdbbf99bd0ea4aa6f28ca99ff5338", size = 10510887, upload-time = "2026-02-26T20:03:45.771Z" }, { url = "https://files.pythonhosted.org/packages/72/cb/2e5259a7eb2a0f87c08c0fe5bf5825a1e4b90883a52685524596bfc93072/ruff-0.15.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:65bb414e5b4eadd95a8c1e4804f6772bbe8995889f203a01f77ddf2d790929dd", size = 10510820, upload-time = "2026-03-05T20:06:37.79Z" },
{ url = "https://files.pythonhosted.org/packages/ff/92/22d1ced50971c5b6433aed166fcef8c9343f567a94cf2b9d9089f6aa80fe/ruff-0.15.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e9737c8161da79fd7cfec19f1e35620375bd8b2a50c3e77fa3d2c16f574105cc", size = 10285939, upload-time = "2026-02-26T20:04:22.42Z" }, { url = "https://files.pythonhosted.org/packages/ff/20/b67ce78f9e6c59ffbdb5b4503d0090e749b5f2d31b599b554698a80d861c/ruff-0.15.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d20aa469ae3b57033519c559e9bc9cd9e782842e39be05b50e852c7c981fa01d", size = 10302395, upload-time = "2026-03-05T20:05:54.504Z" },
{ url = "https://files.pythonhosted.org/packages/e6/f4/7c20aec3143837641a02509a4668fb146a642fd1211846634edc17eb5563/ruff-0.15.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:291258c917539e18f6ba40482fe31d6f5ac023994ee11d7bdafd716f2aab8a68", size = 10765471, upload-time = "2026-02-26T20:03:58.924Z" }, { url = "https://files.pythonhosted.org/packages/5f/e5/719f1acccd31b720d477751558ed74e9c88134adcc377e5e886af89d3072/ruff-0.15.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:15388dd28c9161cdb8eda68993533acc870aa4e646a0a277aa166de9ad5a8752", size = 10754069, upload-time = "2026-03-05T20:06:06.422Z" },
{ url = "https://files.pythonhosted.org/packages/d0/09/6d2f7586f09a16120aebdff8f64d962d7c4348313c77ebb29c566cefc357/ruff-0.15.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3f83c45911da6f2cd5936c436cf86b9f09f09165f033a99dcf7477e34041cbc3", size = 11263382, upload-time = "2026-02-26T20:04:24.424Z" }, { url = "https://files.pythonhosted.org/packages/c3/9c/d1db14469e32d98f3ca27079dbd30b7b44dbb5317d06ab36718dee3baf03/ruff-0.15.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b30da330cbd03bed0c21420b6b953158f60c74c54c5f4c1dabbdf3a57bf355d2", size = 11304315, upload-time = "2026-03-05T20:06:10.867Z" },
{ url = "https://files.pythonhosted.org/packages/1b/fa/2ef715a1cd329ef47c1a050e10dee91a9054b7ce2fcfdd6a06d139afb7ec/ruff-0.15.4-py3-none-win32.whl", hash = "sha256:65594a2d557d4ee9f02834fcdf0a28daa8b3b9f6cb2cb93846025a36db47ef22", size = 10506664, upload-time = "2026-02-26T20:03:50.56Z" }, { url = "https://files.pythonhosted.org/packages/28/3a/950367aee7c69027f4f422059227b290ed780366b6aecee5de5039d50fa8/ruff-0.15.5-py3-none-win32.whl", hash = "sha256:732e5ee1f98ba5b3679029989a06ca39a950cced52143a0ea82a2102cb592b74", size = 10551676, upload-time = "2026-03-05T20:06:13.705Z" },
{ url = "https://files.pythonhosted.org/packages/d0/a8/c688ef7e29983976820d18710f955751d9f4d4eb69df658af3d006e2ba3e/ruff-0.15.4-py3-none-win_amd64.whl", hash = "sha256:04196ad44f0df220c2ece5b0e959c2f37c777375ec744397d21d15b50a75264f", size = 11651048, upload-time = "2026-02-26T20:04:17.191Z" }, { url = "https://files.pythonhosted.org/packages/b8/00/bf077a505b4e649bdd3c47ff8ec967735ce2544c8e4a43aba42ee9bf935d/ruff-0.15.5-py3-none-win_amd64.whl", hash = "sha256:821d41c5fa9e19117616c35eaa3f4b75046ec76c65e7ae20a333e9a8696bc7fe", size = 11678972, upload-time = "2026-03-05T20:06:45.379Z" },
{ url = "https://files.pythonhosted.org/packages/3e/0a/9e1be9035b37448ce2e68c978f0591da94389ade5a5abafa4cf99985d1b2/ruff-0.15.4-py3-none-win_arm64.whl", hash = "sha256:60d5177e8cfc70e51b9c5fad936c634872a74209f934c1e79107d11787ad5453", size = 10966776, upload-time = "2026-02-26T20:03:56.908Z" }, { url = "https://files.pythonhosted.org/packages/fe/4e/cd76eca6db6115604b7626668e891c9dd03330384082e33662fb0f113614/ruff-0.15.5-py3-none-win_arm64.whl", hash = "sha256:b498d1c60d2fe5c10c45ec3f698901065772730b411f164ae270bb6bfcc4740b", size = 10965572, upload-time = "2026-03-05T20:06:16.984Z" },
] ]
[[package]] [[package]]

View File

@@ -631,27 +631,27 @@ wheels = [
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.15.4" version = "0.15.5"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/da/31/d6e536cdebb6568ae75a7f00e4b4819ae0ad2640c3604c305a0428680b0c/ruff-0.15.4.tar.gz", hash = "sha256:3412195319e42d634470cc97aa9803d07e9d5c9223b99bcb1518f0c725f26ae1", size = 4569550, upload-time = "2026-02-26T20:04:14.959Z" } sdist = { url = "https://files.pythonhosted.org/packages/77/9b/840e0039e65fcf12758adf684d2289024d6140cde9268cc59887dc55189c/ruff-0.15.5.tar.gz", hash = "sha256:7c3601d3b6d76dce18c5c824fc8d06f4eef33d6df0c21ec7799510cde0f159a2", size = 4574214, upload-time = "2026-03-05T20:06:34.946Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/f2/82/c11a03cfec3a4d26a0ea1e571f0f44be5993b923f905eeddfc397c13d360/ruff-0.15.4-py3-none-linux_armv6l.whl", hash = "sha256:a1810931c41606c686bae8b5b9a8072adac2f611bb433c0ba476acba17a332e0", size = 10453333, upload-time = "2026-02-26T20:04:20.093Z" }, { url = "https://files.pythonhosted.org/packages/47/20/5369c3ce21588c708bcbe517a8fbe1a8dfdb5dfd5137e14790b1da71612c/ruff-0.15.5-py3-none-linux_armv6l.whl", hash = "sha256:4ae44c42281f42e3b06b988e442d344a5b9b72450ff3c892e30d11b29a96a57c", size = 10478185, upload-time = "2026-03-05T20:06:29.093Z" },
{ url = "https://files.pythonhosted.org/packages/ce/5d/6a1f271f6e31dffb31855996493641edc3eef8077b883eaf007a2f1c2976/ruff-0.15.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5a1632c66672b8b4d3e1d1782859e98d6e0b4e70829530666644286600a33992", size = 10853356, upload-time = "2026-02-26T20:04:05.808Z" }, { url = "https://files.pythonhosted.org/packages/44/ed/e81dd668547da281e5dce710cf0bc60193f8d3d43833e8241d006720e42b/ruff-0.15.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6edd3792d408ebcf61adabc01822da687579a1a023f297618ac27a5b51ef0080", size = 10859201, upload-time = "2026-03-05T20:06:32.632Z" },
{ url = "https://files.pythonhosted.org/packages/b1/d8/0fab9f8842b83b1a9c2bf81b85063f65e93fb512e60effa95b0be49bfc54/ruff-0.15.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a4386ba2cd6c0f4ff75252845906acc7c7c8e1ac567b7bc3d373686ac8c222ba", size = 10187434, upload-time = "2026-02-26T20:03:54.656Z" }, { url = "https://files.pythonhosted.org/packages/c4/8f/533075f00aaf19b07c5cd6aa6e5d89424b06b3b3f4583bfa9c640a079059/ruff-0.15.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:89f463f7c8205a9f8dea9d658d59eff49db05f88f89cc3047fb1a02d9f344010", size = 10184752, upload-time = "2026-03-05T20:06:40.312Z" },
{ url = "https://files.pythonhosted.org/packages/85/cc/cc220fd9394eff5db8d94dec199eec56dd6c9f3651d8869d024867a91030/ruff-0.15.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2496488bdfd3732747558b6f95ae427ff066d1fcd054daf75f5a50674411e75", size = 10535456, upload-time = "2026-02-26T20:03:52.738Z" }, { url = "https://files.pythonhosted.org/packages/66/0e/ba49e2c3fa0395b3152bad634c7432f7edfc509c133b8f4529053ff024fb/ruff-0.15.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba786a8295c6574c1116704cf0b9e6563de3432ac888d8f83685654fe528fd65", size = 10534857, upload-time = "2026-03-05T20:06:19.581Z" },
{ url = "https://files.pythonhosted.org/packages/fa/0f/bced38fa5cf24373ec767713c8e4cadc90247f3863605fb030e597878661/ruff-0.15.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f1c4893841ff2d54cbda1b2860fa3260173df5ddd7b95d370186f8a5e66a4ac", size = 10287772, upload-time = "2026-02-26T20:04:08.138Z" }, { url = "https://files.pythonhosted.org/packages/59/71/39234440f27a226475a0659561adb0d784b4d247dfe7f43ffc12dd02e288/ruff-0.15.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd4b801e57955fe9f02b31d20375ab3a5c4415f2e5105b79fb94cf2642c91440", size = 10309120, upload-time = "2026-03-05T20:06:00.435Z" },
{ url = "https://files.pythonhosted.org/packages/2b/90/58a1802d84fed15f8f281925b21ab3cecd813bde52a8ca033a4de8ab0e7a/ruff-0.15.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:820b8766bd65503b6c30aaa6331e8ef3a6e564f7999c844e9a547c40179e440a", size = 11049051, upload-time = "2026-02-26T20:04:03.53Z" }, { url = "https://files.pythonhosted.org/packages/f5/87/4140aa86a93df032156982b726f4952aaec4a883bb98cb6ef73c347da253/ruff-0.15.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391f7c73388f3d8c11b794dbbc2959a5b5afe66642c142a6effa90b45f6f5204", size = 11047428, upload-time = "2026-03-05T20:05:51.867Z" },
{ url = "https://files.pythonhosted.org/packages/d2/ac/b7ad36703c35f3866584564dc15f12f91cb1a26a897dc2fd13d7cb3ae1af/ruff-0.15.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9fb74bab47139c1751f900f857fa503987253c3ef89129b24ed375e72873e85", size = 11890494, upload-time = "2026-02-26T20:04:10.497Z" }, { url = "https://files.pythonhosted.org/packages/5a/f7/4953e7e3287676f78fbe85e3a0ca414c5ca81237b7575bdadc00229ac240/ruff-0.15.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dc18f30302e379fe1e998548b0f5e9f4dff907f52f73ad6da419ea9c19d66c8", size = 11914251, upload-time = "2026-03-05T20:06:22.887Z" },
{ url = "https://files.pythonhosted.org/packages/93/3d/3eb2f47a39a8b0da99faf9c54d3eb24720add1e886a5309d4d1be73a6380/ruff-0.15.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f80c98765949c518142b3a50a5db89343aa90f2c2bf7799de9986498ae6176db", size = 11326221, upload-time = "2026-02-26T20:04:12.84Z" }, { url = "https://files.pythonhosted.org/packages/77/46/0f7c865c10cf896ccf5a939c3e84e1cfaeed608ff5249584799a74d33835/ruff-0.15.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc6e7f90087e2d27f98dc34ed1b3ab7c8f0d273cc5431415454e22c0bd2a681", size = 11333801, upload-time = "2026-03-05T20:05:57.168Z" },
{ url = "https://files.pythonhosted.org/packages/ff/90/bf134f4c1e5243e62690e09d63c55df948a74084c8ac3e48a88468314da6/ruff-0.15.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451a2e224151729b3b6c9ffb36aed9091b2996fe4bdbd11f47e27d8f2e8888ec", size = 11168459, upload-time = "2026-02-26T20:04:00.969Z" }, { url = "https://files.pythonhosted.org/packages/d3/01/a10fe54b653061585e655f5286c2662ebddb68831ed3eaebfb0eb08c0a16/ruff-0.15.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1cb7169f53c1ddb06e71a9aebd7e98fc0fea936b39afb36d8e86d36ecc2636a", size = 11206821, upload-time = "2026-03-05T20:06:03.441Z" },
{ url = "https://files.pythonhosted.org/packages/b5/e5/a64d27688789b06b5d55162aafc32059bb8c989c61a5139a36e1368285eb/ruff-0.15.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a8f157f2e583c513c4f5f896163a93198297371f34c04220daf40d133fdd4f7f", size = 11104366, upload-time = "2026-02-26T20:03:48.099Z" }, { url = "https://files.pythonhosted.org/packages/7a/0d/2132ceaf20c5e8699aa83da2706ecb5c5dcdf78b453f77edca7fb70f8a93/ruff-0.15.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9b037924500a31ee17389b5c8c4d88874cc6ea8e42f12e9c61a3d754ff72f1ca", size = 11133326, upload-time = "2026-03-05T20:06:25.655Z" },
{ url = "https://files.pythonhosted.org/packages/f1/f6/32d1dcb66a2559763fc3027bdd65836cad9eb09d90f2ed6a63d8e9252b02/ruff-0.15.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:917cc68503357021f541e69b35361c99387cdbbf99bd0ea4aa6f28ca99ff5338", size = 10510887, upload-time = "2026-02-26T20:03:45.771Z" }, { url = "https://files.pythonhosted.org/packages/72/cb/2e5259a7eb2a0f87c08c0fe5bf5825a1e4b90883a52685524596bfc93072/ruff-0.15.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:65bb414e5b4eadd95a8c1e4804f6772bbe8995889f203a01f77ddf2d790929dd", size = 10510820, upload-time = "2026-03-05T20:06:37.79Z" },
{ url = "https://files.pythonhosted.org/packages/ff/92/22d1ced50971c5b6433aed166fcef8c9343f567a94cf2b9d9089f6aa80fe/ruff-0.15.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e9737c8161da79fd7cfec19f1e35620375bd8b2a50c3e77fa3d2c16f574105cc", size = 10285939, upload-time = "2026-02-26T20:04:22.42Z" }, { url = "https://files.pythonhosted.org/packages/ff/20/b67ce78f9e6c59ffbdb5b4503d0090e749b5f2d31b599b554698a80d861c/ruff-0.15.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d20aa469ae3b57033519c559e9bc9cd9e782842e39be05b50e852c7c981fa01d", size = 10302395, upload-time = "2026-03-05T20:05:54.504Z" },
{ url = "https://files.pythonhosted.org/packages/e6/f4/7c20aec3143837641a02509a4668fb146a642fd1211846634edc17eb5563/ruff-0.15.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:291258c917539e18f6ba40482fe31d6f5ac023994ee11d7bdafd716f2aab8a68", size = 10765471, upload-time = "2026-02-26T20:03:58.924Z" }, { url = "https://files.pythonhosted.org/packages/5f/e5/719f1acccd31b720d477751558ed74e9c88134adcc377e5e886af89d3072/ruff-0.15.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:15388dd28c9161cdb8eda68993533acc870aa4e646a0a277aa166de9ad5a8752", size = 10754069, upload-time = "2026-03-05T20:06:06.422Z" },
{ url = "https://files.pythonhosted.org/packages/d0/09/6d2f7586f09a16120aebdff8f64d962d7c4348313c77ebb29c566cefc357/ruff-0.15.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3f83c45911da6f2cd5936c436cf86b9f09f09165f033a99dcf7477e34041cbc3", size = 11263382, upload-time = "2026-02-26T20:04:24.424Z" }, { url = "https://files.pythonhosted.org/packages/c3/9c/d1db14469e32d98f3ca27079dbd30b7b44dbb5317d06ab36718dee3baf03/ruff-0.15.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b30da330cbd03bed0c21420b6b953158f60c74c54c5f4c1dabbdf3a57bf355d2", size = 11304315, upload-time = "2026-03-05T20:06:10.867Z" },
{ url = "https://files.pythonhosted.org/packages/1b/fa/2ef715a1cd329ef47c1a050e10dee91a9054b7ce2fcfdd6a06d139afb7ec/ruff-0.15.4-py3-none-win32.whl", hash = "sha256:65594a2d557d4ee9f02834fcdf0a28daa8b3b9f6cb2cb93846025a36db47ef22", size = 10506664, upload-time = "2026-02-26T20:03:50.56Z" }, { url = "https://files.pythonhosted.org/packages/28/3a/950367aee7c69027f4f422059227b290ed780366b6aecee5de5039d50fa8/ruff-0.15.5-py3-none-win32.whl", hash = "sha256:732e5ee1f98ba5b3679029989a06ca39a950cced52143a0ea82a2102cb592b74", size = 10551676, upload-time = "2026-03-05T20:06:13.705Z" },
{ url = "https://files.pythonhosted.org/packages/d0/a8/c688ef7e29983976820d18710f955751d9f4d4eb69df658af3d006e2ba3e/ruff-0.15.4-py3-none-win_amd64.whl", hash = "sha256:04196ad44f0df220c2ece5b0e959c2f37c777375ec744397d21d15b50a75264f", size = 11651048, upload-time = "2026-02-26T20:04:17.191Z" }, { url = "https://files.pythonhosted.org/packages/b8/00/bf077a505b4e649bdd3c47ff8ec967735ce2544c8e4a43aba42ee9bf935d/ruff-0.15.5-py3-none-win_amd64.whl", hash = "sha256:821d41c5fa9e19117616c35eaa3f4b75046ec76c65e7ae20a333e9a8696bc7fe", size = 11678972, upload-time = "2026-03-05T20:06:45.379Z" },
{ url = "https://files.pythonhosted.org/packages/3e/0a/9e1be9035b37448ce2e68c978f0591da94389ade5a5abafa4cf99985d1b2/ruff-0.15.4-py3-none-win_arm64.whl", hash = "sha256:60d5177e8cfc70e51b9c5fad936c634872a74209f934c1e79107d11787ad5453", size = 10966776, upload-time = "2026-02-26T20:03:56.908Z" }, { url = "https://files.pythonhosted.org/packages/fe/4e/cd76eca6db6115604b7626668e891c9dd03330384082e33662fb0f113614/ruff-0.15.5-py3-none-win_arm64.whl", hash = "sha256:b498d1c60d2fe5c10c45ec3f698901065772730b411f164ae270bb6bfcc4740b", size = 10965572, upload-time = "2026-03-05T20:06:16.984Z" },
] ]
[[package]] [[package]]