diff --git a/.github/actions/setup-tools/action.yml b/.github/actions/setup-tools/action.yml
index f3fc6d6..c54b5e6 100644
--- a/.github/actions/setup-tools/action.yml
+++ b/.github/actions/setup-tools/action.yml
@@ -29,7 +29,7 @@ runs:
shell: bash
env:
RESEARCH_REPO_TOOLS_HOME: ${{ runner.temp }}/rrt
- run: uv run --locked --managed-python --only-group tooling python tooling/shared_tooling.py setup
+ run: uv run --locked --managed-python --only-group tooling research-repo-tools setup
- name: Export verified managed paths
shell: bash
diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml
index 11af4ce..8b30a7d 100644
--- a/.github/workflows/benchmarks.yml
+++ b/.github/workflows/benchmarks.yml
@@ -1,69 +1,22 @@
name: Benchmarks
-# Detect performance regressions.
-# - Push to main: run benchmarks, save Criterion baseline, upload as artifact.
-# - PRs: find latest main baseline artifact, download, compare, report.
-# Regressions are warning-only — this workflow never fails on regression.
-
-permissions:
- contents: read
- actions: read
-
+# Exploratory timings for a maintainer-selected ref.
on:
- push:
- branches:
- - main
- paths:
- - "src/**"
- - "benches/**"
- - "tests/exact_bench_config.rs"
- - ".config/nextest.toml"
- - "Cargo.toml"
- - "Cargo.lock"
- - "justfile"
- - "rust-toolchain.toml"
- - "pyproject.toml"
- - "uv.lock"
- - ".python-version"
- - ".github/actions/setup-tools/action.yml"
- - ".github/workflows/benchmarks.yml"
- pull_request:
- branches:
- - main
- paths:
- - "src/**"
- - "benches/**"
- - "tests/exact_bench_config.rs"
- - ".config/nextest.toml"
- - "Cargo.toml"
- - "Cargo.lock"
- - "justfile"
- - "rust-toolchain.toml"
- - "pyproject.toml"
- - "uv.lock"
- - ".python-version"
- - ".github/actions/setup-tools/action.yml"
- - ".github/workflows/benchmarks.yml"
workflow_dispatch:
-concurrency:
- group: >
- bench-${{ github.workflow }}-${{
- github.event_name == 'pull_request' &&
- github.event.pull_request.number ||
- github.ref
- }}
- cancel-in-progress: true
+permissions:
+ contents: read
env:
CARGO_TERM_COLOR: always
+ CRITERION_HOME: target/criterion
RUST_BACKTRACE: 1
jobs:
bench:
runs-on: ubuntu-latest
- # The full exact suite has 260+ cases: Criterion's default 3 s warm-up
- # plus 5 s measurement already needs 35+ minutes, before build/analysis.
+ # At Criterion's default 8 s of warm-up and measurement per case,
+ # 400+ cases need more than 53 minutes before build and analysis.
timeout-minutes: 90
steps:
@@ -77,157 +30,13 @@ jobs:
- name: Validate benchmark inputs
run: just test-bench-inputs
- # ── PR: find and download the latest main baseline ──────────────
- - name: Find latest main baseline
- if: github.event_name == 'pull_request'
- id: find-baseline
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- with:
- script: |
- // Find the most recent successful run of this workflow on main.
- const runs = await github.rest.actions.listWorkflowRuns({
- owner: context.repo.owner,
- repo: context.repo.repo,
- workflow_id: 'benchmarks.yml',
- branch: 'main',
- status: 'success',
- per_page: 5,
- });
-
- for (const run of runs.data.workflow_runs) {
- const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
- owner: context.repo.owner,
- repo: context.repo.repo,
- run_id: run.id,
- });
-
- const baseline = artifacts.data.artifacts.find(
- a => a.name === 'bench-baseline-main' && !a.expired
- );
-
- if (baseline) {
- console.log(`Found baseline from run ${run.id} (${run.created_at})`);
- core.setOutput('found', 'true');
- core.setOutput('run_id', run.id.toString());
- return;
- }
- }
-
- console.log('No baseline artifact found');
- core.setOutput('found', 'false');
+ - name: Measure the full exact suite
+ run: just bench-exact --noplot
- - name: Download baseline artifact
- if: >
- github.event_name == 'pull_request' &&
- steps.find-baseline.outputs.found == 'true'
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
- with:
- name: bench-baseline-main
- path: target/criterion
- run-id: ${{ steps.find-baseline.outputs.run_id }}
- github-token: ${{ secrets.GITHUB_TOKEN }}
-
- # ── PR: run benchmarks and compare ──────────────────────────────
- - name: Run benchmarks (compare against main)
- if: github.event_name == 'pull_request'
- id: bench-compare
- run: |
- set -euo pipefail
-
- if [ -d target/criterion/exact_d2/det/main ]; then
- echo "::notice::Baseline found — comparing against main"
- echo "comparison_available=true" >> "$GITHUB_OUTPUT"
- # --baseline-lenient rather than --baseline: benches added on the
- # PR branch that don't yet exist in the main baseline run without
- # a comparison instead of aborting the whole run.
- # Keep normal sampling; plots/HTML are unused by the summary.
- cargo bench --locked --features bench,exact --bench exact \
- -- --noplot --baseline-lenient main 2>&1 | tee bench-output.txt
- else
- echo "::notice::No baseline found — running without comparison"
- echo "comparison_available=false" >> "$GITHUB_OUTPUT"
- cargo bench --locked --features bench,exact --bench exact \
- -- --noplot 2>&1 | tee bench-output.txt
- fi
-
- if grep -q "Performance has regressed" bench-output.txt; then
- echo "regression=true" >> "$GITHUB_OUTPUT"
- else
- echo "regression=false" >> "$GITHUB_OUTPUT"
- fi
-
- # ── Main push: run benchmarks and save baseline ─────────────────
- - name: Run benchmarks (save baseline)
- if: >
- (github.event_name == 'push' || github.event_name == 'workflow_dispatch') &&
- github.ref == 'refs/heads/main'
- run: >
- cargo bench --locked --features bench,exact --bench exact
- -- --noplot --save-baseline main
-
- - name: Run benchmarks (manual ref)
- if: github.event_name == 'workflow_dispatch' && github.ref != 'refs/heads/main'
- run: cargo bench --locked --features bench,exact --bench exact -- --noplot
-
- - name: Upload baseline artifact
- if: >
- (github.event_name == 'push' || github.event_name == 'workflow_dispatch') &&
- github.ref == 'refs/heads/main'
+ - name: Upload exploratory timings
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
- name: bench-baseline-main
- path: target/criterion
- retention-days: 90
+ name: exploratory-exact-${{ github.run_id }}-${{ github.run_attempt }}
+ path: target/criterion/
+ retention-days: 14
if-no-files-found: error
-
- # ── PR: report results ──────────────────────────────────────────
- - name: Benchmark summary
- if: github.event_name == 'pull_request' && always()
- run: |
- set -euo pipefail
-
- comparison_available="${BENCH_COMPARISON_AVAILABLE:-}"
- regression="${BENCH_REGRESSION:-}"
-
- # Lenient comparisons silently skip missing baselines. Require a
- # change report for every analyzed case before claiming coverage.
- if [ -f bench-output.txt ]; then
- analyzed="$(grep -c '^Benchmarking .*: Analyzing$' bench-output.txt || true)"
- compared="$(grep -c '^[[:space:]]*change:' bench-output.txt || true)"
- if [ "$analyzed" = "0" ] || [ "$analyzed" != "$compared" ]; then
- comparison_available=false
- fi
- else
- comparison_available=false
- fi
-
- if [ "$comparison_available" != "true" ] || [ -z "$regression" ]; then
- {
- echo "### ❓ Benchmark Comparison Unavailable"
- echo ""
- echo "No usable comparison against the main baseline was produced."
- echo "Benchmarks may be incomplete or lack a baseline; no regression claim can be made."
- } >> "$GITHUB_STEP_SUMMARY"
- echo "::warning::Benchmark comparison unavailable"
- elif [ "$regression" = "true" ]; then
- {
- echo "### ⚠️ Performance Regression Detected"
- echo ""
- echo "Exact-arithmetic benchmarks show regression vs main baseline."
- echo "This is a **warning only** — the workflow will not fail."
- echo ""
- echo '```'
- grep -B1 "Performance has regressed" bench-output.txt || true
- echo '```'
- } >> "$GITHUB_STEP_SUMMARY"
- echo "::warning::Performance regression detected in exact-arithmetic benchmarks"
- else
- {
- echo "### ✅ No Performance Regression"
- echo ""
- echo "Exact-arithmetic benchmarks are within expected range vs main."
- } >> "$GITHUB_STEP_SUMMARY"
- fi
- env:
- BENCH_COMPARISON_AVAILABLE: ${{ steps.bench-compare.outputs.comparison_available }}
- BENCH_REGRESSION: ${{ steps.bench-compare.outputs.regression }}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 739d305..ed6802d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -91,7 +91,7 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
run: |
- uvx --python 3.15 --from research-repo-tools==0.2.4 python -I - <<'PY'
+ uvx --python 3.15 --from research-repo-tools==0.2.6 python -I - <<'PY'
import os
import sys
from pathlib import Path
diff --git a/.github/workflows/release-benchmarks.yml b/.github/workflows/release-benchmarks.yml
index 0a5f36a..9b09a79 100644
--- a/.github/workflows/release-benchmarks.yml
+++ b/.github/workflows/release-benchmarks.yml
@@ -37,7 +37,7 @@ jobs:
- name: Install trusted package runner
uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
with:
- version: "0.13.6"
+ version: "0.13.7"
enable-cache: false
cache-python: false
@@ -54,7 +54,7 @@ jobs:
echo "::error::Dispatch with --ref $RELEASE_TAG and the matching tag input"
exit 1
fi
- target="$(uvx --python 3.15 --from research-repo-tools==0.2.4 research-repo-tools \
+ target="$(uvx --python 3.15 --from research-repo-tools==0.2.6 research-repo-tools \
--root "$RUNNER_TEMP" performance release-draft "$GH_REPO" "$RELEASE_TAG" \
--commit "$GITHUB_SHA" --expected-title "$RELEASE_TAG" | jq -c .)"
echo "target=$target" >> "$GITHUB_OUTPUT"
@@ -109,7 +109,7 @@ jobs:
if [[ "$BENCH_SUITE" == public-exact ]]; then
features=bench,exact
fi
- uvx --python 3.15 --from research-repo-tools==0.2.4 research-repo-tools performance pack-suite \
+ uvx --python 3.15 --from research-repo-tools==0.2.6 research-repo-tools performance pack-suite \
tooling/criterion-saved.toml "$BENCH_SUITE" --saved-sample "$RELEASE_TAG" \
--output "$output/criterion.tar.gz" --revision "$GITHUB_SHA" \
--session "$GITHUB_REPOSITORY:$GITHUB_RUN_ID:$GITHUB_RUN_ATTEMPT" \
@@ -179,7 +179,7 @@ jobs:
- name: Install trusted package runner
uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
with:
- version: "0.13.6"
+ version: "0.13.7"
enable-cache: false
cache-python: false
@@ -202,7 +202,7 @@ jobs:
asset="la-stack-${RELEASE_TAG}-criterion-baseline.tar.gz"
cp target/release-suites/public-default/inventory.json target/release-benchmark-inventory.json
- uvx --python 3.15 --from research-repo-tools==0.2.4 research-repo-tools \
+ uvx --python 3.15 --from research-repo-tools==0.2.6 research-repo-tools \
--root "$GITHUB_WORKSPACE" performance assemble-suites tooling/criterion-saved.toml \
target/release-suites/public-default/criterion.tar.gz \
target/release-suites/public-exact/criterion.tar.gz \
@@ -230,7 +230,7 @@ jobs:
- name: Install trusted package runner
uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
with:
- version: "0.13.6"
+ version: "0.13.7"
enable-cache: false
cache-python: false
@@ -251,7 +251,7 @@ jobs:
[[ "$RELEASE_ASSET" == "la-stack-${RELEASE_TAG}-criterion-baseline.tar.gz" ]] || exit 1
printf '%s\n' "$RELEASE_TARGET" > "$RUNNER_TEMP/release-target.json"
- uvx --python 3.15 --from research-repo-tools==0.2.4 research-repo-tools \
+ uvx --python 3.15 --from research-repo-tools==0.2.6 research-repo-tools \
--root "$RUNNER_TEMP" performance release-upload "$GH_REPO" "$RELEASE_TAG" \
"$GITHUB_WORKSPACE/$RELEASE_ASSET" --target "$RUNNER_TEMP/release-target.json"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6213637..344d89d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -10,7 +10,6 @@
[![Audit dependencies][audit-badge]][audit-workflow]
[![zizmor][zizmor-badge]][zizmor-workflow]
[![Repository Rule SARIF][semgrep-badge]][semgrep-workflow]
-[![Benchmarks][benchmarks-badge]][benchmarks-workflow]
[![Release Benchmarks][release-benchmarks-badge]][release-benchmarks-workflow]
[![Dependabot approval and auto-merge][dependabot-badge]][dependabot-workflow]
@@ -38,7 +37,7 @@ exact version declared in `pyproject.toml`, and `jq`. Authenticate the GitHub CL
the declared Python, Rust, Cargo tools, and Just through the locked PyPI package:
```bash
-uv run --locked --managed-python --only-group tooling python tooling/shared_tooling.py setup
+uv run --locked --managed-python --only-group tooling research-repo-tools setup
```
Set up the remaining development tools and validate the checkout:
@@ -54,7 +53,7 @@ change files. Run bare `just` for the complete generated command list, including
arguments and descriptions. The [command policy](docs/dev/docs.md#command-discovery-and-placement)
owns recipe naming and documentation placement.
-Setup, tool checks, updates, changelog, and opt-in CodeRabbit review use the published `research-repo-tools==0.2.4` package,
+Setup, tool checks, updates, changelog, and opt-in CodeRabbit review use the published `research-repo-tools==0.2.6` package,
locked in the `tooling` dependency group and included by `dev`. Its `python-tools`
extra owns the pytest, Ruff and Ty versions.
`toolchain.inherit-python-tools` checks those versions against the lock and
@@ -86,8 +85,8 @@ Then `just update-dependencies` upgrades Cargo requirements and refreshes Cargo'
lock, resolves exact direct Python `dev` pins, refreshes the complete `uv.lock`,
and explicitly synchronizes `dev` with managed Rust available for native builds.
Included groups retain their constraints, including the exact shared-package pin.
-Semgrep scans and fixture checks share the root Python 3.15 environment and lockfile. Preserve its exact
-pin, native-wheel requirement and compatibility overrides for MCP, PyJWT,
+Semgrep scans and fixture checks share the root Python 3.15 environment and lockfile. Preserve its platform
+constraints, native-wheel requirement and compatibility overrides for MCP, PyJWT,
OpenTelemetry and Python 3.15 Windows wheels. Scanner commands retain the Linux
stack-limit workaround required by Semgrep on Python 3.15. The hosted dependency
audit checks the root Python lockfile with the shared OSV adapter; its existing
@@ -253,6 +252,11 @@ human contributor performs and reviews commits, pushes, tags, and releases.
### Validation Workflow
+On PRs and pushes to `main`, native CI compiles the benchmark targets, runs
+the input tests, and exercises the exact harness in Criterion test mode.
+Timing runs are opt-in. See [Benchmarking](docs/BENCHMARKING.md#ci-validation-and-manual-timings)
+for the coverage and dispatch commands.
+
Start with the smallest test selection that exercises the changed behavior. Use
`cargo nextest run ` for a focused runnable test, `cargo test --doc
` for a focused doctest, or `cargo nextest run --test ` for one
@@ -267,7 +271,7 @@ For final validation of a non-core change, compose each affected surface once:
- Rust unit tests: `just test-unit`
- Doctests: `just test-doc` and, when relevant, `just test-doc-exact`
- Integration tests: `just test-integration`
-- Benchmark inputs or harnesses: `just test-bench-inputs` or `just bench-compile`
+- Benchmark inputs or harnesses: `just test-bench-inputs`, `just bench-compile`, or `just test-bench-exact`
- Examples: `just examples`
For individual edited file formats, use these focused checks and formatters:
@@ -441,8 +445,6 @@ Report suspected vulnerabilities privately through the process in
[audit-badge]: https://github.com/acgetchell/la-stack/actions/workflows/audit.yml/badge.svg?branch=main
[audit-workflow]: https://github.com/acgetchell/la-stack/actions/workflows/audit.yml
-[benchmarks-badge]: https://github.com/acgetchell/la-stack/actions/workflows/benchmarks.yml/badge.svg?branch=main
-[benchmarks-workflow]: https://github.com/acgetchell/la-stack/actions/workflows/benchmarks.yml
[ci-badge]: https://github.com/acgetchell/la-stack/actions/workflows/ci.yml/badge.svg?branch=main
[ci-workflow]: https://github.com/acgetchell/la-stack/actions/workflows/ci.yml
[clippy-badge]: https://github.com/acgetchell/la-stack/actions/workflows/rust-clippy.yml/badge.svg?branch=main
diff --git a/Cargo.lock b/Cargo.lock
index 47a682f..c76625e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -148,9 +148,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
-version = "1.10.0"
+version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86e055fa6e3e40ad6187ba25b6855eeb635d5271f8e0f0e1e9a5826b8d75bea0"
+checksum = "375989decec1a0fa424442bed520fa12ede43a48475ce8c7417d2c69b32b5f6b"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -711,9 +711,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "js-sys"
-version = "0.3.106"
+version = "0.3.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5"
+checksum = "8dc09d2154098bbf4ef54eab6b117a3c24cbbffe94046b16ee6d6d4510584d3a"
dependencies = [
"cfg-if",
"futures-util",
@@ -1420,9 +1420,9 @@ dependencies = [
[[package]]
name = "toml_edit"
-version = "0.25.17+spec-1.1.0"
+version = "0.25.18+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c"
+checksum = "1ec6e78bd70db9420d8d212fa9dc97f116448a1ceff5d00eabb991d407e21396"
dependencies = [
"indexmap",
"toml_datetime",
@@ -1484,12 +1484,11 @@ dependencies = [
[[package]]
name = "wasm-bindgen"
-version = "0.2.129"
+version = "0.2.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409"
+checksum = "a0181c14ddd6c7b8b0b0132a5f1ea2f32be0d893cd3ea97b6e3b28886b8a3d1c"
dependencies = [
"cfg-if",
- "once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
@@ -1497,9 +1496,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.129"
+version = "0.2.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535"
+checksum = "6e931a02a39abdbf66966fc35902b5ed58344750a70885bed4ebb3d8eaf28ed9"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -1507,9 +1506,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.129"
+version = "0.2.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7"
+checksum = "2ca4b54280e455bd831e33a6b8e734baf432fe63fbc35c42ac14ce67a043a53a"
dependencies = [
"bumpalo",
"proc-macro2",
@@ -1520,18 +1519,18 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.129"
+version = "0.2.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6"
+checksum = "3b1f9c3798725016c6ee5f2f57869ec0831e8bbc0ab0310a86e787e97f41389e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "web-sys"
-version = "0.3.106"
+version = "0.3.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4"
+checksum = "d7d179596ff5ec0f6de4db64d54011bde6289cc49603d3a6e4ce3f329207ab88"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -1595,9 +1594,9 @@ dependencies = [
[[package]]
name = "winnow"
-version = "1.0.4"
+version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
+checksum = "8c3b93199df0ab874cdf0c6a94f0a0dc3423fe41f1cafb62b8bd7b1deb277aef"
dependencies = [
"memchr",
]
diff --git a/docs/BENCHMARKING.md b/docs/BENCHMARKING.md
index 0d89ae4..95fd499 100644
--- a/docs/BENCHMARKING.md
+++ b/docs/BENCHMARKING.md
@@ -7,6 +7,7 @@ the commands measure and where their outputs go.
## Contents
- [Start Here](#start-here)
+- [CI validation and manual timings](#ci-validation-and-manual-timings)
- [Benchmark Suites](#benchmark-suites)
- [Release acquisition policy](#release-acquisition-policy)
- [Common Workflows](#common-workflows)
@@ -61,6 +62,42 @@ commit assertions. The commands are shown below. Keep those assertions from the
reviewed source checkouts or workflow inputs, rather than reading them from the
evidence being validated.
+## CI validation and manual timings
+
+The native `build` jobs run `just ci` on Linux, macOS, and Windows for PRs
+and pushes to `main`. They cover the correctness checks previously exercised
+by the automatic benchmark job.
+
+| Former automatic action | Validator or current owner |
+| --- | --- |
+| Install and verify the locked tools | The same `setup-tools` action runs in every native CI job. |
+| Run `just test-bench-inputs` on Linux | `test-rust-ci` runs the complete all-feature workspace unit and integration suite, including `vs_linalg_inputs`, `exact_bench_config`, `column_basis`, `rational_dot`, `interval_solve`, and `polynomial_bench_inputs`. |
+| Build the exact harness for timing | `bench-compile` builds the workspace benchmark targets and exact-feature targets with warnings denied. |
+| Execute the full exact harness and its fixture checks | `test-bench-exact` runs every exact case in Criterion's `--test` mode without timing samples. |
+| Save or download a main baseline, sample a PR, and report cross-runner regressions | The manual workflow collects one exploratory run. Tagged release acquisition and its input gate remain in [Releasing](RELEASING.md#6-run-benchmarks-and-review-the-draft). |
+
+The three native build status checks still cover benchmark validation.
+The optional [Benchmarks workflow](https://github.com/acgetchell/la-stack/actions/workflows/benchmarks.yml)
+runs only when a maintainer selects a reviewed branch or tag:
+
+```bash
+gh workflow run benchmarks.yml --ref "$REVIEWED_REF"
+```
+
+It runs `just test-bench-inputs` followed by `just bench-exact --noplot` on one
+hosted Linux runner. This samples the entire exact target, including diagnostic
+cases. At eight seconds per case, the default warm-up and measurement for 418
+cases total about 56 minutes, before build and analysis. The job has a 90-minute
+limit. Successful runs keep
+`target/criterion/` in the `exploratory-exact--` Actions artifact
+for 14 days. The Actions job log identifies the checked-out revision.
+
+Use these single-revision timings to investigate cases. Hosted runner
+variability limits comparisons across runs. For a performance decision, use
+the paired workflows below to measure both revisions under equivalent inputs,
+harness, and toolchain in a compatible measurement environment. See
+[Criterion's CI guidance](https://bheisler.github.io/criterion.rs/book/faq.html#how-should-i-run-criterionrs-benchmarks-in-a-ci-pipeline).
+
## Benchmark Suites
`la-stack` has twelve Criterion benchmark targets. Routine release acquisition
@@ -902,7 +939,7 @@ baseline's four raw JSON files must match its fresh `new` measurement, including
the native sanitized storage paths. Missing public cases, failed writes, stale
baselines and malformed measurements stop publication. For one suite, use
`just bench-release-check-suite ` with the runner's output directory.
-The shared v0.2.4 `pack-suite` command already performs that validation before
+The shared `pack-suite` command performs that validation before
packaging, so the workflow needs no additional verification pass. It retains
original host metadata captured before timing and digests of every raw file.
Assembly uses `assemble-suites` with the checked-in policy and separately
diff --git a/docs/RELEASING.md b/docs/RELEASING.md
index bf08e63..1817c01 100644
--- a/docs/RELEASING.md
+++ b/docs/RELEASING.md
@@ -34,7 +34,7 @@ including when a rerun happens after UTC midnight.
## Reusable CI evidence
-The publishing process uses the installed `research-repo-tools==0.2.4`
+The publishing process uses the installed `research-repo-tools==0.2.6`
release evidence commands. `tooling/release-evidence.toml` declares the repository,
package, workflow, native jobs/steps and signer policy. The guarantee is
`signed-source`; it does not promise promotion of an identical Cargo archive.
@@ -458,7 +458,7 @@ release ID and tag commit before attaching
the archive. Do not edit the draft, move the tag,
or publish manually while the workflow is running.
-The privileged jobs install the exact published `research-repo-tools==0.2.4`
+The privileged jobs install the exact published `research-repo-tools==0.2.6`
package on Python 3.15 without repository checkout. Preflight emits versioned
target JSON with the captured ID, commit and title assertion. Its trusted job
output passes directly to the publisher, separately from the producer's inert
@@ -470,7 +470,7 @@ shared `pack-suite` command to verify and archive their full samples. Assembly
uses `assemble-suites` with the checked-in policy, separately transported native
inventory, tagged commit and repository/run/attempt identity. It emits the final
asset only after validating both suites, raw-file digests and original provenance.
-These suite commands use the published `research-repo-tools==0.2.4` through
+These suite commands use the published `research-repo-tools==0.2.6` through
`uvx --python 3.15`, matching the standard development dependency pin.
New v0.5.2 archives use the shared layout described in
[Benchmarking](BENCHMARKING.md#hosted-release-runtime-budget); historical assets
diff --git a/docs/code_organization.md b/docs/code_organization.md
index 7b8ae55..efc9f61 100644
--- a/docs/code_organization.md
+++ b/docs/code_organization.md
@@ -182,11 +182,8 @@ The [justfile](../justfile) owns executable development workflows.
Shared complete-inventory Python and file runners own discovery and bounded
execution; repository lint policy and archive exclusions stay in the recipes
and native tool configuration. The [capability inventory](../tooling/README.md#shared-capability-adoption)
-records the v0.2.4 development and release-command dependency.
-`tooling/shared_tooling.py` temporarily selects the scanner from the standard
-Python 3.15 environment before delegating to the installed fixture checker.
-[#317](https://github.com/acgetchell/la-stack/issues/317) owns its removal after
-the shared Python baseline also governs Semgrep in v0.2.5.
+records the v0.2.6 development and release-command dependency. The installed
+CLI verifies the Semgrep scanner in the locked Python 3.15 environment.
`osv-scanner.toml` owns temporary advisory-specific dependency exceptions, and
`.gitleaks.toml` owns narrow secret-scan false-positive exceptions. Their rationale
@@ -238,7 +235,7 @@ owns settings and deployment verification.
`tooling/criterion-inventory.toml` declares hosted la-stack-only list commands and
independent public API coverage. `tooling/criterion-saved.toml` declares
complete saved-sample requirements. Shared discovery and saved-sample APIs own
-inventory parsing and exact raw-file verification. Published v0.2.4 also owns
+inventory parsing and exact raw-file verification. The shared package also owns
suite-scoped verification, provenance, packaging and complete archive assembly.
The release workflow retains the feature matrix, native commands, job budgets
and artifact handoff; its regression and archive tests live in
@@ -278,13 +275,14 @@ without regeneration or invented acquisition records. New complete runs
support offline rendering through the declared eligibility policy.
The root Python 3.15 environment owns Semgrep alongside the other development
-tools. Its exact pin, native-wheel requirement and compatibility overrides live
+tools. Its platform constraints, native-wheel requirement and compatibility overrides live
in `pyproject.toml` and `uv.lock`. Local scans, shared fixture validation and
hosted SARIF use that lock and retain the Linux stack-limit workaround for
[Semgrep #11979](https://github.com/semgrep/semgrep/issues/11979).
The root `.semgrepignore` keeps ordinary Rust and Python tests in those scans
while excluding deliberate `tests/semgrep/` fixtures, which `just semgrep-test`
-validates separately. Git-ignored dependencies, environments, caches, and build
+validates separately. `tests/tooling/test_semgrep_integration.py` exercises the
+locked fixture command and its failure statuses. Git-ignored dependencies, environments, caches, and build
outputs remain outside production scans.
`.github/actions/prepare-release-benchmarks/action.yml` groups tool installation,
diff --git a/docs/dev/performance-tooling-adoption.md b/docs/dev/performance-tooling-adoption.md
index eaa03a8..cd4fb7d 100644
--- a/docs/dev/performance-tooling-adoption.md
+++ b/docs/dev/performance-tooling-adoption.md
@@ -8,14 +8,14 @@ belong to research-repo-tools.
## Publication boundary
-The dependency-only environment pins the published `research-repo-tools==0.2.4`
-package. Release target capture and publication use the same exact version.
-The suite verification, packaging and assembly commands adopted for
+For la-stack v0.5.2, the development and release workflows pinned the
+published `research-repo-tools==0.2.4` package. The suite verification,
+packaging and assembly commands adopted for
[#316](https://github.com/acgetchell/la-stack/issues/316), targeting v0.5.2,
-use that release as well. Its verified tool updater is adopted without local
-updater logic. A temporary scanner-selection override preserves the standard
-Python 3.15 Semgrep environment until the shared fix ships in v0.2.5;
-[#317](https://github.com/acgetchell/la-stack/issues/317) tracks its removal.
+used that release as well. That adoption used the verified shared tool
+updater and a temporary scanner-selection override to keep Semgrep on
+Python 3.15. The [current tooling guide](../../tooling/README.md) records the
+active pin and scanner setup.
Upstream [#123](https://github.com/acgetchell/research-repo-tools/pull/123)
adds trusted main-validation artifact reuse for release promotion; v0.2.3
was published from commit `4085125526e21b2855eb7c61a52ee312782ae31a`.
@@ -34,10 +34,8 @@ and the exact registry lock entries:
| Wheel | `00aaab2b7efd680d79324c6debb64db4d8d1ff076a460ff46e24e0ef1c24bd7d` |
| Source distribution | `30b6d4d2f8d0cb98d5311656928dd65800088b651dd6c0c29f9a02526e9e902a` |
-Runtime imports and acceptance checks use this installed package. No sibling
-checkout or release-candidate installation supplies final evidence. The temporary
-Semgrep adapter overrides only the installed scanner-selection function; all
-benchmark execution, validation and publication use the public shared APIs.
+Runtime imports and acceptance checks used the installed release. Benchmark
+execution, validation and publication used the public shared APIs.
The maintainer approved freezing earlier evidence instead of extending the
shared policy gate to accept missing acquisition declarations. This deliberately
diff --git a/justfile b/justfile
index 6451a2b..8449c28 100644
--- a/justfile
+++ b/justfile
@@ -1,6 +1,6 @@
# shellcheck disable=SC2148
# Justfile for la-stack development workflow
-# Install tools: uv run --locked --managed-python --only-group tooling python tooling/shared_tooling.py setup
+# Install tools: uv run --locked --managed-python --only-group tooling research-repo-tools setup
# Usage: just or just --list
# Use bash with strict error handling for all recipes
@@ -90,8 +90,9 @@ bench-compile:
CARGO_BUILD_WARNINGS=deny {{ _run }} cargo bench --locked --no-run --features bench,exact --bench public_api
# Run the exact-arithmetic benchmark suite.
-bench-exact:
- {{ _run }} cargo bench --locked --features bench,exact --bench exact
+[positional-arguments]
+bench-exact *args:
+ {{ _run }} cargo bench --locked --features bench,exact --bench exact -- "$@"
# Run the outward-rounded interval determinant benchmark suite.
bench-interval:
@@ -230,7 +231,7 @@ check-fast:
{{ _run }} cargo check
# Run the complete local CI validation, tests, examples, and benchmark compilation.
-ci: action-lint workflow-allowlist-check zizmor markdown-check spell-check docs-version-check changelog-check toml-parse-check toml-fmt-check toml-lint yaml-fmt-check yaml-lint citation-check validate-json justfile-fmt-check python-check test-python cargo-lock-check fmt-check clippy-all-targets doc-check semgrep semgrep-test unused-deps shell-check test-rust-ci test-doc test-doc-exact bench-compile examples
+ci: action-lint workflow-allowlist-check zizmor markdown-check spell-check docs-version-check changelog-check toml-parse-check toml-fmt-check toml-lint yaml-fmt-check yaml-lint citation-check validate-json justfile-fmt-check python-check test-python cargo-lock-check fmt-check clippy-all-targets doc-check semgrep semgrep-test unused-deps shell-check test-rust-ci test-doc test-doc-exact bench-compile test-bench-exact examples
@echo "🎯 CI checks complete!"
# Validate CITATION.cff against the Citation File Format schema.
@@ -417,7 +418,7 @@ python-typecheck: python-sync
# Preflight, upload or verify a captured draft target with an isolated trusted runtime.
[positional-arguments]
-[script("uvx", "--python", "3.15", "--from", "research-repo-tools==0.2.4", "python", "-I")]
+[script("uvx", "--python", "3.15", "--from", "research-repo-tools==0.2.6", "python", "-I")]
release-assets operation tag *args:
import sys
from pathlib import Path
@@ -429,7 +430,7 @@ release-assets operation tag *args:
# Verify signed native/Cargo evidence; accept --expected PATH and --github-output PATH.
[positional-arguments]
-[script("uvx", "--python", "3.15", "--from", "research-repo-tools==0.2.4", "python", "-I")]
+[script("uvx", "--python", "3.15", "--from", "research-repo-tools==0.2.6", "python", "-I")]
release-evidence *args:
import sys
import tempfile
@@ -490,16 +491,16 @@ semgrep: _ensure-uv
# Fixture tests for repository-owned Semgrep rules.
semgrep-test: python-sync
- {{ semgrep_stack_limit }} uv run --locked --group dev python tooling/shared_tooling.py semgrep check-fixtures
+ {{ semgrep_stack_limit }} uv run --locked --group dev research-repo-tools semgrep check-fixtures
# Install declared managed tools and explicitly synchronize dev.
setup: _ensure-gh _ensure-jq
- uv run --locked --managed-python --only-group tooling python tooling/shared_tooling.py setup
+ uv run --locked --managed-python --only-group tooling research-repo-tools setup
{{ _run }} cargo build
# Tool-only setup; compilation belongs to setup.
setup-tools: _ensure-gh _ensure-jq
- uv run --locked --managed-python --only-group tooling python tooling/shared_tooling.py setup
+ uv run --locked --managed-python --only-group tooling research-repo-tools setup
# Check shell script lint and formatting.
shell-check: _ensure-shellcheck _ensure-shfmt
@@ -529,6 +530,9 @@ test: test-unit test-doc
test-all: test-rust test-python
@echo "✅ All tests passed"
+# Exercise the full exact benchmark harness in Criterion test mode.
+test-bench-exact: (bench-exact "--test")
+
# Smoke-test deterministic inputs and configuration shared with benchmark suites.
test-bench-inputs: tools-check
{{ _run }} cargo nextest run --workspace --profile ci --features bench,exact --test vs_linalg_inputs --test exact_bench_config --test column_basis --test rational_dot --test interval_solve --test polynomial_bench_inputs --verbose
@@ -561,7 +565,7 @@ test-python: tools-check python-sync
test-rust: test-rust-ci test-doc test-doc-exact
@echo "✅ Rust tests passed"
-# CI Rust bucket: all runnable unit/integration targets in one nextest pass.
+# Run all unit and integration targets, including benchmark input tests, in one CI pass.
test-rust-ci: tools-check
{{ _run }} cargo nextest run --workspace --release --profile ci --all-features --lib --tests --verbose
diff --git a/pyproject.toml b/pyproject.toml
index 4943a7b..381a5c6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -86,11 +86,17 @@ line-length = 160
[tool.uv]
package = false
-required-version = "==0.13.6"
+required-version = "==0.13.7"
+# Semgrep stopped shipping Intel macOS wheels after 1.182.1.
+constraint-dependencies = [
+ "semgrep==1.182.1; sys_platform == 'darwin' and platform_machine == 'x86_64'",
+ "semgrep>=1.184.0; sys_platform != 'darwin' or platform_machine != 'x86_64'",
+]
# Semgrep requires its wheel-bundled native engine.
no-build-package = [ "semgrep" ]
-# Preserve patched MCP, PyJWT and matching OpenTelemetry, plus Python 3.15 Windows wheels.
+# Adopt shared Semgrep compatibility overrides and retain the patched PyJWT constraint.
override-dependencies = [
+ "jsonschema==4.26.0",
"pyjwt[crypto]>=2.15.1,<3",
"mcp==1.30.0",
"opentelemetry-api==1.45.1",
@@ -99,14 +105,14 @@ override-dependencies = [
"opentelemetry-instrumentation-requests==0.66b1",
"opentelemetry-instrumentation-threading==0.66b1",
"pywin32==312; sys_platform == 'win32'",
+ "tomli==2.5.0",
]
[dependency-groups]
-tooling = [ "research-repo-tools[python-tools]==0.2.4" ]
+tooling = [ "research-repo-tools[python-tools,semgrep]==0.2.6" ]
dev = [
{ include-group = "tooling" },
"actionlint-py==1.7.12.25",
- "semgrep==1.182.1",
"shellcheck-py==0.11.0.1",
"shfmt-py==4.2.0",
"yamllint==1.38.0",
@@ -126,15 +132,15 @@ osv-scanner = "2.7.0"
cargo-edit = "0.13.14"
cargo-llvm-cov = "0.9.1"
cargo-machete = "0.9.2"
-cargo-nextest = "0.9.150"
+cargo-nextest = "0.9.151"
clippy-sarif = "0.8.0"
dprint = "0.64.3"
git-cliff = "2.14.2"
rumdl = "0.3.2"
sarif-fmt = "0.8.0"
taplo-cli = "0.10.0"
-typos-cli = "1.51.1"
-zizmor = "1.31.1"
+typos-cli = "1.52.0"
+zizmor = "1.32.0"
[tool.research-repo-tools.changelog]
owner = "acgetchell"
diff --git a/tests/tooling/test_justfile_discoverability.py b/tests/tooling/test_justfile_discoverability.py
index cb8afd6..f06a8f2 100644
--- a/tests/tooling/test_justfile_discoverability.py
+++ b/tests/tooling/test_justfile_discoverability.py
@@ -84,7 +84,7 @@ def test_ci_enforces_full_python_fixture_lint_policy() -> None:
assert "research-repo-tools python check" in python_body
assert "--select" not in python_body
inventory = set(select_files(REPO_ROOT, include=("*.py", "*.pyi")))
- assert {"tooling/shared_tooling.py", "tests/tooling/test_release_evidence.py", "tests/semgrep/scripts/python_portability.py"} <= inventory
+ assert {"tests/tooling/test_semgrep_integration.py", "tests/tooling/test_release_evidence.py", "tests/semgrep/scripts/python_portability.py"} <= inventory
assert not any(".venv" in Path(name).parts for name in inventory)
diff --git a/tests/tooling/test_maintenance_integration.py b/tests/tooling/test_maintenance_integration.py
index 571736e..75f86ca 100644
--- a/tests/tooling/test_maintenance_integration.py
+++ b/tests/tooling/test_maintenance_integration.py
@@ -6,6 +6,7 @@
import shlex
import shutil
import sys
+import sysconfig
import tomllib
from collections import Counter
from pathlib import Path
@@ -13,6 +14,7 @@
import pytest
from packaging.requirements import Requirement
from packaging.version import Version
+from research_repo_tools import semgrep_tool
from research_repo_tools.cli import main
from research_repo_tools.just_inspect import dry_run
from research_repo_tools.process import run_command
@@ -154,27 +156,25 @@ def test_markdown_recipe_preserves_active_and_historical_selection(consumer: Pat
assert "-- rumdl check" in checked
-def test_semgrep_adapter_uses_real_consumer_rules_and_fixtures() -> None:
+def test_semgrep_fixture_recipe_uses_the_published_cli() -> None:
"""The shared checker validates the supplied namespace, including hidden workflows."""
config = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8"))["tool"]["research-repo-tools"]["semgrep"]
assert config["config"] == "semgrep.yaml"
assert config["fixtures"] == "tests/semgrep"
assert config["namespace"] == "la-stack."
result = dry_run(REPO_ROOT, "semgrep-test")
- assert "uv run --locked --group dev python tooling/shared_tooling.py semgrep check-fixtures" in result.stderr
+ assert "uv run --locked --group dev research-repo-tools semgrep check-fixtures" in result.stderr
def test_semgrep_runs_in_the_root_python_315_environment_with_compatible_dependencies() -> None:
"""The unified scanner starts with its native engine and patched dependencies."""
root = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8"))
- requirement = next(Requirement(item) for item in root["dependency-groups"]["dev"] if isinstance(item, str) and item.startswith("semgrep"))
assert root["project"]["requires-python"] == ">=3.15"
assert root["tool"]["uv"]["no-build-package"] == ["semgrep"]
assert not (REPO_ROOT / "tooling/semgrep/pyproject.toml").exists()
overrides = {requirement.name: requirement.specifier for requirement in map(Requirement, root["tool"]["uv"]["override-dependencies"])}
lock = tomllib.loads((REPO_ROOT / "uv.lock").read_text(encoding="utf-8"))
versions = {package["name"]: Version(package["version"]) for package in lock["package"]}
- assert versions["semgrep"] in requirement.specifier
assert versions["pywin32"] == Version("312")
for name, vulnerable, patched in (("mcp", "1.29.0", "1.30.0"), ("opentelemetry-api", "1.45.0", "1.45.1"), ("pyjwt", "2.15.0", "2.15.1")):
assert Version(vulnerable) not in overrides[name]
@@ -188,12 +188,20 @@ def test_semgrep_runs_in_the_root_python_315_environment_with_compatible_depende
== versions["opentelemetry-instrumentation-requests"]
== versions["opentelemetry-instrumentation-threading"]
)
- scanner = next(package for package in lock["package"] if package["name"] == "semgrep")
- assert any("none-win_amd64" in wheel["url"] for wheel in scanner["wheels"])
+ scanners = [package for package in lock["package"] if package["name"] == "semgrep"]
+ assert any("none-win_amd64" in wheel["url"] for scanner in scanners for wheel in scanner["wheels"])
+ assert any("none-macosx_10_14_x86_64" in wheel["url"] for scanner in scanners for wheel in scanner["wheels"])
windows = next(package for package in lock["package"] if package["name"] == "pywin32")
assert any("cp315-cp315-win_amd64" in wheel["url"] for wheel in windows["wheels"])
+ assert sys.version_info[:2] == (3, 15)
+ scripts = Path(sysconfig.get_path("scripts"))
+ production_scanner = shutil.which("semgrep", path=str(scripts))
+ assert production_scanner is not None
+ verified = semgrep_tool.verify(REPO_ROOT)
+ assert verified.path.samefile(production_scanner)
result = run_command("uv", ["run", "--locked", "--group", "dev", "semgrep", "--disable-version-check", "--version"], cwd=REPO_ROOT)
- assert result.stdout.strip() == str(versions["semgrep"])
+ assert result.stdout.strip() == verified.version
+ assert verified.version in {scanner["version"] for scanner in scanners}
def test_python_update_uses_the_unified_environment() -> None:
diff --git a/tests/tooling/test_performance_workflow.py b/tests/tooling/test_performance_workflow.py
index 85cf77d..904d7ec 100644
--- a/tests/tooling/test_performance_workflow.py
+++ b/tests/tooling/test_performance_workflow.py
@@ -12,10 +12,11 @@
from typing import TYPE_CHECKING, cast
import pytest
+import yaml
from research_repo_tools.common_measurement import CompleteMeasurementPlan, DiscoveredSeries, DiscoveryPolicy, measure_prepared_pair, validate_run_policy
from research_repo_tools.complete_runs import RunSeries
from research_repo_tools.evidence import load_evidence, publish_evidence
-from research_repo_tools.just_inspect import dry_run
+from research_repo_tools.just_inspect import dry_run, inspect_justfile
from research_repo_tools.measurement import load_measurement
from research_repo_tools.performance_release import Preparation, serialize_preparation
from research_repo_tools.process import run_command
@@ -166,6 +167,44 @@ def test_declared_scientific_sampling_coverage_and_original_peer_phases(view: st
} <= set(plan.harness)
+def test_routine_ci_validates_benchmarks_without_acquiring_timings() -> None:
+ """Preserve the native CI checks and reserve sampling for explicit requests."""
+ workflows = ROOT / ".github/workflows"
+ ci = yaml.safe_load((workflows / "ci.yml").read_bytes())
+ ci_events = ci.get("on", ci.get(True))
+ assert ci_events["push"]["branches"] == ci_events["pull_request"]["branches"] == ["main"]
+ build = ci["jobs"]["build"]
+ platforms = build["strategy"]["matrix"]["os"]
+ assert set(platforms) == {"ubuntu-latest", "macos-latest", "windows-latest"}
+ assert {"name": "Run CI checks", "run": "just ci"} in build["steps"]
+ ruleset = json.loads((ROOT / ".github/settings/main-ruleset.json").read_bytes())
+ required = next(rule["parameters"]["required_status_checks"] for rule in ruleset["rules"] if rule["type"] == "required_status_checks")
+ contexts = {check["context"] for check in required}
+ assert {f"build ({platform})" for platform in platforms} <= contexts
+
+ commands = dry_run(ROOT, "ci").stderr.splitlines()
+ assert any("cargo nextest run --workspace --release --profile ci --all-features --lib --tests" in line for line in commands)
+ bench_commands = [line.split(" cargo bench ", 1)[1] for line in commands if " cargo bench " in line]
+ compile_commands = [line for line in bench_commands if "--no-run" in line.split()]
+ assert any("--workspace --no-run --features bench" in line for line in compile_commands)
+ assert any("--no-run --features bench,exact --bench exact" in line for line in compile_commands)
+ assert [line for line in bench_commands if line not in compile_commands] == ['--locked --features bench,exact --bench exact -- "$@"']
+ recipes = inspect_justfile(ROOT).recipes
+ assert {"test-bench-exact", "bench-compile", "test-rust-ci"} <= {dep["recipe"] for dep in recipes["ci"]["dependencies"]}
+ smoke = recipes["test-bench-exact"]["dependencies"]
+ assert [(dep["recipe"], dep["arguments"]) for dep in smoke] == [("bench-exact", ["--test"])]
+
+ diagnostic = yaml.safe_load((workflows / "benchmarks.yml").read_bytes())
+ assert set(diagnostic.get("on", diagnostic.get(True))) == {"workflow_dispatch"}
+ assert diagnostic["permissions"] == {"contents": "read"}
+ assert {"bench", "Benchmarks / bench"}.isdisjoint(contexts)
+ steps = diagnostic["jobs"]["bench"]["steps"]
+ assert [step["run"] for step in steps if "run" in step] == ["just test-bench-inputs", "just bench-exact --noplot"]
+ assert steps[-1]["with"]["path"] == "target/criterion/"
+ assert steps[-1]["with"]["name"].startswith("exploratory-exact-")
+ assert not any(step.get("uses", "").startswith("actions/download-artifact@") for step in steps)
+
+
@pytest.mark.parametrize(
("pattern", "count"),
[("column_basis/*", 55), ("interval_division/*", 5), ("interval_solve_d*/*", 30), ("polynomial/*", 5), ("rational_dot/*", 54)],
diff --git a/tests/tooling/test_release_evidence.py b/tests/tooling/test_release_evidence.py
index b1db199..cc0faea 100644
--- a/tests/tooling/test_release_evidence.py
+++ b/tests/tooling/test_release_evidence.py
@@ -2,15 +2,15 @@
import hashlib
import json
+import shlex
import subprocess
-from argparse import Namespace
from copy import deepcopy
from pathlib import Path
from typing import Any
import pytest
from research_repo_tools import release_assets, release_evidence, release_interfaces, release_validation
-from research_repo_tools.cli import main
+from research_repo_tools.cli import main, parser
from research_repo_tools.config import load
from research_repo_tools.release_interfaces import load_evidence_policy
@@ -39,6 +39,28 @@
REQUIRED_STEPS = (*((index, 0) for index in range(4)), *((4, index) for index in range(3)))
+def install_clean_checkout(patch: pytest.MonkeyPatch, checkout: Path, commit: str) -> None:
+ """Supply read-only Git responses to the installed checkout verifier."""
+ options = ("--no-pager", "--no-optional-locks", "--no-replace-objects", "-c", "core.fsmonitor=false", "-c", "diff.autoRefreshIndex=false")
+
+ def git(executable: str | Path, args: list[str], *, cwd: Path, timeout: int, **_kwargs: object) -> subprocess.CompletedProcess[bytes]:
+ assert cwd.samefile(checkout)
+ assert timeout == 30
+ assert Path(executable).is_absolute()
+ assert tuple(args[: len(options)]) == options
+ command = tuple(args[len(options) :])
+ responses = {
+ ("rev-parse", "HEAD"): f"{commit}\n".encode("ascii"),
+ ("ls-files", "-v", "-z"): b"H Cargo.toml\0",
+ ("submodule", "foreach", "--quiet", "--recursive", shlex.join((Path(executable).as_posix(), *options, "ls-files", "-v", "-z"))): b"",
+ ("status", "--porcelain", "-z", "--untracked-files=all", "--ignore-submodules=none"): b"",
+ }
+ assert command in responses, f"unexpected read-only Git command: {command}"
+ return subprocess.CompletedProcess(args, 0, stdout=responses[command], stderr=b"")
+
+ patch.setattr(release_assets, "run_command_bytes", git)
+
+
class EvidenceProvider:
"""Keep signed inputs independent from live provider responses."""
@@ -149,11 +171,12 @@ def gh(self, *args: str) -> str:
self.gh_calls.append(args)
return self.signature_text if self.signature_text is not None else json.dumps(self.signature)
- def download(self, repository: str, artifact: dict[str, Any], directory: Path, names: tuple[str, ...]) -> None:
+ def download(self, repository: str, artifact: dict[str, Any], directory: Path, names: tuple[str, ...], **bounds: int) -> None:
# ZIP authentication and extraction are covered by the shared package.
# This boundary supplies only the adapter's two extracted input files.
self.downloads.append((repository, deepcopy(artifact), directory, names))
assert names == ("validation.json", "validation.sigstore.json")
+ assert bounds == {"max_bytes": 32 * 1024 * 1024, "max_expanded_bytes": 32 * 1024 * 1024}
directory.mkdir()
payload = self.document_text if self.document_text is not None else json.dumps(self.document)
(directory / "validation.json").write_text(payload, encoding="utf-8")
@@ -173,19 +196,25 @@ def verify(self, identity: str | None = None) -> dict[str, Any]:
def seal(self, output: Path) -> None:
"""Exercise the installed producer including its environment/output guards."""
- args = Namespace(
- action="evidence",
- operation="create",
- configuration="tooling/release-evidence.toml",
- checkout=self.root,
- commit=COMMIT,
- version="0.5.42",
- run_id=101,
- attempt=2,
- output=output,
- github_output=None,
- tools=None,
- archive=None,
+ args = parser().parse_args(
+ [
+ "release",
+ "evidence",
+ "create",
+ "tooling/release-evidence.toml",
+ "--checkout",
+ str(self.root),
+ "--commit",
+ COMMIT,
+ "--version",
+ "0.5.42",
+ "--run-id",
+ "101",
+ "--attempt",
+ "2",
+ "--output",
+ str(output),
+ ]
)
release_interfaces.run_evidence(args, load(root=self.root))
@@ -194,8 +223,7 @@ def install(self, patch: pytest.MonkeyPatch) -> None:
patch.setattr(release_interfaces, "api", self.api)
patch.setattr(release_validation, "gh", self.gh)
patch.setattr(release_evidence, "download_artifact", self.download)
- patch.setattr(release_assets, "run_git_command", self.git)
- patch.setattr(release_assets, "run_git_bytes", lambda *args, **_kwargs: subprocess.CompletedProcess(args, 0, stdout=b"", stderr=b""))
+ install_clean_checkout(patch, self.root, COMMIT)
def cli(self, *args: str) -> int:
return main(
diff --git a/tests/tooling/test_release_publishing.py b/tests/tooling/test_release_publishing.py
index cc616d4..99c704d 100644
--- a/tests/tooling/test_release_publishing.py
+++ b/tests/tooling/test_release_publishing.py
@@ -19,7 +19,7 @@
from research_repo_tools.just_inspect import dry_run
from research_repo_tools.process import run_command
-from tests.tooling.test_release_evidence import PIN_JSON, EvidenceProvider
+from tests.tooling.test_release_evidence import PIN_JSON, EvidenceProvider, install_clean_checkout
REPO_ROOT = Path(__file__).resolve().parents[2]
WORKFLOW = REPO_ROOT / ".github/workflows/publish-crates.yml"
@@ -71,6 +71,7 @@ def git(args: list[str], **_kwargs: object) -> subprocess.CompletedProcess[str]:
f"commits/{SHA}/check-runs?filter=latest&per_page=100": [{"total_count": len(checks), "check_runs": checks}],
}
monkeypatch.setattr(release_publishing, "run_git_command", git)
+ install_clean_checkout(monkeypatch, REPO_ROOT, SHA)
monkeypatch.setattr(release_publishing, "_api", lambda _root, _repo, endpoint, **_kwargs: responses[endpoint])
monkeypatch.setattr(release_publishing, "_remote_commit", lambda *_args: SHA)
release = release_publishing.GitHubRelease(
@@ -253,7 +254,7 @@ def run_evidence_fixture() -> int:
def evidence_runner(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""Keep real workflow shell, Just, uv, Python, verifier and output writer."""
source = (REPO_ROOT / "justfile").read_text(encoding="utf-8")
- runtime = '[script("uvx", "--python", "3.15", "--from", "research-repo-tools==0.2.4", "python", "-I")]'
+ runtime = '[script("uvx", "--python", "3.15", "--from", "research-repo-tools==0.2.6", "python", "-I")]'
assert source.count(runtime) == 2
# Run the exact recipe body/argument transport with the locked test runtime.
# The separate isolated-runtime smoke and pin test own uvx installation.
@@ -482,7 +483,7 @@ def test_main_ci_seals_only_complete_native_and_cargo_validation() -> None:
assert evidence["permissions"] == {"contents": "read", "actions": "read", "attestations": "write", "id-token": "write"}
steps = {step["name"]: step for step in evidence["steps"] if "name" in step}
capture = steps["Capture successful native validation identities"]["run"]
- assert "uvx --python 3.15 --from research-repo-tools==0.2.4 python" in capture
+ assert "uvx --python 3.15 --from research-repo-tools==0.2.6 python" in capture
assert '"release", "evidence", "create", "tooling/release-evidence.toml"' in capture
assert '"--output", "target/release-evidence/validation.json"' in capture
assert steps["Sign original main validation evidence"]["with"]["subject-path"] == "target/release-evidence/validation.json"
diff --git a/tests/tooling/test_semgrep_integration.py b/tests/tooling/test_semgrep_integration.py
new file mode 100644
index 0000000..1382528
--- /dev/null
+++ b/tests/tooling/test_semgrep_integration.py
@@ -0,0 +1,50 @@
+"""Consumer integration with the published Semgrep fixture checker."""
+
+import json
+import os
+import shutil
+from pathlib import Path
+
+import pytest
+from research_repo_tools.process import run_command
+
+ROOT = Path(__file__).resolve().parents[2]
+
+
+@pytest.mark.parametrize(
+ ("source", "diagnostic"),
+ [("print('blocked')", ""), ("print('allowed')", "expected finding not reported"), ("print('blocked')\nprint('blocked')", "unexpected finding")],
+)
+def test_real_fixture_checker_keeps_positive_negative_gates(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, source: str, diagnostic: str) -> None:
+ """Actual native scans accept a match and reject missing or extra findings."""
+ for name in ("pyproject.toml", "uv.lock", ".python-version"):
+ shutil.copyfile(ROOT / name, tmp_path / name)
+ (tmp_path / "semgrep.yaml").write_text(
+ json.dumps(
+ {"rules": [{"id": "la-stack.fixture", "languages": ["python"], "message": "fixture violation", "severity": "ERROR", "pattern": "print('blocked')"}]}
+ ),
+ encoding="utf-8",
+ )
+ fixtures = tmp_path / "tests/semgrep"
+ fixtures.mkdir(parents=True)
+ (fixtures / "fixture.py").write_text(f"# ruleid: la-stack.fixture\n{source}\n# ok: la-stack.fixture\nprint('allowed')\n", encoding="utf-8")
+ # The locked uv launch must select the project scripts ahead of ambient tools.
+ ambient = tmp_path / "ambient"
+ ambient.mkdir()
+ for name in ("semgrep", "pysemgrep"):
+ binary = ambient / (f"{name}.exe" if os.name == "nt" else name)
+ binary.write_text("#!/bin/sh\nexit 97\n", encoding="utf-8", newline="\n")
+ binary.chmod(0o755)
+ monkeypatch.setenv("PATH", os.pathsep.join([str(ambient), os.environ["PATH"]]))
+ result = run_command(
+ "uv",
+ ["run", "--locked", "--group", "dev", "research-repo-tools", "--root", str(tmp_path), "semgrep", "check-fixtures"],
+ cwd=ROOT,
+ check=False,
+ )
+ assert result.returncode == (1 if diagnostic else 0), result.stderr
+ if diagnostic:
+ assert "la-stack.fixture" in result.stderr
+ assert diagnostic in result.stderr
+ else:
+ assert not result.stderr
diff --git a/tests/tooling/test_shared_tooling.py b/tests/tooling/test_shared_tooling.py
deleted file mode 100644
index 783fc8c..0000000
--- a/tests/tooling/test_shared_tooling.py
+++ /dev/null
@@ -1,73 +0,0 @@
-"""Temporary scanner selection preserves the installed fixture checker's gates."""
-
-import json
-import os
-import sys
-import sysconfig
-from importlib.metadata import version
-from pathlib import Path
-
-import pytest
-from research_repo_tools import semgrep_tool
-
-from tooling import shared_tooling as adapter
-
-ROOT = Path(__file__).resolve().parents[2]
-
-
-def test_scanner_setup_uses_standard_environment_and_restores_override(capsys: pytest.CaptureFixture[str]) -> None:
- """The shared setup path cannot acquire a scanner in another environment."""
- previous_setup, previous_version, previous_path = semgrep_tool.setup, semgrep_tool.VERSION, os.environ.get("PATH")
- assert sys.version_info[:2] == (3, 15)
- assert adapter.main(["--root", str(ROOT), "semgrep", "setup"]) == 0
- expected = Path(sysconfig.get_path("scripts")) / ("semgrep.exe" if os.name == "nt" else "semgrep")
- output = capsys.readouterr().out
- assert f"Semgrep {version('semgrep')}: {expected}" in output
- assert semgrep_tool.setup is previous_setup
- assert previous_version == semgrep_tool.VERSION
- assert os.environ.get("PATH") == previous_path
-
-
-@pytest.mark.parametrize(
- ("source", "diagnostic"),
- [("print('blocked')", ""), ("print('allowed')", "expected finding not reported"), ("print('blocked')\nprint('blocked')", "unexpected finding")],
-)
-def test_real_fixture_checker_keeps_positive_negative_gates(
- tmp_path: Path, monkeypatch: pytest.MonkeyPatch, source: str, diagnostic: str, capsys: pytest.CaptureFixture[str]
-) -> None:
- """Actual native scans accept a match and reject missing or extra findings."""
- (tmp_path / "pyproject.toml").write_text(
- '[tool.research-repo-tools.semgrep]\nconfig = "rules.yaml"\nfixtures = "fixture.py"\nnamespace = "la-stack."\ncwd = "."\n',
- encoding="utf-8",
- )
- (tmp_path / "rules.yaml").write_text(
- json.dumps(
- {"rules": [{"id": "la-stack.fixture", "languages": ["python"], "message": "fixture violation", "severity": "ERROR", "pattern": "print('blocked')"}]}
- ),
- encoding="utf-8",
- )
- (tmp_path / "fixture.py").write_text(f"# ruleid: la-stack.fixture\n{source}\n# ok: la-stack.fixture\nprint('allowed')\n", encoding="utf-8")
- # A broken ambient scanner must not be selected by either native launcher.
- ambient = tmp_path / "ambient"
- ambient.mkdir()
- for name in ("semgrep", "pysemgrep"):
- binary = ambient / name
- binary.write_text("#!/bin/sh\nexit 97\n", encoding="utf-8")
- binary.chmod(0o755)
- monkeypatch.setenv("PATH", os.pathsep.join([str(ambient), os.environ["PATH"]]))
- previous_path = os.environ["PATH"]
- assert adapter.main(["--root", str(tmp_path), "semgrep", "check-fixtures"]) == (1 if diagnostic else 0)
- errors = capsys.readouterr().err
- if diagnostic:
- assert "la-stack.fixture" in errors
- assert diagnostic in errors
- else:
- assert not errors
- assert os.environ["PATH"] == previous_path
-
-
-def test_override_requires_review_when_shared_package_advances(monkeypatch: pytest.MonkeyPatch) -> None:
- """Do not silently keep the temporary internal override after its fixed release."""
- monkeypatch.setattr(adapter, "version", lambda _name: "0.2.5")
- with pytest.raises(RuntimeError, match="la-stack#317"):
- adapter.main(["semgrep", "setup"])
diff --git a/tests/tooling/test_toolchain_integration.py b/tests/tooling/test_toolchain_integration.py
index 805cd07..2c9daca 100644
--- a/tests/tooling/test_toolchain_integration.py
+++ b/tests/tooling/test_toolchain_integration.py
@@ -25,7 +25,7 @@
UPDATER = ["run", "--locked", "--only-group", "tooling", "--inexact", "research-repo-tools"]
UV_UPDATE = ["run", "--no-config", "--no-sync", "--no-python-downloads", "research-repo-tools", "deps", "update-uv"]
TOOL_UPDATE = [*UPDATER, "toolchain", "upgrade"]
-SETUP = ["run", "--locked", "--managed-python", "--only-group", "tooling", "python", "tooling/shared_tooling.py", "setup"]
+SETUP = ["run", "--locked", "--managed-python", "--only-group", "tooling", "research-repo-tools", "setup"]
CARGO_UPDATE = [
[*UPDATER, "toolchain", "run", "--", "cargo", "upgrade", "--incompatible", "allow", "--exclude", "num-bigint", "--exclude", "num-rational"],
[*UPDATER, "toolchain", "run", "--", "cargo", "update"],
@@ -46,12 +46,12 @@
def test_published_tooling_package_matches_manifest_lock_and_installation() -> None:
"""Keep the exact published-package contract in one consumer test."""
- expected_version = "0.2.4"
+ expected_version = "0.2.6"
manifest = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8"))
assert manifest["project"]["dependencies"] == []
assert manifest["tool"]["uv"]["package"] is False
assert "build-system" not in manifest
- assert manifest["dependency-groups"]["tooling"] == [f"research-repo-tools[python-tools]=={expected_version}"]
+ assert manifest["dependency-groups"]["tooling"] == [f"research-repo-tools[python-tools,semgrep]=={expected_version}"]
assert {"include-group": "tooling"} in manifest["dependency-groups"]["dev"]
assert manifest["tool"]["research-repo-tools"]["toolchain"]["inherit-python-tools"] is True
assert not {Requirement(item).name for item in manifest["dependency-groups"]["dev"] if isinstance(item, str)} & TOOLS
@@ -60,8 +60,8 @@ def test_published_tooling_package_matches_manifest_lock_and_installation() -> N
package = next(item for item in lock["package"] if item["name"] == "research-repo-tools")
assert package["version"] == expected_version
assert package["source"] == {"registry": "https://pypi.org/simple"}
- assert package["wheels"][0]["hash"] == "sha256:00aaab2b7efd680d79324c6debb64db4d8d1ff076a460ff46e24e0ef1c24bd7d"
- assert package["sdist"]["hash"] == "sha256:30b6d4d2f8d0cb98d5311656928dd65800088b651dd6c0c29f9a02526e9e902a"
+ assert package["wheels"][0]["hash"] == "sha256:89ebae476b5bde0cffc321603ed1d867796e3276a7fb88c7dc1f7de53a13e7c2"
+ assert package["sdist"]["hash"] == "sha256:dd826a916f037f47b925ca3268adc76d415a8561547bedda348a67f4f2e2e065"
locked = {item["name"]: item["version"] for item in lock["package"]}
for name, expected in shared_tool_versions().items():
assert locked[name] == version(name) == expected
diff --git a/tooling/README.md b/tooling/README.md
index 1ee4a5e..0c36846 100644
--- a/tooling/README.md
+++ b/tooling/README.md
@@ -46,20 +46,16 @@ Local scans, fixture checks and SARIF retain the Linux stack-limit workaround
for [Semgrep #11979](https://github.com/semgrep/semgrep/issues/11979).
Ordinary Rust and Python tests remain scan targets; deliberate `tests/semgrep/`
fixtures are checked separately by `just semgrep-test`.
-Published v0.2.4 hard-codes Python 3.14 for those fixture checks. The temporary
-`shared_tooling.py` adapter selects the scanner installed beside the active
-interpreter and delegates all fixture validation to the shared CLI. It also puts
-that environment's scripts first on `PATH` for the native scanner launcher.
-Setup uses the same adapter so it does not provision a separate scanner runtime.
-The shared Python baseline must apply to Semgrep too; the fix is tracked in
-[research-repo-tools#165](https://github.com/acgetchell/research-repo-tools/issues/165)
-for v0.2.5. Remove the version-guarded override through
-[#317](https://github.com/acgetchell/la-stack/issues/317) after adopting that fix.
+The shared package's `semgrep` extra declares the compatible scanner range.
+Setup and `semgrep check-fixtures` verify the installed executable against
+`uv.lock` and the root Python selector.
+Intel macOS uses Semgrep 1.182.1, the last release with a wheel for that platform.
+Other platforms resolve Semgrep 1.184.0 or newer within the shared range.
## Shared capability adoption
-The capability inventory below uses the installed v0.2.4 CLI and
-its [versioned contracts](https://github.com/acgetchell/research-repo-tools/blob/v0.2.4/README.md#common-workflows).
+The capability inventory below uses the installed v0.2.6 CLI and
+its [versioned contracts](https://github.com/acgetchell/research-repo-tools/blob/v0.2.6/README.md#common-workflows).
Trusted release jobs use the same exact package pin through `uvx --python 3.15`.
The shared updater installs and verifies candidate Cargo tools before publishing
new pins; no local updater implementation is needed.
@@ -191,7 +187,7 @@ report and plot rendering after scratch cleanup.
Shared implementation matrices live upstream, including
`tests/performance/public_complete_consumer.py` and
`public_criterion_consumer.py`, exercised from wheel and sdist installations.
-Published v0.2.4 also owns `public_suites_consumer.py`: suite selection, raw-file
+The shared package also owns `public_suites_consumer.py`, which covers suite selection, raw-file
digests, archive paths, original host retention and complete assembly failures.
The release workflow calls `performance pack-suite` and `assemble-suites`
directly; `bench-release-check-suite` is a diagnostic wrapper around
diff --git a/tooling/shared_tooling.py b/tooling/shared_tooling.py
deleted file mode 100644
index 5217cb3..0000000
--- a/tooling/shared_tooling.py
+++ /dev/null
@@ -1,49 +0,0 @@
-"""Keep v0.2.4 Semgrep setup/scans in the standard environment until #317.
-
-Only scanner selection is overridden; the installed shared CLI owns all fixture
-validation. Remove this adapter after research-repo-tools#165 ships in v0.2.5.
-"""
-
-import os
-import sys
-import sysconfig
-from importlib.metadata import version
-from pathlib import Path
-
-from research_repo_tools import semgrep_tool
-from research_repo_tools.cli import main as tooling_main
-from research_repo_tools.process import resolve_executable
-
-
-def main(argv: list[str] | None = None) -> int:
- """Run the shared CLI with the scanner installed beside this interpreter."""
- if version("research-repo-tools") != "0.2.4":
- msg = "Review/remove the v0.2.4 Semgrep override before upgrading (la-stack#317)."
- raise RuntimeError(msg)
- scripts = Path(sysconfig.get_path("scripts"))
- previous_setup = semgrep_tool.setup
- previous_version = semgrep_tool.VERSION
- previous_path = os.environ.get("PATH")
-
- def scanner(root: Path) -> Path:
- # Shared setup synchronizes dev before selecting its scanner.
- # The shared CLI prints this module constant after selecting the binary.
- vars(semgrep_tool)["VERSION"] = version("semgrep")
- return resolve_executable(scripts / ("semgrep.exe" if os.name == "nt" else "semgrep"))
-
- try:
- # The native launcher also resolves pysemgrep through PATH.
- os.environ["PATH"] = os.pathsep.join([str(scripts), *([previous_path] if previous_path else [])])
- vars(semgrep_tool)["setup"] = scanner
- return tooling_main(argv)
- finally:
- vars(semgrep_tool)["setup"] = previous_setup
- vars(semgrep_tool)["VERSION"] = previous_version
- if previous_path is None:
- os.environ.pop("PATH", None)
- else:
- os.environ["PATH"] = previous_path
-
-
-if __name__ == "__main__":
- sys.exit(main())
diff --git a/uv.lock b/uv.lock
index 3f629a7..f8ede4f 100644
--- a/uv.lock
+++ b/uv.lock
@@ -1,9 +1,18 @@
version = 1
revision = 5
requires-python = ">=3.15"
+resolution-markers = [
+ "platform_machine == 'x86_64' and sys_platform == 'darwin'",
+ "platform_machine != 'x86_64' or sys_platform != 'darwin'",
+]
[manifest]
+constraints = [
+ { name = "semgrep", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'", specifier = ">=1.184.0" },
+ { name = "semgrep", marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'", specifier = "==1.182.1" },
+]
overrides = [
+ { name = "jsonschema", specifier = "==4.26.0" },
{ name = "mcp", specifier = "==1.30.0" },
{ name = "opentelemetry-api", specifier = "==1.45.1" },
{ name = "opentelemetry-exporter-otlp-proto-http", specifier = "==1.45.1" },
@@ -12,6 +21,7 @@ overrides = [
{ name = "opentelemetry-sdk", specifier = "==1.45.1" },
{ name = "pyjwt", extras = ["crypto"], specifier = ">=2.15.1,<3" },
{ name = "pywin32", marker = "sys_platform == 'win32'", specifier = "==312" },
+ { name = "tomli", specifier = "==2.5.0" },
]
[[package]]
@@ -31,14 +41,14 @@ wheels = [
[[package]]
name = "anyio"
-version = "4.15.1"
+version = "4.16.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966, upload-time = "2026-09-05T10:42:39.44Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/a1/98/7e457a3b014976887a050561955c0b76e1e7e2d6e27b78ecef2ef80518f2/anyio-4.16.0.tar.gz", hash = "sha256:59bb03251907983d1603d27c3087ddb1e93299ea2163adcdba37899a4fb12ffb", size = 310971, upload-time = "2026-11-01T11:20:07.282Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/16/7757fb84c819accc7d7e9af5291b261e4abb8627ee040f99e7d35ef2457a/anyio-4.16.0-py3-none-any.whl", hash = "sha256:c66ae634e0b8228396b801741beb3294326d4b6102f7bba4041c0dfb58c29026", size = 134521, upload-time = "2026-11-01T11:20:05.59Z" },
]
[[package]]
@@ -61,11 +71,11 @@ wheels = [
[[package]]
name = "bracex"
-version = "3.2"
+version = "3.2.1"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/fb/37/adf8dd2cde958c2d086bdd87fc36b532fd824bf38f10be6348d65da0c8f9/bracex-3.2.tar.gz", hash = "sha256:0a3d40bfae4caae4d3bfd0ea715874799fab711c38fd4012cc35343e815405cd", size = 29771, upload-time = "2026-10-22T00:28:49.508Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/4d/fc/137095eb2f24ccf0b4db196abd9f5cfe74398a834af51d4bc4876e0beaf0/bracex-3.2.1.tar.gz", hash = "sha256:07a9b2100ceecc0c24afa1a1714bf8d4a6abb43d592939455f662b682ac9baa4", size = 32694, upload-time = "2026-11-03T14:13:43.53Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/42/65/8030b03de8137d1aab7dd6ecfecdba2fc22fc849fa35959b024f62293a99/bracex-3.2-py3-none-any.whl", hash = "sha256:8f3e908a452f66707e1d6d91b3d7d7d4c649d248df18537c0dc710aeb9c30d87", size = 12019, upload-time = "2026-10-22T00:28:48.233Z" },
+ { url = "https://files.pythonhosted.org/packages/47/ae/30dd8b027b4032b27e81364de5b74bfbef50b3c84c6aa0ead351bbb3749c/bracex-3.2.1-py3-none-any.whl", hash = "sha256:e8622d46b60ef6062b4091033f97a304e1b205e70c0285743c5038b73886a47e", size = 12527, upload-time = "2026-11-03T14:13:42.032Z" },
]
[[package]]
@@ -365,7 +375,7 @@ wheels = [
[[package]]
name = "jsonschema"
-version = "4.25.1"
+version = "4.26.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs" },
@@ -373,9 +383,9 @@ dependencies = [
{ name = "referencing" },
{ name = "rpds-py" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" },
+ { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" },
]
[[package]]
@@ -398,14 +408,13 @@ source = { virtual = "." }
[package.dev-dependencies]
dev = [
{ name = "actionlint-py" },
- { name = "research-repo-tools", extra = ["python-tools"] },
- { name = "semgrep" },
+ { name = "research-repo-tools", extra = ["python-tools", "semgrep"] },
{ name = "shellcheck-py" },
{ name = "shfmt-py" },
{ name = "yamllint" },
]
tooling = [
- { name = "research-repo-tools", extra = ["python-tools"] },
+ { name = "research-repo-tools", extra = ["python-tools", "semgrep"] },
]
[package.metadata]
@@ -413,13 +422,12 @@ tooling = [
[package.metadata.requires-dev]
dev = [
{ name = "actionlint-py", specifier = "==1.7.12.25" },
- { name = "research-repo-tools", extras = ["python-tools"], specifier = "==0.2.4" },
- { name = "semgrep", specifier = "==1.182.1" },
+ { name = "research-repo-tools", extras = ["python-tools", "semgrep"], specifier = "==0.2.6" },
{ name = "shellcheck-py", specifier = "==0.11.0.1" },
{ name = "shfmt-py", specifier = "==4.2.0" },
{ name = "yamllint", specifier = "==1.38.0" },
]
-tooling = [{ name = "research-repo-tools", extras = ["python-tools"], specifier = "==0.2.4" }]
+tooling = [{ name = "research-repo-tools", extras = ["python-tools", "semgrep"], specifier = "==0.2.6" }]
[[package]]
name = "markdown-it-py"
@@ -694,7 +702,7 @@ wheels = [
[[package]]
name = "pydantic"
-version = "2.14.1"
+version = "2.14.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
@@ -702,50 +710,50 @@ dependencies = [
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/7c/0b/8e10b2e693af8ec54346a14caf36221334775975a747c9ceaa3f8371d96d/pydantic-2.14.1.tar.gz", hash = "sha256:94f478203dd03404682a1ada216965651dd74b1d2d5ffd62e00e0837caab5c26", size = 900362, upload-time = "2026-10-11T18:37:55.396Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/b4/fc/1203d011145c98ddce3cd2f505bb54375ef8d43223053df6bfcf3c746f46/pydantic-2.14.3.tar.gz", hash = "sha256:1054f237f4d4733e5a30e1e2333bd33a2004ab9ab1510bd053a9a31bb48e232d", size = 925636, upload-time = "2026-11-05T09:17:04.686Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/35/ea/a56b9fe5066f3537b7882f77e9c5dfb26d8c2eefdaed9b5fc73d57b4dc22/pydantic-2.14.1-py3-none-any.whl", hash = "sha256:9195d967ec791692a04438115466764fb8b9a27b31f14a760437694f40d6b454", size = 488106, upload-time = "2026-10-11T18:37:53.437Z" },
+ { url = "https://files.pythonhosted.org/packages/72/b8/b864503789ef5c58597d2a7b4adbee62c26440ab1e682581fc6838df8947/pydantic-2.14.3-py3-none-any.whl", hash = "sha256:3f7b347782ab7e4cf4c798496ae3a82787d9f21ec12676a9c564355eeef8daa8", size = 494071, upload-time = "2026-11-05T09:17:02.426Z" },
]
[[package]]
name = "pydantic-core"
-version = "2.50.1"
+version = "2.50.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/a6/24/af4ec4be49fbc810f35b0bdcacc3d433b56bbfa469fd3bfd4ae116cd9bf1/pydantic_core-2.50.1.tar.gz", hash = "sha256:e50d7b94baac6c7d09927fa5ca5800a0c7ee5015c7fcff65beb3a1931b5a6e09", size = 513515, upload-time = "2026-10-11T18:35:44.82Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/b2/84/23984b763d8862a02a13d27a44b6e8169428fd85ecfed88f54c29108604d/pydantic_core-2.50.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:8a6791afa2245e6c6b180122d105941644f5bd410bb18623b408808cc41a3102", size = 2224599, upload-time = "2026-10-11T18:34:04.016Z" },
- { url = "https://files.pythonhosted.org/packages/4d/90/a63cf8586abc1d1a3f6d9b18f0224789ebf003851f092ebda3c7c863fd32/pydantic_core-2.50.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:84f34323a61a365b4e9295de6028474754829aaddd59c7bf1a040e7487ef8f3c", size = 2024191, upload-time = "2026-10-11T18:34:05.901Z" },
- { url = "https://files.pythonhosted.org/packages/a6/6a/f34bff9808ffb4907fbf5f5040457d253cacf2da7fce9efbc99ca1b1a44d/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23edad659e8dbd8ca7e4e877fe6c81573abbdf215bd25a68b53e1272f58b80c7", size = 2048252, upload-time = "2026-10-11T18:34:07.757Z" },
- { url = "https://files.pythonhosted.org/packages/b2/54/13f419bf1eb59852003818e25935aaf175687d978f4c4bca70e08fe40a3b/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a5fce22f1e87d181e924e12da7d81cfe031fb3881a5ddf26ad28f141756ca43", size = 2173376, upload-time = "2026-10-11T18:34:09.592Z" },
- { url = "https://files.pythonhosted.org/packages/6d/6c/b5a34d24cd0c81669d8f8339d74e6815abcf2f8fb48ab4b49b84c09be1d5/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c73622ef819328873b53109ee4f77ceb598bffedd02daf916102be3228866b78", size = 2364559, upload-time = "2026-10-11T18:34:11.534Z" },
- { url = "https://files.pythonhosted.org/packages/eb/8d/d64d6216a8df365082665927ff923f183056f9049fee08e9777c9ac05296/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce8c25ca38cc0e3d7753ba180808de2c0c8cb24eae0df64491e40921454e9831", size = 2444697, upload-time = "2026-10-11T18:34:13.535Z" },
- { url = "https://files.pythonhosted.org/packages/b8/0d/1b1149f60a00ea21ba5f70e28acbd40feb4598af414f80f53c921fac07c9/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7689580e72a642ab5ec64d5f55b2e33636fa43b4ebe63c0c2c965ef307c7d1aa", size = 2159961, upload-time = "2026-10-11T18:34:15.56Z" },
- { url = "https://files.pythonhosted.org/packages/1e/35/f236549299dcc78e71e945495d7ec67e78d20844d0999c60601685003031/pydantic_core-2.50.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:d5c0e32fdbce7f1e8ef4d11f655694bf5f4175c757a9f1dc2be09b8864e5bcf5", size = 2234581, upload-time = "2026-10-11T18:34:17.502Z" },
- { url = "https://files.pythonhosted.org/packages/6a/85/26901a490522b7f75ef9bb9a7afb73e5bb550f0e1cb5b99a0069183e2eb9/pydantic_core-2.50.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40f523349960fa30f3ea51404308ff50f9997a90df639590f47a057c1f32b415", size = 2244090, upload-time = "2026-10-11T18:34:19.402Z" },
- { url = "https://files.pythonhosted.org/packages/ca/2c/481bcfc70ceeb77a778ca6e5b705fe592cb64735c108157f81b7dd9c280e/pydantic_core-2.50.1-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:d4193206b6587047437f6f11d7e776df23e1c1e23af2a54d9347275614791e10", size = 2277948, upload-time = "2026-10-11T18:34:21.317Z" },
- { url = "https://files.pythonhosted.org/packages/24/eb/f1e09333faa7ba447cde967310f758430cc7c5e987bdab5228816c70030d/pydantic_core-2.50.1-cp315-cp315-musllinux_1_1_armv7l.whl", hash = "sha256:84bc765b282a9d5b7fe0348b8648904f25a6a04b2139da52b1dd30c8ac3a2c8f", size = 2449688, upload-time = "2026-10-11T18:34:23.321Z" },
- { url = "https://files.pythonhosted.org/packages/d1/b3/036bde636db8f76d92996e81aefc75678ab5cec4a07eea1ad0c72a893fc3/pydantic_core-2.50.1-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:ed1e728b39a383c81035b2459cfcb35d99dfb01f7d6ebe3a913bc1cc5b81e459", size = 2479286, upload-time = "2026-10-11T18:34:25.214Z" },
- { url = "https://files.pythonhosted.org/packages/d0/a3/07f018294ee18d144afeb6df1a47d9e92960f014be45c5ed13519fa5af95/pydantic_core-2.50.1-cp315-cp315-win32.whl", hash = "sha256:bc94f474417604bd383d2cd445d071b07dd55fedceed3ce33407bf1fcc107290", size = 2103683, upload-time = "2026-10-11T18:34:27.42Z" },
- { url = "https://files.pythonhosted.org/packages/5f/98/f9bd7e1f9b6709f155acb9ef826d9c3884fe925f811fd8e55b9b52280bad/pydantic_core-2.50.1-cp315-cp315-win_amd64.whl", hash = "sha256:983a662de2571cb2502fc8ff47b6770b03d025d2eb314c92f77b3f07c74720ed", size = 2171681, upload-time = "2026-10-11T18:34:29.508Z" },
- { url = "https://files.pythonhosted.org/packages/10/87/4bb3e1e7f385c076ab5af4d6dd0571b22042cd8207eb810d5b9fef15ae31/pydantic_core-2.50.1-cp315-cp315-win_arm64.whl", hash = "sha256:94845ff54dc5193f228cab81b2662a04bfbb892e95bdc15edf7399000ce57d54", size = 2156251, upload-time = "2026-10-11T18:34:31.455Z" },
- { url = "https://files.pythonhosted.org/packages/47/47/83643225b08f2aef6c8cc4bbe6e3f79c5e139c4364a6e450d0f399d87774/pydantic_core-2.50.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:4a53d13cdfbedbfa87f08b83c1a0a5efcc767d785a4b41934fa9cb672670493a", size = 2234363, upload-time = "2026-10-11T18:34:33.65Z" },
- { url = "https://files.pythonhosted.org/packages/5e/66/127ca649ba2f2462039dc1e694c6c01e00a3689f023a617364d3507e6d97/pydantic_core-2.50.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:efbecf43d321f7b9281441f1f213f7c21c66988b0e06c2730ba13ed47a46bb08", size = 2000972, upload-time = "2026-10-11T18:34:36.037Z" },
- { url = "https://files.pythonhosted.org/packages/5e/4f/e421e0a5d653b1203b090b2e48745976988d7338a647940704b5b9c2b399/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc1f08f68dac9f9e83845a8039880aba2ab553eb9b2259c3243a313182c253fe", size = 2022925, upload-time = "2026-10-11T18:34:37.972Z" },
- { url = "https://files.pythonhosted.org/packages/d5/4e/ea5568e2491e1a71100f15ae8c2d01ef52db42a184a2d4e716bc79e5eb8f/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5dfe41f232befddb9c4377f6cfc702b51595e2d78ed082672adf8758d2c4619f", size = 2192867, upload-time = "2026-10-11T18:34:39.921Z" },
- { url = "https://files.pythonhosted.org/packages/ae/5e/3b8c3a35acbe219909ada5defad5d7d9fed845fb2b37bd5ec518f453c119/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adc06d218a1cadfd2ec4628424d7d79ce4eba69c2965e7e7b55106f0da5208c8", size = 2388687, upload-time = "2026-10-11T18:34:42.184Z" },
- { url = "https://files.pythonhosted.org/packages/b6/aa/7889b4e515f91a2e8c0ae6b5081fec0feb30c4398d1434e14793c60f173a/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cf91809d0721ab81592ba67bea7694821679c10b1a2e3c3460082b286c1918a", size = 2464415, upload-time = "2026-10-11T18:34:44.384Z" },
- { url = "https://files.pythonhosted.org/packages/08/78/93449e628eb8a6fdcce3eff9043081179d1bc7ce6f1bff32dc5006f41e00/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23923ab9292c40da026330b1ecf4dc2618c8e86e0422e5d1fbf50d94d64ca4f8", size = 2150823, upload-time = "2026-10-11T18:34:46.392Z" },
- { url = "https://files.pythonhosted.org/packages/f4/f1/72c5bc129fceb0d00f05dc1e67f518c1728de1928c55f81fc13d7690de39/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:f3377c8c2b3ce898423c5e5dd94c7982e30aa7717a7e6ab2470b9de364963709", size = 2245806, upload-time = "2026-10-11T18:34:48.805Z" },
- { url = "https://files.pythonhosted.org/packages/28/2a/922a0e78f3aa6ab837b59f88190233fb8dad546c17995fde9bb3ed3b9b49/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:455a773617b5913bf5c20d0692e5787b119e52c4d40ea644ca31f5758fd31be2", size = 2264160, upload-time = "2026-10-11T18:34:50.876Z" },
- { url = "https://files.pythonhosted.org/packages/52/a8/0f1449e3e1b20941c9372faa18e7b6a092e30cdfa841902473f7989532ac/pydantic_core-2.50.1-cp315-cp315t-musllinux_1_1_aarch64.whl", hash = "sha256:1a9006395dece0e32e704c315eff8a00bede494f6108546cfc5539c89fef4f9a", size = 2294031, upload-time = "2026-10-11T18:34:52.876Z" },
- { url = "https://files.pythonhosted.org/packages/d1/d0/1031f492857de70355fb16524bbb03efce5fd34c93ed4a1ec60be07e0d4b/pydantic_core-2.50.1-cp315-cp315t-musllinux_1_1_armv7l.whl", hash = "sha256:d2d82aa62521c55ddfb000ae70f88cdd8de974078f6024e821dfe5addd0c818f", size = 2468423, upload-time = "2026-10-11T18:34:54.995Z" },
- { url = "https://files.pythonhosted.org/packages/46/52/269ffffa645b8e47906395a39cf9db1151ae9fa4bcd7f47b960bc31baf37/pydantic_core-2.50.1-cp315-cp315t-musllinux_1_1_x86_64.whl", hash = "sha256:009634b83993777ddcd69cad0ffcace43dabde692109528e35f0fde91e386a8b", size = 2512598, upload-time = "2026-10-11T18:34:57.149Z" },
- { url = "https://files.pythonhosted.org/packages/31/5c/e47e28281f20326ff6f3c31d626f0a83e615d2d94ba41bb0ad6ec237184a/pydantic_core-2.50.1-cp315-cp315t-win32.whl", hash = "sha256:3fde4fdc6487a58d944ca87cf5adc95d5f266e872c19599f5f4c0a8a1b1f9f9f", size = 2098535, upload-time = "2026-10-11T18:34:59.313Z" },
- { url = "https://files.pythonhosted.org/packages/03/ad/759e181e69c1b472c60b2049e5f61d5da1deefdd5ce1df4bb2ebcf771f25/pydantic_core-2.50.1-cp315-cp315t-win_amd64.whl", hash = "sha256:1c8632d4ac04e6f91128fca584b3a8a507d81604c24eeaaad00d4be42765c32b", size = 2148645, upload-time = "2026-10-11T18:35:01.571Z" },
- { url = "https://files.pythonhosted.org/packages/6d/56/8a702c27e5be9f47e5f19d8669227424290e4c024e7c370279cbaf244b4e/pydantic_core-2.50.1-cp315-cp315t-win_arm64.whl", hash = "sha256:c3ede305158e75510be50869b319550ab072008c13d64d4ab1e094fb286b6f44", size = 2175297, upload-time = "2026-10-11T18:35:04.079Z" },
+sdist = { url = "https://files.pythonhosted.org/packages/9c/0f/34f9ce4ba701dd0b815033ef94d99b56f66200e578ee6052aa3ac26f064e/pydantic_core-2.50.4.tar.gz", hash = "sha256:aead3b74b68396bf0feab5d57e050b730ded7df55853c1e048a87e00aaf033c6", size = 522249, upload-time = "2026-11-05T09:15:26.774Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/6d/9f/e6673535165d965a11c1d5339f53995f90234019827eaecb41a57e6e463d/pydantic_core-2.50.4-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:7ac2769a7ddd661e07e26c12220adc31b17014c48234b2a5a88993b8d4109e09", size = 2241950, upload-time = "2026-11-05T09:13:53.055Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/02/65951efadbf7826e6a270843f97e3da2ef726de2b6d9cd7c5dfa4c48807e/pydantic_core-2.50.4-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:930ad463d3fb7a340d323bcc496ec32dae469ec9974c29ff569f19806b6b03b4", size = 2112412, upload-time = "2026-11-05T09:13:55.309Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/d3/4fb6e2e4a9e4951f1e05b198366ed5330e0874e83d52077e8b985350fe0e/pydantic_core-2.50.4-cp315-cp315-manylinux1_i686.manylinux_2_5_i686.whl", hash = "sha256:b867f9fd183d76977f810c626d539265183c25b2284762893a31e8d6cacc81c0", size = 2263220, upload-time = "2026-11-05T09:13:57.555Z" },
+ { url = "https://files.pythonhosted.org/packages/63/83/42e70436c523357b49393b73f161f2741acf36003c8efd2f941d2be56bae/pydantic_core-2.50.4-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:49eb1241a969a5994db6880c18c012b6cbe2340343d8f529721571806583e500", size = 2134716, upload-time = "2026-11-05T09:13:59.413Z" },
+ { url = "https://files.pythonhosted.org/packages/36/66/ed9b87f4751fd07e472c62cfe151f575abdf4c452fab943d35ba8efdc6c4/pydantic_core-2.50.4-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:477af785d1846e4e96bf56983b92ea7553aaeccdeb8acdaa55c7cee9eda0ae32", size = 2201901, upload-time = "2026-11-05T09:14:01.472Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/21/ff2e6ac7202b6d326e9f007c6082f6e6f3aaa063790ea2a355bdc985addc/pydantic_core-2.50.4-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:29358a8ca74e7d81158d14e2b10c904bd18773293ae3788716c0626fe0342347", size = 2389670, upload-time = "2026-11-05T09:14:03.431Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/34/e9348bc13d549641791a51843ed140fbb311d9066628e27477343412d5ad/pydantic_core-2.50.4-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:300753f71c73302e218124baf995f00058e085cd01869bd9610bcbd0bd4500c1", size = 2468951, upload-time = "2026-11-05T09:14:05.028Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/5e/145a02bf2b9f66981108caf6cc9e78917e31e4caca14d1e3ccd3ff2c7293/pydantic_core-2.50.4-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:26cb012c4b8ca9f847bd0ac15978bd3d0f55b050bfbe6c47355edfc7e945029b", size = 2272957, upload-time = "2026-11-05T09:14:06.587Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/38/b71c5b245e923f6cf144e948ed2a9d6b7fb97d8905784db8d860f5176b8b/pydantic_core-2.50.4-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:9be1cfe715ecaa060ffd5d599ef410b94ef2697a0eed4cee562f788a2aa2175b", size = 2275028, upload-time = "2026-11-05T09:14:08.219Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/71/2504dacdc5cd3ea3ec5e68c9b6e097c979500bcff220b97046cc8ea3c1ad/pydantic_core-2.50.4-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:c81e35862aced259177a537ce1a6264c3a97bae1ced184ca9dc10ea754563688", size = 2305447, upload-time = "2026-11-05T09:14:09.795Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/90/904233a6c148bcac9f30bb96a2761e5fb52682d0d6de31a40c74a3714ceb/pydantic_core-2.50.4-cp315-cp315-musllinux_1_1_armv7l.whl", hash = "sha256:b2f2d13239487cecaae98c9f4d5904ad0737643cdf8873c88ec6a78b12214f20", size = 2477852, upload-time = "2026-11-05T09:14:11.443Z" },
+ { url = "https://files.pythonhosted.org/packages/72/33/1cbdcbf343ca23b507e967b21dddc4c8bf17a74c8b5cad59228c85ba4efc/pydantic_core-2.50.4-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:e6b4b08d09054310a4dc389fa61ee22a7a5a292cb23ab8a985136ca8828bf924", size = 2506352, upload-time = "2026-11-05T09:14:13.165Z" },
+ { url = "https://files.pythonhosted.org/packages/91/24/3ad9ea0876e6624c90967e5f1446e751b275cfdfaaf798a0d8e0b30025bc/pydantic_core-2.50.4-cp315-cp315-win32.whl", hash = "sha256:6619d49a89a15cd04334f76fec530c155923bfc60da74ff5d815a52629e1aa6c", size = 2127780, upload-time = "2026-11-05T09:14:17.52Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/3c/0582532b90a12492471977bdbca1317eca5144f6342ea581d2cee73ccd35/pydantic_core-2.50.4-cp315-cp315-win_amd64.whl", hash = "sha256:e5bd109d095aaae989a0463d5ef0fcc43e9ddd5de125e656d3c119a79e466380", size = 2270596, upload-time = "2026-11-05T09:14:20.955Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/50/967cfe4f7043a993c0c80cd3563c598b5281d2276aa775d0f6ff7f714271/pydantic_core-2.50.4-cp315-cp315-win_arm64.whl", hash = "sha256:6118e5a5550e36582e19bac1a403711a748f9f18251b054c326d3db02aaa7a18", size = 2170880, upload-time = "2026-11-05T09:14:24.822Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/71/3ec5bd8a82f792482f16d79fdbe8ce85aa71bf7147e556c5fc5f8546818d/pydantic_core-2.50.4-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:d8201c9f9035520cdcc7dcad383b039ec8123969b87e01cf27c3dd883c02313b", size = 2233053, upload-time = "2026-11-05T09:14:27.942Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/2e/614dc37aedf8e9f811c1f2eedaf8eb09a7c196e2d137fe82bcc9d5662ca2/pydantic_core-2.50.4-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:1de7be9a66fc8536770481ab64a05aae5067c725f2b29a09543af00e6a417b97", size = 2090776, upload-time = "2026-11-05T09:14:30.281Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/14/aefc14e3e05013070ee959fc0b8703f6860a2776ed6e75b9c748f61aced0/pydantic_core-2.50.4-cp315-cp315t-manylinux1_i686.manylinux_2_5_i686.whl", hash = "sha256:789a6453e5ffd18a7ab6b8c3a0dd8403053829197ec258ecb90c604c21ffcc99", size = 2272481, upload-time = "2026-11-05T09:14:32.578Z" },
+ { url = "https://files.pythonhosted.org/packages/13/c9/d56999aa7236dc9303091db2b1ef7303f0b022baf25ebf33cd200c0cb933/pydantic_core-2.50.4-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6348ad12fa741bcc9c2f173e99fbf27cbc038826d8c31a4a7d836eca27878ba2", size = 2113069, upload-time = "2026-11-05T09:14:34.69Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/d5/a62da9e72f272f198f6118c5457332bbd4b9fdc4b77858448120569756de/pydantic_core-2.50.4-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:d49127f137f871cde3c5650af33bbbdcee02679dae76e020902fa106f522b8ae", size = 2202754, upload-time = "2026-11-05T09:14:36.49Z" },
+ { url = "https://files.pythonhosted.org/packages/37/4a/e7f21577f96e42ac3082cd014df398c1c913d482afac1c8ef17b9e039198/pydantic_core-2.50.4-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:2f5cc5bcb94063fbbdfd285a2b302da34af07491337c10bb35de1d5220fa1583", size = 2411472, upload-time = "2026-11-05T09:14:38.63Z" },
+ { url = "https://files.pythonhosted.org/packages/38/ad/c57c5e13bbfd3404ec1af519764aadbc52ee01e39f9f9ae9d28317f982f2/pydantic_core-2.50.4-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b99bed995ed7c41515c11fe2beb86f8a6cc298e5537cae2f736de2f982f74ef0", size = 2475330, upload-time = "2026-11-05T09:14:40.519Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/63/65cf0e1f1fd8cfaafee3c005712b8020e063b0f3e3571493ea68db5ec44f/pydantic_core-2.50.4-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:faa4e96385c820ce9d7f7a623486e23d4134ae3c9ba2071e44e0ab5a5a5002c4", size = 2265718, upload-time = "2026-11-05T09:14:42.379Z" },
+ { url = "https://files.pythonhosted.org/packages/72/5d/fd9fdec9b25e2ef1f788970a84f749f0e28f166573c87654fd5387d8e5ce/pydantic_core-2.50.4-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:d6a166978f22f10cb91413f70519861605219346decf65bc79008f9257c94afe", size = 2273454, upload-time = "2026-11-05T09:14:44.286Z" },
+ { url = "https://files.pythonhosted.org/packages/34/8c/19705c6d5eb72ff8c25006df8a375da7466176a5968781789e5415384f65/pydantic_core-2.50.4-cp315-cp315t-musllinux_1_1_aarch64.whl", hash = "sha256:a70d5effebd81ebd8e15a8aabdc7980832367b2b00c36543bac378977004cc80", size = 2314862, upload-time = "2026-11-05T09:14:45.97Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/fb/acefa27031aba2646b0d3026c4a1602e526489d39395ab17bc47f03234e3/pydantic_core-2.50.4-cp315-cp315t-musllinux_1_1_armv7l.whl", hash = "sha256:f8fd23653a252cefabea11cd041efd7b80bdf403e43a36dea53f9cb0aaa21f0f", size = 2479073, upload-time = "2026-11-05T09:14:47.526Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/e1/db08fa1e5edf1779fa0612f8d09f9030bdc513f593687c883cb781b86caa/pydantic_core-2.50.4-cp315-cp315t-musllinux_1_1_x86_64.whl", hash = "sha256:7646353a5a7c553c04167aca628e2550b538f492765b03ccf34321a560acc43c", size = 2521328, upload-time = "2026-11-05T09:14:49.042Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/51/bf1ec5b96c076d8d514b08f3bcffb3397df7a0fcc729eb960838a8bbe27f/pydantic_core-2.50.4-cp315-cp315t-win32.whl", hash = "sha256:65376dbe1ab4dbc768ca2b72b7c13993562c035f5925fd3bc7ff9c8c585c515d", size = 2107629, upload-time = "2026-11-05T09:14:50.891Z" },
+ { url = "https://files.pythonhosted.org/packages/18/32/43d5db1824ec2314f737ff8d9962ff59954a74616d8656a8cc78790e45f1/pydantic_core-2.50.4-cp315-cp315t-win_amd64.whl", hash = "sha256:f08ee8248aad2d054e5bc4c73554d4505464698f6d754f16198dbc5764e9821d", size = 2244906, upload-time = "2026-11-05T09:14:53.056Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/c0/38c505004969c4641c3a077a8e72e354f02d5c06d8f51d7a103699704772/pydantic_core-2.50.4-cp315-cp315t-win_arm64.whl", hash = "sha256:da779ae50b46f3f7d1e1b5f362fdc0ad376a49fd55f6f3122dc2b6149936cdf7", size = 2179018, upload-time = "2026-11-05T09:14:54.448Z" },
]
[[package]]
@@ -796,6 +804,7 @@ dependencies = [
{ name = "packaging" },
{ name = "pluggy" },
{ name = "pygments" },
+ { name = "tomli" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
wheels = [
@@ -813,11 +822,11 @@ wheels = [
[[package]]
name = "python-multipart"
-version = "0.0.32"
+version = "0.0.33"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/9f/b7/15b905f6142a9a8a68f66900788da1a32e4d4254452783b37ad1eccb0506/python_multipart-0.0.33.tar.gz", hash = "sha256:b8112424a6abd162ec08d390565dd113ad699f2fe6fef3c987b8bbcbfe9e390d", size = 50499, upload-time = "2026-10-31T14:23:47.461Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/1b/6b1b4164ec734cac911460ab599bfbe46afedd42c5c9627fe4235942c314/python_multipart-0.0.33-py3-none-any.whl", hash = "sha256:8f16c07b36add9da6b2ee87d3efcaf0a45a6de9c3d0a5dd94e9ccf7675a7bc45", size = 32253, upload-time = "2026-10-31T14:23:46.33Z" },
]
[[package]]
@@ -866,16 +875,16 @@ wheels = [
[[package]]
name = "research-repo-tools"
-version = "0.2.4"
+version = "0.2.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "packaging" },
{ name = "pyyaml" },
{ name = "rust-just" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/3a/e9/576fd80eb942edbdfe5d8be0e15cf0a6a6af3d30d4ad0000830d876740f5/research_repo_tools-0.2.4.tar.gz", hash = "sha256:30b6d4d2f8d0cb98d5311656928dd65800088b651dd6c0c29f9a02526e9e902a", size = 1042746, upload-time = "2026-10-28T20:12:31.688Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/cd/96/73185e1070954e856cc2ca3a99141bbc17e487950717fcff5859587c6f7b/research_repo_tools-0.2.6.tar.gz", hash = "sha256:dd826a916f037f47b925ca3268adc76d415a8561547bedda348a67f4f2e2e065", size = 1209707, upload-time = "2026-11-05T15:57:52.368Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/53/1d/8f7eed9320adca20787a4b10d323a72e1259a7718fc8fd53805c82e509c2/research_repo_tools-0.2.4-py3-none-any.whl", hash = "sha256:00aaab2b7efd680d79324c6debb64db4d8d1ff076a460ff46e24e0ef1c24bd7d", size = 431258, upload-time = "2026-10-28T20:12:29.604Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/8e/e712f3e57bc1141f2dd89624aeb106a99200c3bc7611c7ce1686eb0a5fb3/research_repo_tools-0.2.6-py3-none-any.whl", hash = "sha256:89ebae476b5bde0cffc321603ed1d867796e3276a7fb88c7dc1f7de53a13e7c2", size = 496754, upload-time = "2026-11-05T15:57:50.732Z" },
]
[package.optional-dependencies]
@@ -884,6 +893,10 @@ python-tools = [
{ name = "ruff" },
{ name = "ty" },
]
+semgrep = [
+ { name = "semgrep", version = "1.182.1", source = { registry = "https://pypi.org/simple" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" },
+ { name = "semgrep", version = "1.184.0", source = { registry = "https://pypi.org/simple" }, marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" },
+]
[[package]]
name = "rich"
@@ -1012,6 +1025,9 @@ wheels = [
name = "semgrep"
version = "1.182.1"
source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "platform_machine == 'x86_64' and sys_platform == 'darwin'",
+]
dependencies = [
{ name = "attrs" },
{ name = "boltons" },
@@ -1031,7 +1047,6 @@ dependencies = [
{ name = "packaging" },
{ name = "peewee" },
{ name = "pyjwt", extra = ["crypto"] },
- { name = "pywin32", marker = "sys_platform == 'win32'" },
{ name = "requests" },
{ name = "rich" },
{ name = "ruamel-yaml" },
@@ -1045,12 +1060,54 @@ dependencies = [
sdist = { url = "https://files.pythonhosted.org/packages/85/cc/105417f17abe0247fdc595be4690a3dc46ec816e9f8dd67a4a40e3dc7e79/semgrep-1.182.1.tar.gz", hash = "sha256:b072c59734384784a912b991c417b6c8e4771abdb3c39b9196fc01efdff8aa10", size = 522145, upload-time = "2026-10-22T02:11:13.063Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/37/7e/20dafdcb0a7d11d6a5017601a3737ea3b4bea5628711db7fddde3f5ff3de/semgrep-1.182.1-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-macosx_10_14_x86_64.whl", hash = "sha256:1db6d5becbb60d697a312fce6ec7e00cfde4eff1d221cce7ce85c49df55240eb", size = 46795686, upload-time = "2026-10-22T02:13:05.646Z" },
- { url = "https://files.pythonhosted.org/packages/ad/be/e93f291ccda669ae3be90e2716cd812b43d3fa1772f0c4a4f961abe6baad/semgrep-1.182.1-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-macosx_11_0_arm64.whl", hash = "sha256:89fe0afe240455800f806145f5679707892ea9bbd442a56eab1ea0b25a95f4c6", size = 50876869, upload-time = "2026-10-22T02:13:09.729Z" },
- { url = "https://files.pythonhosted.org/packages/f3/48/2a0fb9d839570da42817aca2dc913f5cb13a42c4259331870339ca82b212/semgrep-1.182.1-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-manylinux_2_34_aarch64.whl", hash = "sha256:3fdfbd68ac5eeead60983dbcd539428bbc40d01f805e0569301068d06be7eaa3", size = 73888910, upload-time = "2026-10-22T02:13:14.187Z" },
- { url = "https://files.pythonhosted.org/packages/92/11/1bfd4abd746509ad90726397dd8e0d9e1150378ac329821757ea56d4ea4c/semgrep-1.182.1-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-manylinux_2_34_x86_64.whl", hash = "sha256:bba25e5a38db5061d725c0920699ecee3e8137026427e722a271bd9bb8d25a40", size = 71829150, upload-time = "2026-10-22T02:13:19.709Z" },
- { url = "https://files.pythonhosted.org/packages/91/83/c479d634fe3e48d1620ca9599659b84f0048cd978fa330a8e1c5ae18241c/semgrep-1.182.1-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-musllinux_1_2_aarch64.whl", hash = "sha256:c38a3490fca93080e26e5f169e7e1fcb46742dd77e3d8e0fd9190653cb6c4ccd", size = 80976759, upload-time = "2026-10-22T02:13:25.094Z" },
- { url = "https://files.pythonhosted.org/packages/de/01/e66fc84a2a792f0715c17e0929faf4b6e7ad476418a934320631eb5cc27b/semgrep-1.182.1-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-musllinux_1_2_x86_64.whl", hash = "sha256:1e18f12a091cb7258f879c5fea1b47a10768af212f1f78ee0c16c52e00be32b7", size = 78547857, upload-time = "2026-10-22T02:13:31.602Z" },
- { url = "https://files.pythonhosted.org/packages/bc/2d/a161b139db9d28f94849696f3f68bc18e119b009d614b3b0df7d2748d953/semgrep-1.182.1-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-win_amd64.whl", hash = "sha256:e464d398107d026aa6e9c938fab2efabdf2ff8a9c7a52d7abeed0c149e62b68c", size = 59181689, upload-time = "2026-10-22T02:13:37.143Z" },
+]
+
+[[package]]
+name = "semgrep"
+version = "1.184.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "platform_machine != 'x86_64' or sys_platform != 'darwin'",
+]
+dependencies = [
+ { name = "attrs" },
+ { name = "boltons" },
+ { name = "certifi" },
+ { name = "click" },
+ { name = "click-option-group" },
+ { name = "colorama" },
+ { name = "exceptiongroup" },
+ { name = "glom" },
+ { name = "jsonschema" },
+ { name = "mcp" },
+ { name = "opentelemetry-api" },
+ { name = "opentelemetry-exporter-otlp-proto-http" },
+ { name = "opentelemetry-instrumentation-requests" },
+ { name = "opentelemetry-instrumentation-threading" },
+ { name = "opentelemetry-sdk" },
+ { name = "packaging" },
+ { name = "peewee" },
+ { name = "pyjwt", extra = ["crypto"] },
+ { name = "pywin32", marker = "sys_platform == 'win32'" },
+ { name = "requests" },
+ { name = "rich" },
+ { name = "ruamel-yaml" },
+ { name = "ruamel-yaml-clib" },
+ { name = "semantic-version" },
+ { name = "structlog" },
+ { name = "tomli" },
+ { name = "typing-extensions" },
+ { name = "urllib3" },
+ { name = "wcmatch" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0c/59/791da93d093ef5e6fe671c43c751986ba24cfcb2a5e91958e11ef4153f84/semgrep-1.184.0.tar.gz", hash = "sha256:0fc8a58e9a62de59ce644dde792dfc36764245d0139ab85632f66a8e0846a8c5", size = 542984, upload-time = "2026-11-05T19:17:59.533Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/59/f9/fb7fd6b74cf7bdf087b4c3ef7cfcc1627df99e46c2a6805baacb8245812a/semgrep-1.184.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-macosx_11_0_arm64.whl", hash = "sha256:0ce3064175a44236dd9fd35603627e3f56139ac61e13ca967bae2b76b2cec9bf", size = 51257278, upload-time = "2026-11-05T19:17:23.777Z" },
+ { url = "https://files.pythonhosted.org/packages/32/39/ac0756de46826d89e28fe06a7673a66b7f3203d2cb73740463aa113cfc98/semgrep-1.184.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-manylinux_2_34_aarch64.whl", hash = "sha256:f794d7c6d33126b6881f80d069583ef35b52bd796d882a5fa249ce1124aba3f3", size = 74301251, upload-time = "2026-11-05T19:17:29.544Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/32/d5f5f37bbfa8fee1527c749a7bfc5a1ab57f4316f98e91783721b340a35d/semgrep-1.184.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-manylinux_2_34_x86_64.whl", hash = "sha256:d847abb8fb6fdde77d9e30a6bc869dbfcb68eff4142dcd77897c52635a7600d9", size = 72254465, upload-time = "2026-11-05T19:17:35.941Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/9d/89e713d1faf9133274cc52b035986fa4dd46e36344c036a28c1fd33cf741/semgrep-1.184.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-musllinux_1_2_aarch64.whl", hash = "sha256:42a59e95433fd05a0908f8083e25656c90994c65549e483e0de8a1a0004a7adf", size = 81388020, upload-time = "2026-11-05T19:17:43.459Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/56/63cd45a37ec2c292e4fc6b548a54df2f10d22948cf01672f13f652707e3e/semgrep-1.184.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-musllinux_1_2_x86_64.whl", hash = "sha256:cf82d77eb911f921902f3e9d098fb97604c5419c0a87cf7de7ab2944b05e2ceb", size = 78963066, upload-time = "2026-11-05T19:17:50.279Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/9b/71a620f72bc6203286a4a6c031d53186863acf7d1af4f27c697ef176cbdf/semgrep-1.184.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-win_amd64.whl", hash = "sha256:3c86b2961b4663d1338fb965de038c7869571661971b78e18e9346ab57e1e20a", size = 59524393, upload-time = "2026-11-05T19:17:56.788Z" },
]
[[package]]
@@ -1104,12 +1161,39 @@ wheels = [
]
[[package]]
-name = "tomli"
-version = "2.4.1"
+name = "structlog"
+version = "26.1.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/5e/89/b4a0bcfdf4f71a3dea31379f095929613d7e4528a0996bca6aa964cd0dca/structlog-26.1.0.tar.gz", hash = "sha256:f63a716cbd1b1291cf7661de7794b455acfa4c43c5bcf1630e6ad5ddc1adb3b7", size = 1459881, upload-time = "2026-06-06T07:33:39.348Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/18/489c97b834dfff9cf2fc2507cede4bcd4b11e67f84bc462acd1992496f86/structlog-26.1.0-py3-none-any.whl", hash = "sha256:e081a26d6c373e6d201eca24eede26d8ffab07f88f477822e679183428d3d91e", size = 73764, upload-time = "2026-06-06T07:33:38.046Z" },
+]
+
+[[package]]
+name = "tomli"
+version = "2.5.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b0/78/9ad63712633ed3ab5cc1a648d863d7e7da371e9425e209555a0fe711b695/tomli-2.5.0.tar.gz", hash = "sha256:264507556cd8b8c8e7c6ee037cdf443a463f03f4c958e57195e3d369711b8ff6", size = 17662, upload-time = "2026-10-07T12:23:37.892Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/66/98/316fdc00f8c0939e6fe50461dd343c162d3ad51d1286eb25b7db54361d50/tomli-2.5.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:a525685c2f97da40762b8695eb7aa0af4c8344ca1905c73e4e29cb04d34607dc", size = 162775, upload-time = "2026-10-07T12:23:13.941Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/22/7b10fa5bb01c9539f53f69b619361b19350acc73657772ea7ac70ba309a8/tomli-2.5.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:9dbb18c1cfb2f6517942fc9314437f66aa06d94436ffb1f06102ef3572f35276", size = 161406, upload-time = "2026-10-07T12:23:15.215Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/e7/1a069d86dfd20f1f84f71c63faed9f83c1d890bc06c27d82dc7d888fb573/tomli-2.5.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:752e8b1aa6a4367ef8bf6a1a1e005540f7ed055ba36d7193796812ca5404eb52", size = 273855, upload-time = "2026-10-07T12:23:16.471Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/83/d1ef43d1687d092ab9c235455c76e6e709483b346b056f086095c7c263a5/tomli-2.5.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c47300f9bf791808f77d82747691c4bb09cb14bdf3060cca99b42cdc4361d5a7", size = 284910, upload-time = "2026-10-07T12:23:18.166Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/05/f4d9cf7de61822ece0c3873f30d291e324911c71a378b8bfe5ced13fd9f5/tomli-2.5.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:19b0dd8749f4ea2f112c5fcfb3c5248390c899d7e2e173f1d91abee1fa0ff391", size = 277723, upload-time = "2026-10-07T12:23:19.355Z" },
+ { url = "https://files.pythonhosted.org/packages/42/28/78262493141fa543151cf005760c3cb01d09fc28a11f993c05109902cb8c/tomli-2.5.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:57b1c3b01fab802e2899bc3d168dca320e14165e2fd9fd584760fb4ca5826859", size = 285115, upload-time = "2026-10-07T12:23:20.698Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/b9/e1dab9a30bcb677b5cc5cee810609cfd64f24306a3055767dd3fda00b1e0/tomli-2.5.0-cp315-cp315-win32.whl", hash = "sha256:667e521b37a6c5ccaa044202c235b530f90177ffe2cd4a64ecc213c7dd535feb", size = 103475, upload-time = "2026-10-07T12:23:21.941Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/bd/31a3790c11d6ea95fcf5e6022ac0f8d0543c9b61120b730fc481bd43d3b4/tomli-2.5.0-cp315-cp315-win_amd64.whl", hash = "sha256:d747252933c8a65ef6bd8da0fbb7ce28a90eb6119d8cd00772cd528aa07b68d5", size = 114589, upload-time = "2026-10-07T12:23:23.098Z" },
+ { url = "https://files.pythonhosted.org/packages/47/a2/4f6310fa699364f0e3af7ee3af88dddd9af066d33e716a0265bbe2b3ea84/tomli-2.5.0-cp315-cp315-win_arm64.whl", hash = "sha256:75dbcde8751b0a960aa3de173aa5e894d590755c6d7758b7e774c06f1dc3cbdd", size = 111493, upload-time = "2026-10-07T12:23:24.233Z" },
+ { url = "https://files.pythonhosted.org/packages/68/14/00853f0b396d8971107ae1921bb5b322fdee1650d2f16bf06c20adb532e5/tomli-2.5.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:2419c2a189551987b59d80e63ec355671283336f41c6b9b89462df679c7d0c57", size = 171380, upload-time = "2026-10-07T12:23:25.512Z" },
+ { url = "https://files.pythonhosted.org/packages/89/ad/fa6949321dadee46b27363974fb197b94c911c3b0f7a5fd26d7dc18fc2a0/tomli-2.5.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:0dc598040da8d42cf20f0be588ed7004f46db12a0ac6c32e03a59dccedaaadcd", size = 170553, upload-time = "2026-10-07T12:23:26.855Z" },
+ { url = "https://files.pythonhosted.org/packages/53/aa/3056c919eb3e084df3752b2cf5f865dcc04af0b27dba2f66d7b28af4633a/tomli-2.5.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49096930c8d886c9bbdab62d2d0d17ce823ddeea522309a190b36245d5b49e01", size = 294428, upload-time = "2026-10-07T12:23:28.132Z" },
+ { url = "https://files.pythonhosted.org/packages/96/b2/faeeb5d8769ea3832021d73e892c8391eae7b4b4f8b55a789127bd8b18a9/tomli-2.5.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8ade5023067f99fe72b88accd30d0ea05a158e9e32a11f124e731ea9695313f", size = 304909, upload-time = "2026-10-07T12:23:29.381Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/52/f094c09e73fb654b621716d019acb5d29bdfd1be01df80c281d552bda48d/tomli-2.5.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:b69564772b5c8f22ea5f498dff08cfa825045b4d4c4400529000bdf818aa3b2a", size = 293220, upload-time = "2026-10-07T12:23:30.608Z" },
+ { url = "https://files.pythonhosted.org/packages/86/f5/0c30541078ca4b505ce3bd76ed931facbfec524dd018535d691d1af0a6d2/tomli-2.5.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:8ff3a2ca028c7eee0c777f9a092038d0a594a9fa04e215f929a22c329e2cb142", size = 305705, upload-time = "2026-10-07T12:23:32.181Z" },
+ { url = "https://files.pythonhosted.org/packages/05/74/590e7d19d6a118fc5cc5704ff358e21d95b8573f6b9443b1519f29ca8825/tomli-2.5.0-cp315-cp315t-win32.whl", hash = "sha256:62fc1bc8eb03e3a9cadfca713d65614ed8e09d974a283295ffe3a831976b4dc5", size = 108432, upload-time = "2026-10-07T12:23:33.496Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/b8/63a75cfb27a17c38550e44025d3a6e7be64516fd8608a3b75703bf37d81b/tomli-2.5.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f3fcbc57b1791fa6cbe5d8434179d51de12be1a4811469529f47f6e7487a2571", size = 117281, upload-time = "2026-10-07T12:23:34.648Z" },
+ { url = "https://files.pythonhosted.org/packages/72/01/e8c1debb2173973372934c68fc8e46170ab60ef23ed4592dff4dec6e8993/tomli-2.5.0-cp315-cp315t-win_arm64.whl", hash = "sha256:d2ba24db8a9376921b5e87b4762b9adb0f3f1deaea68f2b8b0bb2c11efb9c3e7", size = 115069, upload-time = "2026-10-07T12:23:35.77Z" },
+ { url = "https://files.pythonhosted.org/packages/60/3f/3e3f8fd0919249b0200c80fbc4f9a1e70be19f9883da71dfb7f8b9ab8aca/tomli-2.5.0-py3-none-any.whl", hash = "sha256:32a7b79ac57a2e83670ce329ccf675798bc5a2094783a63676866b70503f2e2b", size = 14765, upload-time = "2026-10-07T12:23:36.875Z" },
]
[[package]]
@@ -1148,14 +1232,14 @@ wheels = [
[[package]]
name = "typing-inspection"
-version = "0.4.4"
+version = "0.4.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/ff/50/0195198eb1d75bab716bb0a813abea4cc394bfa17a92a189f021a34859af/typing_inspection-0.4.5.tar.gz", hash = "sha256:7f16c411e7930dc79596d42ab44672a3fe5f60eb34b781a1647e7872363120c7", size = 81194, upload-time = "2026-11-04T14:13:23.774Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" },
+ { url = "https://files.pythonhosted.org/packages/de/0c/ea2544850a7c135e15033a5c7a8e7211136184b7ae63e2239d557c2b3019/typing_inspection-0.4.5-py3-none-any.whl", hash = "sha256:bc210535442b39e1bf2805e0c7a17dfd6a4ace760c84c7f7d16245d23a6b1c39", size = 14874, upload-time = "2026-11-04T14:13:22.616Z" },
]
[[package]]
@@ -1194,33 +1278,33 @@ wheels = [
[[package]]
name = "wrapt"
-version = "2.5.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/04/22/89e2f3bdae5cb34e0cab0cd86d7172dbf418de4b46c9b17b9c7a560dfa44/wrapt-2.5.1.tar.gz", hash = "sha256:f595bb0185aab3e9dc31950c95d914f56ea8278810c3b928f3426e12ed6d27bc", size = 184455, upload-time = "2026-10-14T00:39:39.24Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/90/2b/eec5745baaad284fa19232f47796914134e1ea2dd21e289b012aa7981323/wrapt-2.5.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:a18e63910252eb75d8806b4baefbc3a03612502f63eab042e3741b00b719f043", size = 106441, upload-time = "2026-10-14T00:38:32.504Z" },
- { url = "https://files.pythonhosted.org/packages/29/f3/976b0f014a08654289358d41a799c2d24642151b091cfe18a8b91766ee28/wrapt-2.5.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:183bf0bb893f783c9d22f953cb01fababb9f618e098763f8e66337b575b0647a", size = 106411, upload-time = "2026-10-14T00:38:34.338Z" },
- { url = "https://files.pythonhosted.org/packages/c7/f4/4b94583d9bec0ff0573a5f10fab295675e3b9a64b701c4609b1a1982c390/wrapt-2.5.1-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a1e823aecb3746b8f9e0aee2e1413887871ee2f5c502a3e0ef8d466dbd4adde1", size = 252017, upload-time = "2026-10-14T00:38:36.142Z" },
- { url = "https://files.pythonhosted.org/packages/5e/3c/4f9ba033343b2935a453188f97866f0bd4f7748748ab307aaefb18be3a0a/wrapt-2.5.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bde5d1b37101b1e9dd3da1f35072e2e7028e9c5e3511f7d76d3fdd4d071b7663", size = 251030, upload-time = "2026-10-14T00:38:38.003Z" },
- { url = "https://files.pythonhosted.org/packages/69/a1/704c761913be404ed893d05702eeda5ff96c5d8448271c28b80101202bcb/wrapt-2.5.1-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:12d3d2b9d6553df6e2421ab99e1cc5413509076788f57fcb3169f5ce100a19d1", size = 232875, upload-time = "2026-10-14T00:38:40.124Z" },
- { url = "https://files.pythonhosted.org/packages/f0/3a/779ca20fb8c70238069efd0a2b60ea3da450e57c4f103748db0247f52f3a/wrapt-2.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:521bd5ef2a33171fac08a0a302d51a983c19c3519406c1ee8da7ce29285488da", size = 248302, upload-time = "2026-10-14T00:38:42.138Z" },
- { url = "https://files.pythonhosted.org/packages/03/02/80e13786204ce8e1002edb66d06a3194906c0dbbc038bce7bbda426d0f2b/wrapt-2.5.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:129cab3c7b21e68e693c2819a95c47f3b1c41a834b931154688c83b6aef6bdab", size = 229839, upload-time = "2026-10-14T00:38:44.039Z" },
- { url = "https://files.pythonhosted.org/packages/9d/d1/14c0d041375ae5d0a12445b5c5bc61b109df954cd5bfb852736cd4281cbd/wrapt-2.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:8a7c078323e6e1534968cb85488c5eb7ee2b9bbd0f8a291095213a763da40dab", size = 239634, upload-time = "2026-10-14T00:38:45.896Z" },
- { url = "https://files.pythonhosted.org/packages/97/6e/dacc92526fbed1013eb903406ce961da6ff2a1e66b7349a253f439b979aa/wrapt-2.5.1-cp315-cp315-win32.whl", hash = "sha256:736c1de0230c6d24327b14684794214167b2c5ebb6332e28a10f504641b600df", size = 100931, upload-time = "2026-10-14T00:38:47.718Z" },
- { url = "https://files.pythonhosted.org/packages/e8/e4/84bbd88554052958ecbbb481a75559b1e40753aba52ec86e3e1efb50ccf0/wrapt-2.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:69fd0fbb3daf7c8c6f5e062847a0061f880f347374d74cf1daba57220fb64cd0", size = 106252, upload-time = "2026-10-14T00:38:49.511Z" },
- { url = "https://files.pythonhosted.org/packages/b7/98/98d4c4524e8af70ccf35b66864be29ea9d232e5a918efc1dbcf5c87a039d/wrapt-2.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:051220e5071fdfb1a6678707c8abb7bbf4824d40f99758394b2b4d64855fb284", size = 103980, upload-time = "2026-10-14T00:38:51.384Z" },
- { url = "https://files.pythonhosted.org/packages/75/d9/4b242519d6d29eabb73cb9e50e645e014eb2c13f022601151953b3e81946/wrapt-2.5.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:711e73da3d7983547fc9dd208973b6b0c52640822f5d477910ba24622df6ba64", size = 109538, upload-time = "2026-10-14T00:38:53.124Z" },
- { url = "https://files.pythonhosted.org/packages/58/05/e434f56fcceaafb251cc56c03107ae278e99158466b49f4146f7b33a2532/wrapt-2.5.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:5be9816d9de88f02fce23cf55f392403411d9bd9c7ae57fdc965a43b22e2de5e", size = 109874, upload-time = "2026-10-14T00:38:54.941Z" },
- { url = "https://files.pythonhosted.org/packages/23/09/d2c0b34d02804018225279a157307b873c8d0f8452790efdd7f0004387a8/wrapt-2.5.1-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b3f410c416752e1dba53d361e2e6562f22c2c3ec855740dfa5836e061b22571", size = 284866, upload-time = "2026-10-14T00:38:57.081Z" },
- { url = "https://files.pythonhosted.org/packages/71/6f/2b56319c0565d9a6b63eeba2f11f3e699dc324aaa460f1aae079bd4507b0/wrapt-2.5.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:094b847491b813b6e6c1775e03770930d75078c0821adf929ac712830951ef25", size = 295154, upload-time = "2026-10-14T00:38:59.007Z" },
- { url = "https://files.pythonhosted.org/packages/3c/1b/9ac4238a1a839457d6b687b9e9c35d57ba1b2050a42d6bf8c93176bc1bdf/wrapt-2.5.1-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:26d8ea2ec6818aeb656bd8a9e745a6f1fb0edfcd8f54291ccd94f62eb5f5e3bd", size = 264914, upload-time = "2026-10-14T00:39:01.154Z" },
- { url = "https://files.pythonhosted.org/packages/4f/95/9faed8e5f6e5431edd36b2cfb4f305df520c197ba3639e1c78d11c70a068/wrapt-2.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:0a526227efe17dd94bd16b123d170f879bce42c15f10eb92495a745f54caa943", size = 290238, upload-time = "2026-10-14T00:39:03.052Z" },
- { url = "https://files.pythonhosted.org/packages/b5/52/cae26590ef8ee46b55aa8b211c507f6e5ec0fbd241a6730bf7da024b5dab/wrapt-2.5.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:36d7d0ad593c4f1a651e4032de834db59aee1a929ee396cd483895b673328e51", size = 262208, upload-time = "2026-10-14T00:39:05.008Z" },
- { url = "https://files.pythonhosted.org/packages/00/f3/34e5008307be4169592e99de52946cd8800b98097e2a145da11484c7b3e4/wrapt-2.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:89d9a8607b7028054bb6fd01d437f205534a5d59d53c3665d15949a99a2fce0d", size = 280005, upload-time = "2026-10-14T00:39:07.048Z" },
- { url = "https://files.pythonhosted.org/packages/f3/f9/64e000aa84a88c52a481c7c8b011c80a8ae60bd5f68598e567a918a624a0/wrapt-2.5.1-cp315-cp315t-win32.whl", hash = "sha256:ad81bf81b0a0b6c6ec74169638202851962843e86749570c463eecc55072f93b", size = 103378, upload-time = "2026-10-14T00:39:09.336Z" },
- { url = "https://files.pythonhosted.org/packages/2f/e4/69efa7c6e8535c5188e041ac278079949fb2daaa97e6f08beb91cf31b3d1/wrapt-2.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:d5b665a43fe0d3b390cbdd3c003d61c92fa07bd5e3fb1ed3f47920c2d03cd9fd", size = 109928, upload-time = "2026-10-14T00:39:11.156Z" },
- { url = "https://files.pythonhosted.org/packages/13/77/6e414b3388b9f1ecb76107ef4a2aae501f1bdfcab85c8e34ef78f7db22db/wrapt-2.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:6405ff2160af9d59132ebb076eda0304db44d9d09809582932412ef7c0788a36", size = 106226, upload-time = "2026-10-14T00:39:13.061Z" },
- { url = "https://files.pythonhosted.org/packages/bc/0c/7da7513ddcc8f1d831ec4bfbedc9f7f174ecb91042bc16916fc1e0d06b22/wrapt-2.5.1-py3-none-any.whl", hash = "sha256:c6e6c226b1ca5402d7ae5fb34a0d21f1b49124fe4200e5884d1e19e53c47ac1d", size = 81849, upload-time = "2026-10-14T00:39:37.441Z" },
+version = "2.5.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/9c/b0/f752d19e1e83ab8bd64aeccfde32ff086aee3ef08520f7340b69a8d0a4d2/wrapt-2.5.2.tar.gz", hash = "sha256:551cb49e88b3722faad780303833bc4fa70685fb1773270fb98b40d39d9fddbf", size = 186066, upload-time = "2026-10-29T23:06:39.868Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/67/8c/3b2b95cd60b78f9450d748854e421fcf48bc350d76ca9e0fe048b87ad796/wrapt-2.5.2-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:2c17ec4310cc06f864799a4c37c1c716d5dcf05ceead2e63ca992a9466903b73", size = 106621, upload-time = "2026-10-29T23:05:49.433Z" },
+ { url = "https://files.pythonhosted.org/packages/16/12/c430cd1289711cdf2b6b1c469d5efa527f4815fabab32bff3249545344da/wrapt-2.5.2-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:430070776fb30d925a7a1e13d68086e4731b63eedf4c43ee62fc4c8108920cce", size = 106590, upload-time = "2026-10-29T23:05:50.967Z" },
+ { url = "https://files.pythonhosted.org/packages/69/4b/d7230c57ca30023a5630bf5cc0e092ad1cfce4d999a309b8377a36b3c249/wrapt-2.5.2-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:99978d196df45c115273c6119e4d0dacbc67bc267a196506625bf7084b741f0b", size = 252192, upload-time = "2026-10-29T23:05:52.344Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/74/6dc841c96b9906e0c466aaeed7f51162129cc8745a91bb28168aa3b45194/wrapt-2.5.2-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:217798fb0cfb31479a264f841b0242bc1bed1ffc70d233e8444a876cec57928c", size = 251207, upload-time = "2026-10-29T23:05:54.011Z" },
+ { url = "https://files.pythonhosted.org/packages/52/bb/25e710cc9d3bd25d81d46791dd25029d6f9b9d44b990e3d81755b549ff71/wrapt-2.5.2-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:62b339565cfdfcc0ba71c527b4ad13ac9de25504d474fa4ae5db95d1c7e4c6a2", size = 233048, upload-time = "2026-10-29T23:05:55.796Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/80/672640d799862a36abae740e778146dbe155ec8ffd6211b3a99240f599ca/wrapt-2.5.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:84619ca6b6f67a7dd53754ad402cafceb4ddc9d8981f144f62718b783f05786e", size = 248479, upload-time = "2026-10-29T23:05:57.346Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/34/91eb6724eb7cf64b77637d720ef71cad1ec6e4ce2d15eb548670c47078fa/wrapt-2.5.2-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:08d1d7bcb7dd277b7b976fd914455c3c1088f5c101ac7469ae5a5b8c03be0a0a", size = 230013, upload-time = "2026-10-29T23:05:58.745Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/f8/f1fc0c3394819c9a6a3c0d0055b06614a3bef7ad133cf18f9c09a2b2141f/wrapt-2.5.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:4e86f3543c67d72b4d4c08c3e351523c2d69cd05932df65937d79bc472e9d62f", size = 239808, upload-time = "2026-10-29T23:06:00.266Z" },
+ { url = "https://files.pythonhosted.org/packages/56/d1/70f9e71674d7b6c21c8e8cafc3016f773dc3acb31a131f23ab4af8c1effb/wrapt-2.5.2-cp315-cp315-win32.whl", hash = "sha256:035cd71e2aadfc0ad366ab4785b49e62093370885ea4b527b61b308b7a92ff14", size = 101099, upload-time = "2026-10-29T23:06:01.777Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/e6/fdea9e35605a6e7fc71556068ea184fb3e9cc09d1e3be8ca8f26d16339c9/wrapt-2.5.2-cp315-cp315-win_amd64.whl", hash = "sha256:b9f41029f9bc6ba80755b9c07f5e77e9e7aafb71c625b9f67614e4ebfb24302a", size = 106423, upload-time = "2026-10-29T23:06:03.076Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/50/8c41d8ba868cde9afd5b68e18f19b94300d091c409ef081b80c2aecda653/wrapt-2.5.2-cp315-cp315-win_arm64.whl", hash = "sha256:ae84013e89ec1874415549a46cfea373e28ddecbb72e1f5456cddfa2b76a28e6", size = 104152, upload-time = "2026-10-29T23:06:04.356Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/e7/b43d341b036e2862550eb7fffe2b4130015c39c861f30eb34014a2f032d2/wrapt-2.5.2-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:b680bef5200899524667f813fa34dcfde9313fef9f9fa432985d4bc7b4195ba3", size = 109711, upload-time = "2026-10-29T23:06:05.768Z" },
+ { url = "https://files.pythonhosted.org/packages/be/52/8012aaf046264e27969f79bc94cd2afb4729338f5434b90edd3d4bb60b8f/wrapt-2.5.2-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:da51f5ac290f2e024ee842799c6592f68cd880c20f6b3a54305d1b6aeab459a4", size = 110047, upload-time = "2026-10-29T23:06:07.309Z" },
+ { url = "https://files.pythonhosted.org/packages/54/a7/628c315d19c6027e8b0e700c0dd7d5c3fd662329c7aa1294315064b87cdd/wrapt-2.5.2-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3ebc07822649eda442382c1d20983bc6ec5aa2bf79a600da11b29ecb62a0fb2", size = 285041, upload-time = "2026-10-29T23:06:08.777Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/fd/4b418c9655bacb5ce8b0aed894f8355e12940bde74237ec45d9396e1f87f/wrapt-2.5.2-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bbb6e959c1e2c32bbad291f83af6320c8c46bb64e256801e85f55d88a09522d5", size = 295329, upload-time = "2026-10-29T23:06:10.489Z" },
+ { url = "https://files.pythonhosted.org/packages/92/24/dbe9776fded6dd5f2bfa1446ec04a33afe5afac1d5fb89cd7f5e96fbc61d/wrapt-2.5.2-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a0541e62f7b9c0d42130e3469d6ac3b1767f019314ae795646a2850a75c64412", size = 265088, upload-time = "2026-10-29T23:06:11.963Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/ee/4103576d31bed902224fed8b1d06b94313491504d1d644e42bceba4de880/wrapt-2.5.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:18819c05409a161999be68da8e451636470c35f77e25c8201825a33a7d88c89c", size = 290413, upload-time = "2026-10-29T23:06:13.536Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/7c/944e95c25232a5805ea9f25cf2ce90386262db025ba04fbb61f451d9523f/wrapt-2.5.2-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:2594e0be4ee09b2f19828265a39df90e591def44215fe8c5b8d6b83aa4cc18b5", size = 262383, upload-time = "2026-10-29T23:06:14.887Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/b3/a4b66497e3bb63d2e94ea1605f4beaf5b6b5dafbb6ce41abd727a6fcb7b5/wrapt-2.5.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:32c0387872e67b9dfdd2b599bf31fe127e597ed8526c5f192cdaca57a491e9dc", size = 280180, upload-time = "2026-10-29T23:06:16.314Z" },
+ { url = "https://files.pythonhosted.org/packages/91/bd/28d046dccfcade6ad62ad19f2f805e5b2e3f3463431307c3b8bd54e24e7f/wrapt-2.5.2-cp315-cp315t-win32.whl", hash = "sha256:9cb33f293e3aa04b36f7d165dc05b42db85e0a88e20a8fd81f60426d1f17d535", size = 103551, upload-time = "2026-10-29T23:06:17.732Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/d9/aef3035a3e3d918eb51e7db454af9e06919f0c5208fa0a9315548fef7734/wrapt-2.5.2-cp315-cp315t-win_amd64.whl", hash = "sha256:7f89423965b23aa62a90bf21c20353056a829ca9a60c01fde9aec9876c99392e", size = 110099, upload-time = "2026-10-29T23:06:19.293Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/ea/56eddba6249fae5361b53eb62e2e25ad86cabec4632d943d356aba8dea1b/wrapt-2.5.2-cp315-cp315t-win_arm64.whl", hash = "sha256:fa642d3781c061579cb138773add4a55b16f99de9ebc41603e89a756cf9e62c6", size = 106397, upload-time = "2026-10-29T23:06:20.826Z" },
+ { url = "https://files.pythonhosted.org/packages/77/8b/1ea57b936b70f98e8e7620a352578096d778dbee95b262f73c066d210ffb/wrapt-2.5.2-py3-none-any.whl", hash = "sha256:61eb54e9540ea522c2c4752a57549a83d3c67e6704ed708403d28e067aaf90f0", size = 82025, upload-time = "2026-10-29T23:06:38.56Z" },
]
[[package]]