diff --git a/.gitignore b/.gitignore index 27b0842..55ae1b6 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,16 @@ dist/ samples/agents-md-readiness/*.json samples/agents-md-readiness/data/jev-samples-agents-md.json +# pr-triage writes a dataset and a triage report per selector, and the repo keeps +# the two reports the sample's README quotes. The datasets stay out: one mirrors +# 26 contributors' diffs at a couple of megabytes, and one command rebuilds it +# (`uv run fetch_prs.py owner/repo --all`). Other reports stay out too, because a +# --since window names a date that is stale the next day. Naming the two keeps the +# invariant visible: these are the runs behind the README's numbers. +samples/pr-triage/data/*.json +!samples/pr-triage/data/agentic-community-mcp-gateway-registry-open-all-triage.json +!samples/pr-triage/data/apache-airflow-open-latest-18-triage.json + # Compiled binaries from samples/agents-md-readiness/go/. build.sh writes dist/, # `go build -o agents-md-readiness .` writes that name beside the source, and a # plain `go build` names its output after the directory, which is `go`. A release diff --git a/AGENTS.md b/AGENTS.md index 886e698..b4c5641 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,6 +22,10 @@ jev-samples/ readme_check.py the sample agents-md-readiness/ the second sample go/ a Go port of that sample, one static binary for CI + pr-triage/ the third sample, one Jev call per pull request + fetch_prs.py builds the dataset from the GitHub API, needs no Jev key + pr_triage.py the Jev call, the tiers, the tables + data/ triage reports, committed; the datasets they scored, gitignored ``` ## Setup and commands @@ -48,7 +52,7 @@ One check runs in CI: `.github/workflows/agents-md-readiness.yml` scores this fi 1. `uv run python -m py_compile ` on every Python file you touched. 2. `uvx ruff check .` and `uvx ruff format --check .` both report clean. -3. The sample runs against a live key, and every code path you changed runs at least once. For readme-check that means a local file, a GitHub repo root, a `/blob/` file page, and an `http://` URL it should refuse. For agents-md-readiness add a repo holding neither AGENTS.md nor CLAUDE.md, and a directory with neither. +3. The sample runs against a live key, and every code path you changed runs at least once. For readme-check that means a local file, a GitHub repo root, a `/blob/` file page, and an `http://` URL it should refuse. For agents-md-readiness add a repo holding neither AGENTS.md nor CLAUDE.md, and a directory with neither. For pr-triage that means a live fetch, a `--dataset` replay, `--explain` on a number in the dataset and on one that is absent, and a repo whose pull requests include a diff too large to send whole. 4. Any output shown in a README comes from a run you just did, with the date next to it. 5. `samples/agents-md-readiness/go/` compiles that sample into one static binary, so a change to its `questions.yml` or its Go files means `gofmt -l .`, `go vet ./...`, `go test ./...` and `./build.sh ` in that folder, then one live run of the binary you built. The Python stays canonical, and `payload_test.go` fails when the embedded copy of the payload drifts. @@ -92,7 +96,7 @@ These are the habits the samples exist to teach, so breaking one in a sample tea 1. Create `samples//` with kebab-case naming. 2. Give it its own `pyproject.toml`, depending on `typesafe-sdk` and `pyyaml`. -3. Put the whole Jev payload in `questions.yml`: `model`, `max_state_chars`, then `questions`, each entry carrying `type`, `label`, `instructions` and any `criteria`. Question order in the file is print order. +3. Put the whole Jev payload in `questions.yml`: `model`, the state budget, then `questions`, each entry carrying `type`, `label`, `instructions` and any `criteria`. Question order in the file is print order. One state field takes one `max_state_chars`; a state with several fields takes one budget per field, as `pr-triage` does, so a long diff cannot crowd out a description. 4. Write the sample as a single module where possible. Reach for a second file only when one stops being readable. 5. Write the sample README to answer three questions in this order: what it does, how to run it, what to notice. 6. Add a row to the table in the root README. diff --git a/README.md b/README.md index eb49570..cb24794 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Every sample is a small self-contained project under [samples/](samples/), and y | --- | --- | | [readme-check](samples/readme-check/) | Reads a README from disk or a GitHub URL, asks five questions about it in one call, and explains every number it prints. Uses all three primitives: `Choice`, `Score` and `Noul`. | | [agents-md-readiness](samples/agents-md-readiness/) | Scores an AGENTS.md or CLAUDE.md against the [agents.md](https://agents.md) format: seventeen questions in one call, a padded markdown table, one weighted readiness score, and a JSON report per document. Ships scored runs for eleven open-source repos in [data/](samples/agents-md-readiness/data/), plus a [Go port](samples/agents-md-readiness/go/) that compiles the same check into one static binary for a CI runner without Python. | +| [pr-triage](samples/pr-triage/) | Sorts a repository's open pull requests into four review tiers, one call each: eleven questions about the title, the description, the file list and the diff, nine of them weighted into a review load, and a size floor in Python for the diffs too large to send whole. Builds its dataset from the GitHub API, prints a triage table grouped by tier with the advice for each one, and ships the scored runs for two repositories in [data/](samples/pr-triage/data/). Twenty-six pull requests took four seconds and $0.0077. | Each sample keeps its Jev payload in `questions.yml`: the model pin, the state budget, every question with the label it prints under, and what each answer is worth toward the score. The Python reads that file and does the arithmetic, so changing a question or a weight is a data change and changing a threshold is a code change. @@ -144,8 +145,14 @@ uv run readme_check.py https://github.com/psf/requests cd ../agents-md-readiness uv run agents_md_readiness.py --help uv run agents_md_readiness.py https://github.com/apache/airflow + +cd ../pr-triage +uv run pr_triage.py --help +uv run pr_triage.py apache/airflow --limit 10 ``` +`pr-triage` reads the GitHub API as well, so it wants `GITHUB_TOKEN`, `GH_TOKEN`, or a logged-in `gh` CLI for the fetch. + ## The three primitives | Type | Asks | Returns | diff --git a/samples/pr-triage/README.md b/samples/pr-triage/README.md new file mode 100644 index 0000000..a967398 --- /dev/null +++ b/samples/pr-triage/README.md @@ -0,0 +1,168 @@ +# pr-triage + +Sort a repository's open pull requests into four review tiers, one Jev call each. + +A maintainer with twenty-six open pull requests wants to know which ones a glance clears and which ones need an hour. The sample sends Jev the title, the description, the file list and as much of the diff as fits, then asks eleven questions about each pull request in one call. Nine of the answers carry a weight, and their average is a review load from 0 to 1. The load picks a tier, Python raises the tier when the change is too big to take Jev's word for it, and each tier carries the advice that goes with it. + +Twenty-six pull requests cost $0.0077 and four seconds. + +The sample prints the triage and writes a report. Merging stays with you. + +## What it asks + +Eleven questions go to Jev in one call: how far the change reaches, whether it touches auth or secrets, how many judgment calls a reviewer has to agree with, and eight more. Nine of them carry a weight in [questions.yml](questions.yml), and their weighted average is the load. The table after the diagram names all eleven. + +```mermaid +flowchart TD + PR["one pull request
title, description, file list, as much of the diff as fits"] + CALL["one Jev call, eleven questions"] + NINE["nine scored criteria
how far the change reaches, whether it touches auth or secrets,
how many judgment calls it holds, and six more"] + TWO["two labels, printed beside the tier and never scored
what kind of change: bugfix, feature, refactor, and four more
what to check first: security, correctness, deployment, and three more"] + LOAD["load = the weighted average of those nine
0.00 to 1.00"] + BAND{"which band does
the load land in?"} + HIGH["high"] + MED["medium"] + LOW["low"] + TRIV["trivial"] + SIZE["the file and line counts can raise the tier
over 30 files or 1,500 lines lands in high, whatever Jev said
this only ever raises a tier, never lowers one"] + OUT["one line per pull request
the tier, its advice, and the questions that drove the load"] + + PR --> CALL + CALL --> NINE + CALL --> TWO + NINE --> LOAD + LOAD --> BAND + BAND -->|0.62 and up| HIGH + BAND -->|0.42 to 0.62| MED + BAND -->|0.22 to 0.42| LOW + BAND -->|under 0.22| TRIV + HIGH --> SIZE + MED --> SIZE + LOW --> SIZE + TRIV --> SIZE + SIZE --> OUT + TWO --> OUT +``` + +| Question | Type | Weight | What a high answer means | +| --- | --- | --- | --- | +| `change_kind` | Choice | | docs, tests, dependency, config, bugfix, feature or refactor | +| `review_focus` | Choice | | where a reviewer should start | +| `blast_radius` | Score | 0.15 | the change reaches shared code paths | +| `design_decisions` | Score | 0.14 | a reviewer has judgment calls to agree with | +| `security_surface` | Noul | 0.15 | it touches auth, secrets, tokens or input validation | +| `mechanical` | Noul (inverted) | 0.12 | one edit repeated, so checking one instance checks them all | +| `breaking_change` | Noul | 0.11 | an existing caller stops working | +| `scope_creep` | Noul | 0.09 | the diff does things the description never mentions | +| `infra_surface` | Noul | 0.08 | it touches deployment, images or CI | +| `has_tests` | Noul (inverted) | 0.08 | the change comes with tests that exercise it | +| `description_quality` | Score (inverted) | 0.08 | the author explained what, why and how they checked | + +`questions.yml` inverts three of them, so a mechanical diff, a covered change and a thorough description each lower the load. + +## Running it + +```bash +cd samples/pr-triage + +# The 10 most recent open pull requests, fetched and triaged in one command +uv run pr_triage.py agentic-community/mcp-gateway-registry + +# Every open one, or everything opened in the last 30 days +uv run pr_triage.py owner/repo --all +uv run pr_triage.py owner/repo --since 30 +uv run pr_triage.py owner/repo --since 2026-08-01 + +# Build a dataset once, then triage it as often as you like with no GitHub calls +uv run fetch_prs.py owner/repo --all +uv run pr_triage.py --dataset data/owner-repo-open-all.json + +# Every question for one pull request, and what each one contributed +uv run pr_triage.py --dataset data/owner-repo-open-all.json --explain 1693 +``` + +`fetch_prs.py` needs a GitHub token and no Jev key. It reads `GITHUB_TOKEN` or `GH_TOKEN`, and falls back to `gh auth token`. Unauthenticated callers get sixty requests an hour and one pull request costs two of them, so twenty-six pull requests want a token. + +`pr_triage.py` needs a Jev key in `TYPESAFE_API_KEY`, from the environment or from `.env` beside the sample or at the repo root. + +## What it prints + +From a run on 25 September 2026 against the twenty-six open pull requests of [agentic-community/mcp-gateway-registry](https://github.com/agentic-community/mcp-gateway-registry), trimmed to the ends of the table: + +``` +| PR | Files | Lines | Kind | Load | Tier | Review focus | Title | +| ----- | ----- | ---------- | ------- | ---- | ----------------- | ------------ | ---------------------------------------------------------- | +| #610 | 19 | +533/-74 | feature | 0.82 | high | security | ALB-direct deployment mode - Public Endpoint with IP fi... | +| #1671 | 12 | +791/-71 | feature | 0.75 | high | deployment | feat(charts): keycloak official image | +| #1693 | 51 | +3052/-626 | feature | 0.75 | high | security | feat(security): Asymmetric (ES256) internal JWT signing | +| ... | +| #1598 | 1 | +39/-3 | feature | 0.45 | medium | security | feat: add static bearer token fast-path for egress auth | +| #1765 | 6 | +421/-26 | bugfix | 0.44 | medium | correctness | fix(mcpgw): return the custom records and asset metadat... | +| #1713 | 4 | +84/-1 | bugfix | 0.38 | low | correctness | fix(auth): normalize IdP group names before matching sc... | +``` + +Then the same pull requests grouped by tier, each with the questions that drove its load: + +``` +HIGH (13) -> a human reads this line by line, and the author walks them through it + #610 load 0.82 ALB-direct deployment mode - Public Endpoint with IP filtering + blast radius, security surface, design decisions + https://github.com/agentic-community/mcp-gateway-registry/pull/610 + +LOW (1) -> one reviewer, one pass, no meeting + #1713 load 0.38 fix(auth): normalize IdP group names before matching scope mappings + security surface, blast radius, design decisions + https://github.com/agentic-community/mcp-gateway-registry/pull/1713 + +26 pull requests, 11 questions each, one call apiece. 183,426 input tokens, 4,230 ms total, +163 ms per call on average, $0.00770 at $0.042 per million input tokens. +10 of 26 had patches too large to send whole, so Jev read part of the diff and the paths of +the rest. Those are the ones the size floor guards. +``` + +Every run also writes a JSON report to [data/](data/), holding each answer as Jev sent it next to the credit and the tier the sample derived from it. + +### One pull request, from eleven answers to one tier + +Pull request #610 adds an ALB-direct deployment mode with IP filtering, across 19 files and +533/-74 lines. Jev read 84% of its diff and answered all eleven questions in one call. Nine of them carry weight, and `credit` is what each answer contributed after inversion: + +| Question | Jev returned | Weight | Credit | Adds to load | +| --- | --- | --- | --- | --- | +| `blast_radius` | 2.00 / 2 | 0.15 | 1.00 | 0.1500 | +| `security_surface` | 0.99 | 0.15 | 0.99 | 0.1485 | +| `design_decisions` | 1.98 / 2 | 0.14 | 0.99 | 0.1386 | +| `infra_surface` | 0.99 | 0.08 | 0.99 | 0.0792 | +| `has_tests` (inverted) | 0.03 | 0.08 | 0.97 | 0.0776 | +| `mechanical` (inverted) | 0.39 | 0.12 | 0.61 | 0.0732 | +| `breaking_change` | 0.66 | 0.11 | 0.66 | 0.0726 | +| `scope_creep` | 0.58 | 0.09 | 0.58 | 0.0522 | +| `description_quality` (inverted) | 1.18 / 2 | 0.08 | 0.41 | 0.0328 | +| | | **1.00** | | **0.8247** | + +The weights sum to 1.00, so the load is that column added up: 0.8247, which the table above prints as 0.82. It sits in the 0.62-and-up band, so #610 lands in high, and the run names its three heaviest rows as the drivers: blast radius, security surface, design decisions. + +The inverted rows are where a good answer lowers the load. `has_tests` came back 0.03, so Jev found almost nothing exercising the change, and its credit is 1 minus 0.03. That row adds 0.078, and the same row on a tested change would add close to nothing. `mechanical` at 0.39 says the diff is not one edit repeated, so a reviewer cannot check one instance and stop. `description_quality` is the one row holding the load down: the author explained enough to reach 1.18 out of 2, which leaves a credit of 0.41 where a bare description would have left 0.9 or more. + +The two unweighted labels came back `feature` at 0.99 confidence and `security` at 0.95. They tell a reviewer what kind of change this is and where to start, and neither moves the number. + +The size floor changed nothing here. Nineteen files sets a floor of medium, under the high the load already earned. Airflow #73713 is the case where it bites: 54 files of one repeated locale-formatting edit score a load of 0.37, which alone would read low, and the floor raises it to high. + +## What to notice + +**All four tiers get used.** Eighteen recent open pull requests from [apache/airflow](https://github.com/apache/airflow) came out 3 high, 5 medium, 8 low and 2 trivial. The smallest is #73722, a two-line clarification of `max_db_retries` doc wording, which scores 0.21 and lands in trivial. How a queue spreads across the tiers is a fact about the repository, so read your own distribution before you move a cut. + +**Nine files can outrank fifty-four.** #73704 changes 89 lines across 9 files and lands in medium at 0.51, because it fixes socket leaks and adds request timeouts across providers: `security_surface` came back 0.84 and `has_tests` 0.03. #73713 changes 598 lines across 54 files and scores 0.37, because `mechanical` came back 0.80 on one locale-formatting edit repeated through the UI, and the change ships tests. The eleven questions sort by what a review has to catch, and the driver line names the question that did it. + +**Arithmetic stays in Python.** Jev cannot count, so the file and line totals reach it as a sentence for context, and every threshold on a number lives in [pr_triage.py](pr_triage.py). `SIZE_FLOORS` names the lowest tier a change of a given size can land in: over 30 files or 1,500 lines is high whatever Jev returned. The floor only ever raises a tier, and the output marks the rows where it did, so a reader can see the disagreement instead of inheriting it. + +**Say how much of the diff the model read.** Five of those eighteen hold more patch than the 24,000-character budget, so Jev read some files whole and the rest as paths and line counts. `_diff_text` fills the budget with the smallest patches first, because triage asks how far a change reaches and whether one edit repeats, and both of those want breadth. Coverage lands in the report as `diff_coverage` and in the grouped output as a line under any pull request below 70%. The 0.37 on #73713 came from 44% of its diff, which is a weaker claim than the same number across all of it, and the size floor guards those rows. + +**Weights are a data change.** Raising `security_surface` to 0.25 means editing one line of `questions.yml`. Moving a tier cut means editing `LOAD_FLOORS`. Both sit in a diff, and neither hides inside a question's wording. + +**The description is evidence, and the author wrote it.** An author who opens with "trivial, please merge" is arguing for their own triage. The state names that field `description_written_by_the_author` and `scope_creep` asks whether the diff matches it, which turns the claim into something to check. + +**Answers move between runs.** Two runs of the same dataset moved a load by up to 0.03. The tier column marks any load within 0.02 of a cut as `(on a cut)` rather than picking a side, so a pull request sitting on the boundary reads the same way twice. + +## Calibrate before you trust a tier + +TypeSafe reports 67.8% accuracy on their own benchmark, published September 2026. These tiers are advice about where to spend attention, and nothing here gates a merge. Point the sample at fifty pull requests your team already reviewed, compare the tiers against what those reviews cost, and move the cuts in `LOAD_FLOORS` until they match your repository. [jevcal](https://github.com/abhixhek/jevcal) turns that comparison into a measurement. diff --git a/samples/pr-triage/data/agentic-community-mcp-gateway-registry-open-all-triage.json b/samples/pr-triage/data/agentic-community-mcp-gateway-registry-open-all-triage.json new file mode 100644 index 0000000..10c181b --- /dev/null +++ b/samples/pr-triage/data/agentic-community-mcp-gateway-registry-open-all-triage.json @@ -0,0 +1,5532 @@ +{ + "repo": "agentic-community/mcp-gateway-registry", + "selector": { + "state": "open", + "limit": null, + "since": null + }, + "triaged_at": "2026-09-25T14:35:03+00:00", + "model": "jev-1.13.0", + "questions_asked": 11, + "tier_counts": { + "trivial": 0, + "low": 1, + "medium": 12, + "high": 13 + }, + "cost_usd": 0.00770389, + "pull_requests": [ + { + "number": 610, + "title": "ALB-direct deployment mode - Public Endpoint with IP filtering", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/610", + "author": "jantmer", + "changed_files": 19, + "additions": 533, + "deletions": 74, + "review_load": 0.8247, + "tier": "high", + "size_floor": "medium", + "raised_by_size": false, + "diff_coverage": 0.8421, + "drivers": [ + "blast radius", + "security surface", + "design decisions" + ], + "usage": { + "input_tokens": 8611, + "output_tokens": 282 + }, + "latency_ms": 149, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "feature", + "confidence": 0.99, + "probabilities": { + "docs": 0.0, + "dependency": 0.0, + "refactor": 0.0, + "tests": 0.0, + "feature": 0.99, + "bugfix": 0.0, + "config": 0.01 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "security", + "confidence": 0.95, + "probabilities": { + "correctness": 0.03, + "compatibility": 0.0, + "deployment": 0.0, + "security": 0.97, + "tests": 0.0, + "readability": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 0.99, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 1.0, + "adds_to_load": 0.15 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.98, + "confidence": 0.98, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.01, + "2": 0.99 + } + }, + "credit": 0.99, + "adds_to_load": 0.1386 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 1.18, + "confidence": 0.71, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.01, + "1": 0.8, + "2": 0.19 + } + }, + "credit": 0.41, + "adds_to_load": 0.0328 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.99 + }, + "credit": 0.99, + "adds_to_load": 0.1485 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.39 + }, + "credit": 0.61, + "adds_to_load": 0.0732 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.66 + }, + "credit": 0.66, + "adds_to_load": 0.0726 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.58 + }, + "credit": 0.58, + "adds_to_load": 0.0522 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.99 + }, + "credit": 0.99, + "adds_to_load": 0.0792 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.03 + }, + "credit": 0.97, + "adds_to_load": 0.0776 + } + } + }, + { + "number": 1671, + "title": "feat(charts): keycloak official image", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/1671", + "author": "omrishiv", + "changed_files": 12, + "additions": 791, + "deletions": 71, + "review_load": 0.7509, + "tier": "high", + "size_floor": "medium", + "raised_by_size": false, + "diff_coverage": 0.75, + "drivers": [ + "security surface", + "design decisions", + "blast radius" + ], + "usage": { + "input_tokens": 7947, + "output_tokens": 282 + }, + "latency_ms": 134, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "feature", + "confidence": 0.84, + "probabilities": { + "refactor": 0.0, + "tests": 0.0, + "config": 0.13, + "docs": 0.0, + "feature": 0.87, + "bugfix": 0.0, + "dependency": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "deployment", + "confidence": 0.7, + "probabilities": { + "correctness": 0.05, + "tests": 0.0, + "security": 0.08, + "deployment": 0.75, + "readability": 0.0, + "compatibility": 0.12 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.34, + "confidence": 0.49, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.66, + "2": 0.34 + } + }, + "credit": 0.67, + "adds_to_load": 0.1005 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.99, + "confidence": 0.98, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.01, + "2": 0.99 + } + }, + "credit": 0.995, + "adds_to_load": 0.1393 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 1.14, + "confidence": 0.79, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.86, + "2": 0.14 + } + }, + "credit": 0.43, + "adds_to_load": 0.0344 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.97 + }, + "credit": 0.97, + "adds_to_load": 0.1455 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.28 + }, + "credit": 0.72, + "adds_to_load": 0.0864 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.57 + }, + "credit": 0.57, + "adds_to_load": 0.0627 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.29 + }, + "credit": 0.29, + "adds_to_load": 0.0261 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.98 + }, + "credit": 0.98, + "adds_to_load": 0.0784 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.03 + }, + "credit": 0.97, + "adds_to_load": 0.0776 + } + } + }, + { + "number": 1693, + "title": "feat(security): Asymmetric (ES256) internal JWT signing", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/1693", + "author": "omrishiv", + "changed_files": 51, + "additions": 3052, + "deletions": 626, + "review_load": 0.7397, + "tier": "high", + "size_floor": "high", + "raised_by_size": false, + "diff_coverage": 0.3725, + "drivers": [ + "security surface", + "blast radius", + "design decisions" + ], + "usage": { + "input_tokens": 9322, + "output_tokens": 282 + }, + "latency_ms": 173, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "feature", + "confidence": 1.0, + "probabilities": { + "refactor": 0.0, + "tests": 0.0, + "dependency": 0.0, + "bugfix": 0.0, + "config": 0.0, + "feature": 1.0, + "docs": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "security", + "confidence": 0.98, + "probabilities": { + "readability": 0.0, + "compatibility": 0.0, + "deployment": 0.0, + "correctness": 0.01, + "security": 0.99, + "tests": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 1.0, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 1.0, + "adds_to_load": 0.15 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 0.99, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 1.0, + "adds_to_load": 0.14 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 1.7, + "confidence": 0.55, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.29, + "2": 0.71 + } + }, + "credit": 0.15, + "adds_to_load": 0.012 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 1.0 + }, + "credit": 1.0, + "adds_to_load": 0.15 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.38 + }, + "credit": 0.62, + "adds_to_load": 0.0744 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.73 + }, + "credit": 0.73, + "adds_to_load": 0.0803 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.58 + }, + "credit": 0.58, + "adds_to_load": 0.0522 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.97 + }, + "credit": 0.97, + "adds_to_load": 0.0776 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.96 + }, + "credit": 0.04, + "adds_to_load": 0.0032 + } + } + }, + { + "number": 1653, + "title": "feat(go-validate): Go fast-path sidecar for the /validate hot path", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/1653", + "author": "aarora79", + "changed_files": 58, + "additions": 4968, + "deletions": 168, + "review_load": 0.715, + "tier": "high", + "size_floor": "high", + "raised_by_size": false, + "diff_coverage": 0.4483, + "drivers": [ + "blast radius", + "security surface", + "design decisions" + ], + "usage": { + "input_tokens": 9575, + "output_tokens": 282 + }, + "latency_ms": 162, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "feature", + "confidence": 1.0, + "probabilities": { + "dependency": 0.0, + "tests": 0.0, + "bugfix": 0.0, + "docs": 0.0, + "refactor": 0.0, + "feature": 1.0, + "config": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "security", + "confidence": 0.97, + "probabilities": { + "compatibility": 0.01, + "deployment": 0.0, + "correctness": 0.02, + "tests": 0.0, + "security": 0.97, + "readability": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.99, + "confidence": 0.99, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.01, + "2": 0.99 + } + }, + "credit": 0.995, + "adds_to_load": 0.1492 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.99, + "confidence": 0.99, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.995, + "adds_to_load": 0.1393 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 1.99, + "confidence": 0.99, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.01, + "2": 0.99 + } + }, + "credit": 0.005, + "adds_to_load": 0.0004 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.99 + }, + "credit": 0.99, + "adds_to_load": 0.1485 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.35 + }, + "credit": 0.65, + "adds_to_load": 0.078 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.49 + }, + "credit": 0.49, + "adds_to_load": 0.0539 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.65 + }, + "credit": 0.65, + "adds_to_load": 0.0585 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.99 + }, + "credit": 0.99, + "adds_to_load": 0.0792 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.9 + }, + "credit": 0.1, + "adds_to_load": 0.008 + } + } + }, + { + "number": 1791, + "title": "feat(auth): Logto identity provider support (OAuth login + IAM role/group management)", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/1791", + "author": "scs001", + "changed_files": 11, + "additions": 1324, + "deletions": 6, + "review_load": 0.6981, + "tier": "high", + "size_floor": "medium", + "raised_by_size": false, + "diff_coverage": 0.7273, + "drivers": [ + "security surface", + "blast radius", + "design decisions" + ], + "usage": { + "input_tokens": 7701, + "output_tokens": 282 + }, + "latency_ms": 150, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "feature", + "confidence": 1.0, + "probabilities": { + "feature": 1.0, + "refactor": 0.0, + "bugfix": 0.0, + "docs": 0.0, + "tests": 0.0, + "config": 0.0, + "dependency": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "security", + "confidence": 0.88, + "probabilities": { + "tests": 0.0, + "readability": 0.0, + "correctness": 0.07, + "compatibility": 0.01, + "security": 0.91, + "deployment": 0.01 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.98, + "confidence": 0.97, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.02, + "2": 0.98 + } + }, + "credit": 0.99, + "adds_to_load": 0.1485 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.98, + "confidence": 0.97, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.01, + "2": 0.99 + } + }, + "credit": 0.99, + "adds_to_load": 0.1386 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 1.0, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.0, + "adds_to_load": 0.0 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.99 + }, + "credit": 0.99, + "adds_to_load": 0.1485 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.57 + }, + "credit": 0.43, + "adds_to_load": 0.0516 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.57 + }, + "credit": 0.57, + "adds_to_load": 0.0627 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.74 + }, + "credit": 0.74, + "adds_to_load": 0.0666 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.98 + }, + "credit": 0.98, + "adds_to_load": 0.0784 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.96 + }, + "credit": 0.04, + "adds_to_load": 0.0032 + } + } + }, + { + "number": 1798, + "title": "feat(egress): optional OBO exchanged-token cache (issue #1666)", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/1798", + "author": "omrishiv", + "changed_files": 30, + "additions": 1601, + "deletions": 50, + "review_load": 0.698, + "tier": "high", + "size_floor": "high", + "raised_by_size": false, + "diff_coverage": 0.5333, + "drivers": [ + "security surface", + "blast radius", + "design decisions" + ], + "usage": { + "input_tokens": 8906, + "output_tokens": 282 + }, + "latency_ms": 145, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "feature", + "confidence": 1.0, + "probabilities": { + "docs": 0.0, + "dependency": 0.0, + "feature": 1.0, + "refactor": 0.0, + "bugfix": 0.0, + "tests": 0.0, + "config": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "security", + "confidence": 0.96, + "probabilities": { + "deployment": 0.0, + "security": 0.97, + "correctness": 0.03, + "compatibility": 0.0, + "tests": 0.0, + "readability": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.92, + "confidence": 0.88, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.08, + "2": 0.92 + } + }, + "credit": 0.96, + "adds_to_load": 0.144 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.99, + "confidence": 0.99, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.995, + "adds_to_load": 0.1393 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 1.62, + "confidence": 0.43, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.37, + "2": 0.63 + } + }, + "credit": 0.19, + "adds_to_load": 0.0152 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.99 + }, + "credit": 0.99, + "adds_to_load": 0.1485 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.45 + }, + "credit": 0.55, + "adds_to_load": 0.066 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.74 + }, + "credit": 0.74, + "adds_to_load": 0.0814 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.28 + }, + "credit": 0.28, + "adds_to_load": 0.0252 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.93 + }, + "credit": 0.93, + "adds_to_load": 0.0744 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.95 + }, + "credit": 0.05, + "adds_to_load": 0.004 + } + } + }, + { + "number": 1784, + "title": "fix(charts): chart-render keycloak-client-secret and gate secret generation", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/1784", + "author": "omrishiv", + "changed_files": 26, + "additions": 1246, + "deletions": 80, + "review_load": 0.6944, + "tier": "high", + "size_floor": "medium", + "raised_by_size": false, + "diff_coverage": 0.6538, + "drivers": [ + "security surface", + "blast radius", + "design decisions" + ], + "usage": { + "input_tokens": 8856, + "output_tokens": 283 + }, + "latency_ms": 170, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "bugfix", + "confidence": 1.0, + "probabilities": { + "tests": 0.0, + "feature": 0.0, + "config": 0.0, + "bugfix": 1.0, + "dependency": 0.0, + "docs": 0.0, + "refactor": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "security", + "confidence": 0.32, + "probabilities": { + "readability": 0.0, + "tests": 0.01, + "security": 0.43, + "correctness": 0.29, + "deployment": 0.15, + "compatibility": 0.12 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.98, + "confidence": 0.96, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.02, + "2": 0.98 + } + }, + "credit": 0.99, + "adds_to_load": 0.1485 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.99, + "confidence": 0.99, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.995, + "adds_to_load": 0.1393 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 1.82, + "confidence": 0.73, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.18, + "2": 0.82 + } + }, + "credit": 0.09, + "adds_to_load": 0.0072 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.99 + }, + "credit": 0.99, + "adds_to_load": 0.1485 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.5 + }, + "credit": 0.5, + "adds_to_load": 0.06 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.76 + }, + "credit": 0.76, + "adds_to_load": 0.0836 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.41 + }, + "credit": 0.41, + "adds_to_load": 0.0369 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.84 + }, + "credit": 0.84, + "adds_to_load": 0.0672 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.96 + }, + "credit": 0.04, + "adds_to_load": 0.0032 + } + } + }, + { + "number": 1169, + "title": "feat(auth): Fine grained auth integration with external services", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/1169", + "author": "nathanzilgo", + "changed_files": 16, + "additions": 2944, + "deletions": 0, + "review_load": 0.6801, + "tier": "high", + "size_floor": "high", + "raised_by_size": false, + "diff_coverage": 0.625, + "drivers": [ + "security surface", + "design decisions", + "blast radius" + ], + "usage": { + "input_tokens": 7541, + "output_tokens": 282 + }, + "latency_ms": 163, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "feature", + "confidence": 1.0, + "probabilities": { + "tests": 0.0, + "feature": 1.0, + "refactor": 0.0, + "dependency": 0.0, + "bugfix": 0.0, + "config": 0.0, + "docs": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "security", + "confidence": 0.95, + "probabilities": { + "tests": 0.02, + "security": 0.96, + "correctness": 0.02, + "deployment": 0.0, + "compatibility": 0.0, + "readability": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.77, + "confidence": 0.66, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.23, + "2": 0.77 + } + }, + "credit": 0.885, + "adds_to_load": 0.1328 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 1.0, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 1.0, + "adds_to_load": 0.14 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 1.99, + "confidence": 0.99, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.01, + "2": 0.99 + } + }, + "credit": 0.005, + "adds_to_load": 0.0004 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.99 + }, + "credit": 0.99, + "adds_to_load": 0.1485 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.25 + }, + "credit": 0.75, + "adds_to_load": 0.09 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.48 + }, + "credit": 0.48, + "adds_to_load": 0.0528 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.36 + }, + "credit": 0.36, + "adds_to_load": 0.0324 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.97 + }, + "credit": 0.97, + "adds_to_load": 0.0776 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.93 + }, + "credit": 0.07, + "adds_to_load": 0.0056 + } + } + }, + { + "number": 1677, + "title": "fix(helm): stack-owned keycloak-internal Service so nginx survives Keycloak pod rolls", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/1677", + "author": "doppelc", + "changed_files": 26, + "additions": 878, + "deletions": 25, + "review_load": 0.6674, + "tier": "high", + "size_floor": "medium", + "raised_by_size": false, + "diff_coverage": 0.8077, + "drivers": [ + "blast radius", + "security surface", + "design decisions" + ], + "usage": { + "input_tokens": 9198, + "output_tokens": 285 + }, + "latency_ms": 155, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "bugfix", + "confidence": 1.0, + "probabilities": { + "docs": 0.0, + "bugfix": 1.0, + "feature": 0.0, + "config": 0.0, + "dependency": 0.0, + "refactor": 0.0, + "tests": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.77, + "probabilities": { + "security": 0.01, + "readability": 0.0, + "compatibility": 0.08, + "deployment": 0.01, + "tests": 0.09, + "correctness": 0.81 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.99, + "confidence": 0.99, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.01, + "2": 0.99 + } + }, + "credit": 0.995, + "adds_to_load": 0.1492 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.98, + "confidence": 0.98, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.01, + "1": 0.01, + "2": 0.98 + } + }, + "credit": 0.99, + "adds_to_load": 0.1386 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 1.0, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.0, + "adds_to_load": 0.0 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.96 + }, + "credit": 0.96, + "adds_to_load": 0.144 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.58 + }, + "credit": 0.42, + "adds_to_load": 0.0504 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.81 + }, + "credit": 0.81, + "adds_to_load": 0.0891 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.33 + }, + "credit": 0.33, + "adds_to_load": 0.0297 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.8 + }, + "credit": 0.8, + "adds_to_load": 0.064 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.97 + }, + "credit": 0.03, + "adds_to_load": 0.0024 + } + } + }, + { + "number": 1686, + "title": "feat(egress): add operator_credential mode to inject the shared backend credential", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/1686", + "author": "doppelc", + "changed_files": 24, + "additions": 973, + "deletions": 179, + "review_load": 0.6561, + "tier": "high", + "size_floor": "medium", + "raised_by_size": false, + "diff_coverage": 0.5833, + "drivers": [ + "blast radius", + "security surface", + "design decisions" + ], + "usage": { + "input_tokens": 8173, + "output_tokens": 282 + }, + "latency_ms": 152, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "feature", + "confidence": 1.0, + "probabilities": { + "refactor": 0.0, + "tests": 0.0, + "bugfix": 0.0, + "dependency": 0.0, + "docs": 0.0, + "feature": 1.0, + "config": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "security", + "confidence": 0.95, + "probabilities": { + "deployment": 0.0, + "tests": 0.0, + "security": 0.96, + "correctness": 0.04, + "compatibility": 0.0, + "readability": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.99, + "confidence": 0.98, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.01, + "2": 0.99 + } + }, + "credit": 0.995, + "adds_to_load": 0.1492 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.99, + "confidence": 0.99, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.01, + "2": 0.99 + } + }, + "credit": 0.995, + "adds_to_load": 0.1393 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 1.44, + "confidence": 0.34, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.56, + "2": 0.44 + } + }, + "credit": 0.28, + "adds_to_load": 0.0224 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.99 + }, + "credit": 0.99, + "adds_to_load": 0.1485 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.42 + }, + "credit": 0.58, + "adds_to_load": 0.0696 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.72 + }, + "credit": 0.72, + "adds_to_load": 0.0792 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.38 + }, + "credit": 0.38, + "adds_to_load": 0.0342 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.13 + }, + "credit": 0.13, + "adds_to_load": 0.0104 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.96 + }, + "credit": 0.04, + "adds_to_load": 0.0032 + } + } + }, + { + "number": 1709, + "title": "fix(egress): stop resolving the egress vault id across identifier namespaces", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/1709", + "author": "go-faustino", + "changed_files": 12, + "additions": 277, + "deletions": 14, + "review_load": 0.6326, + "tier": "high", + "size_floor": "low", + "raised_by_size": false, + "diff_coverage": 0.9167, + "drivers": [ + "security surface", + "blast radius", + "design decisions" + ], + "usage": { + "input_tokens": 7360, + "output_tokens": 285 + }, + "latency_ms": 192, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "bugfix", + "confidence": 1.0, + "probabilities": { + "feature": 0.0, + "refactor": 0.0, + "docs": 0.0, + "dependency": 0.0, + "config": 0.0, + "tests": 0.0, + "bugfix": 1.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.56, + "probabilities": { + "deployment": 0.0, + "security": 0.32, + "readability": 0.0, + "tests": 0.02, + "compatibility": 0.02, + "correctness": 0.64 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.97, + "confidence": 0.96, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.02, + "2": 0.98 + } + }, + "credit": 0.985, + "adds_to_load": 0.1477 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.93, + "confidence": 0.89, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.01, + "1": 0.06, + "2": 0.9299999999999999 + } + }, + "credit": 0.965, + "adds_to_load": 0.1351 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 1.9, + "confidence": 0.85, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.1, + "2": 0.9 + } + }, + "credit": 0.05, + "adds_to_load": 0.004 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.99 + }, + "credit": 0.99, + "adds_to_load": 0.1485 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.32 + }, + "credit": 0.68, + "adds_to_load": 0.0816 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.66 + }, + "credit": 0.66, + "adds_to_load": 0.0726 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.39 + }, + "credit": 0.39, + "adds_to_load": 0.0351 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.08 + }, + "credit": 0.08, + "adds_to_load": 0.0064 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.98 + }, + "credit": 0.02, + "adds_to_load": 0.0016 + } + } + }, + { + "number": 1783, + "title": "feat(ingress): configure registry subdomain and additional hostnames", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/1783", + "author": "endriu0", + "changed_files": 16, + "additions": 354, + "deletions": 24, + "review_load": 0.6308, + "tier": "high", + "size_floor": "medium", + "raised_by_size": false, + "diff_coverage": 0.875, + "drivers": [ + "blast radius", + "security surface", + "design decisions" + ], + "usage": { + "input_tokens": 7439, + "output_tokens": 284 + }, + "latency_ms": 135, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "feature", + "confidence": 1.0, + "probabilities": { + "feature": 1.0, + "bugfix": 0.0, + "tests": 0.0, + "dependency": 0.0, + "docs": 0.0, + "refactor": 0.0, + "config": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.5, + "probabilities": { + "compatibility": 0.12, + "tests": 0.07, + "security": 0.22, + "deployment": 0.0, + "correctness": 0.59, + "readability": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.96, + "confidence": 0.94, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.04, + "2": 0.96 + } + }, + "credit": 0.98, + "adds_to_load": 0.147 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.87, + "confidence": 0.8, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.02, + "1": 0.1, + "2": 0.88 + } + }, + "credit": 0.935, + "adds_to_load": 0.1309 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 1.0, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.0, + "adds_to_load": 0.0 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.96 + }, + "credit": 0.96, + "adds_to_load": 0.144 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.57 + }, + "credit": 0.43, + "adds_to_load": 0.0516 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.53 + }, + "credit": 0.53, + "adds_to_load": 0.0583 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.46 + }, + "credit": 0.46, + "adds_to_load": 0.0414 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.67 + }, + "credit": 0.67, + "adds_to_load": 0.0536 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.95 + }, + "credit": 0.05, + "adds_to_load": 0.004 + } + } + }, + { + "number": 1796, + "title": "feat(mcpgw): pool the registry and M2M token egress on one long-lived client", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/1796", + "author": "omrishiv", + "changed_files": 31, + "additions": 2308, + "deletions": 151, + "review_load": 0.6293, + "tier": "high", + "size_floor": "high", + "raised_by_size": false, + "diff_coverage": 0.6774, + "drivers": [ + "blast radius", + "design decisions", + "security surface" + ], + "usage": { + "input_tokens": 8885, + "output_tokens": 284 + }, + "latency_ms": 157, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "feature", + "confidence": 0.94, + "probabilities": { + "feature": 0.96, + "tests": 0.0, + "bugfix": 0.04, + "docs": 0.0, + "refactor": 0.0, + "config": 0.0, + "dependency": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.7, + "probabilities": { + "tests": 0.04, + "readability": 0.0, + "correctness": 0.75, + "compatibility": 0.02, + "security": 0.18, + "deployment": 0.01 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 1.0, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 1.0, + "adds_to_load": 0.15 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.99, + "confidence": 0.99, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.995, + "adds_to_load": 0.1393 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 1.97, + "confidence": 0.95, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.01, + "1": 0.01, + "2": 0.98 + } + }, + "credit": 0.015, + "adds_to_load": 0.0012 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.92 + }, + "credit": 0.92, + "adds_to_load": 0.138 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.68 + }, + "credit": 0.32, + "adds_to_load": 0.0384 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.47 + }, + "credit": 0.47, + "adds_to_load": 0.0517 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.35 + }, + "credit": 0.35, + "adds_to_load": 0.0315 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.95 + }, + "credit": 0.95, + "adds_to_load": 0.076 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.96 + }, + "credit": 0.04, + "adds_to_load": 0.0032 + } + } + }, + { + "number": 1519, + "title": "feat(egress): client-side Dynamic Client Registration for Atlassian Rovo authv2", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/1519", + "author": "go-faustino", + "changed_files": 12, + "additions": 1444, + "deletions": 28, + "review_load": 0.609, + "tier": "medium", + "size_floor": "medium", + "raised_by_size": false, + "diff_coverage": 0.6667, + "drivers": [ + "security surface", + "design decisions", + "blast radius" + ], + "usage": { + "input_tokens": 6788, + "output_tokens": 282 + }, + "latency_ms": 144, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "feature", + "confidence": 1.0, + "probabilities": { + "feature": 1.0, + "tests": 0.0, + "bugfix": 0.0, + "docs": 0.0, + "refactor": 0.0, + "dependency": 0.0, + "config": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "security", + "confidence": 0.54, + "probabilities": { + "tests": 0.02, + "readability": 0.0, + "compatibility": 0.02, + "deployment": 0.0, + "security": 0.61, + "correctness": 0.35 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.73, + "confidence": 0.59, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.27, + "2": 0.73 + } + }, + "credit": 0.865, + "adds_to_load": 0.1298 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.99, + "confidence": 0.98, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.01, + "2": 0.99 + } + }, + "credit": 0.995, + "adds_to_load": 0.1393 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 1.0, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.0, + "adds_to_load": 0.0 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.99 + }, + "credit": 0.99, + "adds_to_load": 0.1485 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.32 + }, + "credit": 0.68, + "adds_to_load": 0.0816 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.68 + }, + "credit": 0.68, + "adds_to_load": 0.0748 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.31 + }, + "credit": 0.31, + "adds_to_load": 0.0279 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.05 + }, + "credit": 0.05, + "adds_to_load": 0.004 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.96 + }, + "credit": 0.04, + "adds_to_load": 0.0032 + } + } + }, + { + "number": 1795, + "title": "fix(virtual-servers): stop credential egress and enforce backend state on the virtual data path", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/1795", + "author": "omrishiv", + "changed_files": 7, + "additions": 898, + "deletions": 56, + "review_load": 0.5927, + "tier": "medium", + "size_floor": "medium", + "raised_by_size": false, + "diff_coverage": 0.7143, + "drivers": [ + "security surface", + "blast radius", + "design decisions" + ], + "usage": { + "input_tokens": 6835, + "output_tokens": 283 + }, + "latency_ms": 156, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "bugfix", + "confidence": 0.96, + "probabilities": { + "docs": 0.0, + "tests": 0.0, + "refactor": 0.0, + "config": 0.0, + "feature": 0.03, + "bugfix": 0.97, + "dependency": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "security", + "confidence": 0.96, + "probabilities": { + "correctness": 0.03, + "tests": 0.0, + "compatibility": 0.0, + "readability": 0.0, + "security": 0.97, + "deployment": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.93, + "confidence": 0.9, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.07, + "2": 0.93 + } + }, + "credit": 0.965, + "adds_to_load": 0.1447 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.99, + "confidence": 0.98, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.01, + "2": 0.99 + } + }, + "credit": 0.995, + "adds_to_load": 0.1393 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 1.95, + "confidence": 0.93, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.02, + "1": 0.01, + "2": 0.97 + } + }, + "credit": 0.025, + "adds_to_load": 0.002 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.99 + }, + "credit": 0.99, + "adds_to_load": 0.1485 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.28 + }, + "credit": 0.72, + "adds_to_load": 0.0864 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.13 + }, + "credit": 0.13, + "adds_to_load": 0.0143 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.22 + }, + "credit": 0.22, + "adds_to_load": 0.0198 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.44 + }, + "credit": 0.44, + "adds_to_load": 0.0352 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.97 + }, + "credit": 0.03, + "adds_to_load": 0.0024 + } + } + }, + { + "number": 1722, + "title": "fix(servers): make PATCH a field-scoped, revision-guarded write", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/1722", + "author": "atirna", + "changed_files": 11, + "additions": 724, + "deletions": 22, + "review_load": 0.5661, + "tier": "medium", + "size_floor": "medium", + "raised_by_size": false, + "diff_coverage": 0.7273, + "drivers": [ + "design decisions", + "security surface", + "blast radius" + ], + "usage": { + "input_tokens": 6985, + "output_tokens": 285 + }, + "latency_ms": 163, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "bugfix", + "confidence": 1.0, + "probabilities": { + "refactor": 0.0, + "tests": 0.0, + "config": 0.0, + "docs": 0.0, + "bugfix": 1.0, + "dependency": 0.0, + "feature": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.9, + "probabilities": { + "compatibility": 0.01, + "readability": 0.0, + "tests": 0.05, + "security": 0.01, + "deployment": 0.0, + "correctness": 0.93 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.76, + "confidence": 0.65, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.23, + "2": 0.77 + } + }, + "credit": 0.88, + "adds_to_load": 0.132 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.99, + "confidence": 0.99, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.995, + "adds_to_load": 0.1393 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 1.0, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.0, + "adds_to_load": 0.0 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.88 + }, + "credit": 0.88, + "adds_to_load": 0.132 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.42 + }, + "credit": 0.58, + "adds_to_load": 0.0696 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.48 + }, + "credit": 0.48, + "adds_to_load": 0.0528 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.36 + }, + "credit": 0.36, + "adds_to_load": 0.0324 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.08 + }, + "credit": 0.08, + "adds_to_load": 0.0064 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.98 + }, + "credit": 0.02, + "adds_to_load": 0.0016 + } + } + }, + { + "number": 1522, + "title": "Remove public-DNS resolver default for dynamic proxy_pass; log/meter backend 502s", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/1522", + "author": "soheilkhan", + "changed_files": 4, + "additions": 182, + "deletions": 9, + "review_load": 0.5524, + "tier": "medium", + "size_floor": "low", + "raised_by_size": false, + "diff_coverage": 1.0, + "drivers": [ + "blast radius", + "design decisions", + "has tests" + ], + "usage": { + "input_tokens": 5775, + "output_tokens": 285 + }, + "latency_ms": 147, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "bugfix", + "confidence": 1.0, + "probabilities": { + "refactor": 0.0, + "tests": 0.0, + "dependency": 0.0, + "bugfix": 1.0, + "docs": 0.0, + "feature": 0.0, + "config": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.92, + "probabilities": { + "readability": 0.0, + "compatibility": 0.04, + "deployment": 0.0, + "correctness": 0.93, + "security": 0.01, + "tests": 0.01 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.93, + "confidence": 0.9, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.07, + "2": 0.93 + } + }, + "credit": 0.965, + "adds_to_load": 0.1447 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.98, + "confidence": 0.97, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.01, + "2": 0.99 + } + }, + "credit": 0.99, + "adds_to_load": 0.1386 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 1.0, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.0, + "adds_to_load": 0.0 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.26 + }, + "credit": 0.26, + "adds_to_load": 0.039 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.37 + }, + "credit": 0.63, + "adds_to_load": 0.0756 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.22 + }, + "credit": 0.22, + "adds_to_load": 0.0242 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.15 + }, + "credit": 0.15, + "adds_to_load": 0.0135 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.51 + }, + "credit": 0.51, + "adds_to_load": 0.0408 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.05 + }, + "credit": 0.95, + "adds_to_load": 0.076 + } + } + }, + { + "number": 1801, + "title": "fix(auth): normalize IdP group names and name the groups the login filter drops", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/1801", + "author": "Evolver-sweden", + "changed_files": 9, + "additions": 428, + "deletions": 28, + "review_load": 0.5523, + "tier": "medium", + "size_floor": "low", + "raised_by_size": false, + "diff_coverage": 0.8889, + "drivers": [ + "blast radius", + "security surface", + "design decisions" + ], + "usage": { + "input_tokens": 7353, + "output_tokens": 285 + }, + "latency_ms": 377, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "bugfix", + "confidence": 1.0, + "probabilities": { + "refactor": 0.0, + "config": 0.0, + "tests": 0.0, + "bugfix": 1.0, + "dependency": 0.0, + "docs": 0.0, + "feature": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.4, + "probabilities": { + "correctness": 0.51, + "readability": 0.0, + "tests": 0.03, + "compatibility": 0.01, + "security": 0.45, + "deployment": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.98, + "confidence": 0.98, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.02, + "2": 0.98 + } + }, + "credit": 0.99, + "adds_to_load": 0.1485 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.98, + "confidence": 0.98, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.01, + "2": 0.99 + } + }, + "credit": 0.99, + "adds_to_load": 0.1386 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 1.98, + "confidence": 0.97, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.01, + "2": 0.99 + } + }, + "credit": 0.01, + "adds_to_load": 0.0008 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.98 + }, + "credit": 0.98, + "adds_to_load": 0.147 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.47 + }, + "credit": 0.53, + "adds_to_load": 0.0636 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.25 + }, + "credit": 0.25, + "adds_to_load": 0.0275 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.23 + }, + "credit": 0.23, + "adds_to_load": 0.0207 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.04 + }, + "credit": 0.04, + "adds_to_load": 0.0032 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.97 + }, + "credit": 0.03, + "adds_to_load": 0.0024 + } + } + }, + { + "number": 1794, + "title": "harden CLI server-import pipeline against code and command injection", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/1794", + "author": "omrishiv", + "changed_files": 7, + "additions": 943, + "deletions": 478, + "review_load": 0.5314, + "tier": "medium", + "size_floor": "medium", + "raised_by_size": false, + "diff_coverage": 0.5714, + "drivers": [ + "security surface", + "design decisions", + "blast radius" + ], + "usage": { + "input_tokens": 5746, + "output_tokens": 283 + }, + "latency_ms": 134, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "bugfix", + "confidence": 0.99, + "probabilities": { + "docs": 0.0, + "tests": 0.0, + "refactor": 0.01, + "bugfix": 0.99, + "dependency": 0.0, + "config": 0.0, + "feature": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "security", + "confidence": 0.95, + "probabilities": { + "security": 0.96, + "readability": 0.0, + "correctness": 0.02, + "tests": 0.02, + "compatibility": 0.0, + "deployment": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.14, + "confidence": 0.79, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.86, + "2": 0.14 + } + }, + "credit": 0.57, + "adds_to_load": 0.0855 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.99, + "confidence": 0.99, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.995, + "adds_to_load": 0.1393 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 1.93, + "confidence": 0.89, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.02, + "1": 0.04, + "2": 0.94 + } + }, + "credit": 0.035, + "adds_to_load": 0.0028 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.99 + }, + "credit": 0.99, + "adds_to_load": 0.1485 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.3 + }, + "credit": 0.7, + "adds_to_load": 0.084 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.25 + }, + "credit": 0.25, + "adds_to_load": 0.0275 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.3 + }, + "credit": 0.3, + "adds_to_load": 0.027 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.14 + }, + "credit": 0.14, + "adds_to_load": 0.0112 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.93 + }, + "credit": 0.07, + "adds_to_load": 0.0056 + } + } + }, + { + "number": 1678, + "title": "feat(egress): implement Keycloak RFC 8693 token-exchange for OBO", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/1678", + "author": "ruspg", + "changed_files": 6, + "additions": 1177, + "deletions": 62, + "review_load": 0.5221, + "tier": "medium", + "size_floor": "medium", + "raised_by_size": false, + "diff_coverage": 0.3333, + "drivers": [ + "security surface", + "design decisions", + "blast radius" + ], + "usage": { + "input_tokens": 6231, + "output_tokens": 282 + }, + "latency_ms": 141, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "feature", + "confidence": 1.0, + "probabilities": { + "feature": 1.0, + "tests": 0.0, + "bugfix": 0.0, + "docs": 0.0, + "refactor": 0.0, + "config": 0.0, + "dependency": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "security", + "confidence": 0.96, + "probabilities": { + "compatibility": 0.0, + "readability": 0.0, + "correctness": 0.03, + "tests": 0.01, + "security": 0.96, + "deployment": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.42, + "confidence": 0.36, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.58, + "2": 0.42 + } + }, + "credit": 0.71, + "adds_to_load": 0.1065 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.96, + "confidence": 0.94, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.03, + "2": 0.97 + } + }, + "credit": 0.98, + "adds_to_load": 0.1372 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 1.0, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.0, + "adds_to_load": 0.0 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.99 + }, + "credit": 0.99, + "adds_to_load": 0.1485 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.38 + }, + "credit": 0.62, + "adds_to_load": 0.0744 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.22 + }, + "credit": 0.22, + "adds_to_load": 0.0242 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.25 + }, + "credit": 0.25, + "adds_to_load": 0.0225 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.07 + }, + "credit": 0.07, + "adds_to_load": 0.0056 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.96 + }, + "credit": 0.04, + "adds_to_load": 0.0032 + } + } + }, + { + "number": 1764, + "title": "feat(keycloak): make the admin token realm configurable", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/1764", + "author": "mrchi", + "changed_files": 8, + "additions": 259, + "deletions": 1, + "review_load": 0.5071, + "tier": "medium", + "size_floor": "low", + "raised_by_size": false, + "diff_coverage": 1.0, + "drivers": [ + "security surface", + "design decisions", + "blast radius" + ], + "usage": { + "input_tokens": 6103, + "output_tokens": 284 + }, + "latency_ms": 136, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "feature", + "confidence": 0.99, + "probabilities": { + "tests": 0.0, + "feature": 0.99, + "config": 0.0, + "docs": 0.0, + "bugfix": 0.01, + "dependency": 0.0, + "refactor": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.44, + "probabilities": { + "compatibility": 0.19, + "security": 0.07, + "readability": 0.0, + "deployment": 0.0, + "tests": 0.21, + "correctness": 0.53 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.14, + "confidence": 0.78, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.86, + "2": 0.14 + } + }, + "credit": 0.57, + "adds_to_load": 0.0855 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.57, + "confidence": 0.35, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.04, + "1": 0.35, + "2": 0.61 + } + }, + "credit": 0.785, + "adds_to_load": 0.1099 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 1.0, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.0, + "adds_to_load": 0.0 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.98 + }, + "credit": 0.98, + "adds_to_load": 0.147 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.77 + }, + "credit": 0.23, + "adds_to_load": 0.0276 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.46 + }, + "credit": 0.46, + "adds_to_load": 0.0506 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.17 + }, + "credit": 0.17, + "adds_to_load": 0.0153 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.87 + }, + "credit": 0.87, + "adds_to_load": 0.0696 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.98 + }, + "credit": 0.02, + "adds_to_load": 0.0016 + } + } + }, + { + "number": 1766, + "title": "fix(security): a scan that could not run is not an unsafe verdict", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/1766", + "author": "go-faustino", + "changed_files": 14, + "additions": 493, + "deletions": 21, + "review_load": 0.4835, + "tier": "medium", + "size_floor": "medium", + "raised_by_size": false, + "diff_coverage": 0.8571, + "drivers": [ + "blast radius", + "design decisions", + "breaking change" + ], + "usage": { + "input_tokens": 6938, + "output_tokens": 285 + }, + "latency_ms": 137, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "bugfix", + "confidence": 1.0, + "probabilities": { + "refactor": 0.0, + "tests": 0.0, + "feature": 0.0, + "config": 0.0, + "dependency": 0.0, + "bugfix": 1.0, + "docs": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.58, + "probabilities": { + "readability": 0.0, + "security": 0.06, + "compatibility": 0.21, + "correctness": 0.65, + "deployment": 0.01, + "tests": 0.07 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.88, + "confidence": 0.82, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.12, + "2": 0.88 + } + }, + "credit": 0.94, + "adds_to_load": 0.141 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.98, + "confidence": 0.96, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.02, + "2": 0.98 + } + }, + "credit": 0.99, + "adds_to_load": 0.1386 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 1.94, + "confidence": 0.9, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.06, + "2": 0.94 + } + }, + "credit": 0.03, + "adds_to_load": 0.0024 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.28 + }, + "credit": 0.28, + "adds_to_load": 0.042 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.66 + }, + "credit": 0.34, + "adds_to_load": 0.0408 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.68 + }, + "credit": 0.68, + "adds_to_load": 0.0748 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.39 + }, + "credit": 0.39, + "adds_to_load": 0.0351 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.08 + }, + "credit": 0.08, + "adds_to_load": 0.0064 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.97 + }, + "credit": 0.03, + "adds_to_load": 0.0024 + } + } + }, + { + "number": 1748, + "title": "fix(prm): resolve RFC 9728 path-aware per-server PRM under a path-prefixed registry_url", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/1748", + "author": "peelandsee", + "changed_files": 3, + "additions": 94, + "deletions": 0, + "review_load": 0.4827, + "tier": "medium", + "size_floor": "trivial", + "raised_by_size": false, + "diff_coverage": 1.0, + "drivers": [ + "design decisions", + "security surface", + "mechanical" + ], + "usage": { + "input_tokens": 3173, + "output_tokens": 285 + }, + "latency_ms": 146, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "bugfix", + "confidence": 1.0, + "probabilities": { + "dependency": 0.0, + "tests": 0.0, + "feature": 0.0, + "docs": 0.0, + "bugfix": 1.0, + "refactor": 0.0, + "config": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.89, + "probabilities": { + "deployment": 0.0, + "tests": 0.05, + "readability": 0.0, + "security": 0.02, + "correctness": 0.9, + "compatibility": 0.03 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.19, + "confidence": 0.7, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.01, + "1": 0.79, + "2": 0.2 + } + }, + "credit": 0.595, + "adds_to_load": 0.0892 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.72, + "confidence": 0.57, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.28, + "2": 0.72 + } + }, + "credit": 0.86, + "adds_to_load": 0.1204 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 1.0, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.0, + "adds_to_load": 0.0 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.8 + }, + "credit": 0.8, + "adds_to_load": 0.12 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.24 + }, + "credit": 0.76, + "adds_to_load": 0.0912 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.24 + }, + "credit": 0.24, + "adds_to_load": 0.0264 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.1 + }, + "credit": 0.1, + "adds_to_load": 0.009 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.31 + }, + "credit": 0.31, + "adds_to_load": 0.0248 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.98 + }, + "credit": 0.02, + "adds_to_load": 0.0016 + } + } + }, + { + "number": 1598, + "title": "feat: add static bearer token fast-path for egress auth", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/1598", + "author": "hochbit", + "changed_files": 1, + "additions": 39, + "deletions": 3, + "review_load": 0.4522, + "tier": "medium", + "size_floor": "trivial", + "raised_by_size": false, + "diff_coverage": 1.0, + "drivers": [ + "security surface", + "design decisions", + "has tests" + ], + "usage": { + "input_tokens": 2590, + "output_tokens": 282 + }, + "latency_ms": 116, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "feature", + "confidence": 0.64, + "probabilities": { + "feature": 0.69, + "refactor": 0.0, + "dependency": 0.0, + "docs": 0.0, + "tests": 0.0, + "bugfix": 0.31, + "config": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "security", + "confidence": 0.97, + "probabilities": { + "correctness": 0.02, + "deployment": 0.0, + "readability": 0.0, + "compatibility": 0.0, + "tests": 0.0, + "security": 0.98 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 0.99, + "confidence": 0.07, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.32, + "1": 0.37, + "2": 0.31 + } + }, + "credit": 0.495, + "adds_to_load": 0.0742 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.33, + "confidence": 0.48, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.01, + "1": 0.65, + "2": 0.34 + } + }, + "credit": 0.665, + "adds_to_load": 0.0931 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 1.99, + "confidence": 0.99, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.005, + "adds_to_load": 0.0004 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.99 + }, + "credit": 0.99, + "adds_to_load": 0.1485 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.84 + }, + "credit": 0.16, + "adds_to_load": 0.0192 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.26 + }, + "credit": 0.26, + "adds_to_load": 0.0286 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.1 + }, + "credit": 0.1, + "adds_to_load": 0.009 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.03 + }, + "credit": 0.03, + "adds_to_load": 0.0024 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.04 + }, + "credit": 0.96, + "adds_to_load": 0.0768 + } + } + }, + { + "number": 1765, + "title": "fix(mcpgw): return the custom records and asset metadata the registry already sends", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/1765", + "author": "go-faustino", + "changed_files": 6, + "additions": 421, + "deletions": 26, + "review_load": 0.4429, + "tier": "medium", + "size_floor": "low", + "raised_by_size": false, + "diff_coverage": 0.8333, + "drivers": [ + "design decisions", + "mechanical", + "blast radius" + ], + "usage": { + "input_tokens": 6119, + "output_tokens": 285 + }, + "latency_ms": 158, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "bugfix", + "confidence": 1.0, + "probabilities": { + "feature": 0.0, + "tests": 0.0, + "bugfix": 1.0, + "dependency": 0.0, + "docs": 0.0, + "refactor": 0.0, + "config": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.65, + "probabilities": { + "deployment": 0.0, + "tests": 0.19, + "security": 0.03, + "correctness": 0.72, + "compatibility": 0.06, + "readability": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.21, + "confidence": 0.68, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.78, + "2": 0.22 + } + }, + "credit": 0.605, + "adds_to_load": 0.0907 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.82, + "confidence": 0.73, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.01, + "1": 0.17, + "2": 0.82 + } + }, + "credit": 0.91, + "adds_to_load": 0.1274 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 1.78, + "confidence": 0.67, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.22, + "2": 0.78 + } + }, + "credit": 0.11, + "adds_to_load": 0.0088 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.27 + }, + "credit": 0.27, + "adds_to_load": 0.0405 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.22 + }, + "credit": 0.78, + "adds_to_load": 0.0936 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.57 + }, + "credit": 0.57, + "adds_to_load": 0.0627 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.15 + }, + "credit": 0.15, + "adds_to_load": 0.0135 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.04 + }, + "credit": 0.04, + "adds_to_load": 0.0032 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.97 + }, + "credit": 0.03, + "adds_to_load": 0.0024 + } + } + }, + { + "number": 1713, + "title": "fix(auth): normalize IdP group names before matching scope mappings", + "url": "https://github.com/agentic-community/mcp-gateway-registry/pull/1713", + "author": "AmirF194", + "changed_files": 4, + "additions": 84, + "deletions": 1, + "review_load": 0.3903, + "tier": "low", + "size_floor": "trivial", + "raised_by_size": false, + "diff_coverage": 1.0, + "drivers": [ + "security surface", + "blast radius", + "design decisions" + ], + "usage": { + "input_tokens": 3276, + "output_tokens": 285 + }, + "latency_ms": 137, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "bugfix", + "confidence": 1.0, + "probabilities": { + "docs": 0.0, + "bugfix": 1.0, + "dependency": 0.0, + "refactor": 0.0, + "feature": 0.0, + "config": 0.0, + "tests": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.43, + "probabilities": { + "security": 0.34, + "compatibility": 0.01, + "deployment": 0.0, + "readability": 0.0, + "correctness": 0.52, + "tests": 0.13 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.46, + "confidence": 0.31, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.54, + "2": 0.46 + } + }, + "credit": 0.73, + "adds_to_load": 0.1095 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.21, + "confidence": 0.0, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.24, + "1": 0.31, + "2": 0.45 + } + }, + "credit": 0.605, + "adds_to_load": 0.0847 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 1.0, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.0, + "adds_to_load": 0.0 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.99 + }, + "credit": 0.99, + "adds_to_load": 0.1485 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.77 + }, + "credit": 0.23, + "adds_to_load": 0.0276 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.08 + }, + "credit": 0.08, + "adds_to_load": 0.0088 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.08 + }, + "credit": 0.08, + "adds_to_load": 0.0072 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.03 + }, + "credit": 0.03, + "adds_to_load": 0.0024 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.98 + }, + "credit": 0.02, + "adds_to_load": 0.0016 + } + } + } + ] +} diff --git a/samples/pr-triage/data/apache-airflow-open-latest-18-triage.json b/samples/pr-triage/data/apache-airflow-open-latest-18-triage.json new file mode 100644 index 0000000..847e8e1 --- /dev/null +++ b/samples/pr-triage/data/apache-airflow-open-latest-18-triage.json @@ -0,0 +1,3831 @@ +{ + "repo": "apache/airflow", + "selector": { + "state": "open", + "limit": 18, + "since": null + }, + "triaged_at": "2026-09-25T14:35:07+00:00", + "model": "jev-1.13.0", + "questions_asked": 11, + "tier_counts": { + "trivial": 2, + "low": 8, + "medium": 5, + "high": 3 + }, + "cost_usd": 0.00367731, + "pull_requests": [ + { + "number": 73698, + "title": "Bind the AWS auth manager SAML response to the browser that started the login", + "url": "https://github.com/apache/airflow/pull/73698", + "author": "potiuk", + "changed_files": 8, + "additions": 626, + "deletions": 73, + "review_load": 0.6231, + "tier": "high", + "size_floor": "medium", + "raised_by_size": false, + "diff_coverage": 0.875, + "drivers": [ + "security surface", + "design decisions", + "mechanical" + ], + "usage": { + "input_tokens": 7154, + "output_tokens": 283 + }, + "latency_ms": 150, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "bugfix", + "confidence": 0.99, + "probabilities": { + "docs": 0.0, + "bugfix": 0.99, + "feature": 0.01, + "tests": 0.0, + "dependency": 0.0, + "refactor": 0.0, + "config": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "security", + "confidence": 0.97, + "probabilities": { + "security": 0.98, + "readability": 0.0, + "compatibility": 0.0, + "deployment": 0.0, + "tests": 0.0, + "correctness": 0.02 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.02, + "confidence": 0.97, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.98, + "2": 0.02 + } + }, + "credit": 0.51, + "adds_to_load": 0.0765 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 0.99, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 1.0, + "adds_to_load": 0.14 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 1.99, + "confidence": 0.99, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.01, + "2": 0.99 + } + }, + "credit": 0.005, + "adds_to_load": 0.0004 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.99 + }, + "credit": 0.99, + "adds_to_load": 0.1485 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.3 + }, + "credit": 0.7, + "adds_to_load": 0.084 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.7 + }, + "credit": 0.7, + "adds_to_load": 0.077 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.47 + }, + "credit": 0.47, + "adds_to_load": 0.0423 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.62 + }, + "credit": 0.62, + "adds_to_load": 0.0496 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.94 + }, + "credit": 0.06, + "adds_to_load": 0.0048 + } + } + }, + { + "number": 73696, + "title": "Check admin-only views against a dedicated Keycloak resource in multi-team mode", + "url": "https://github.com/apache/airflow/pull/73696", + "author": "potiuk", + "changed_files": 7, + "additions": 136, + "deletions": 5, + "review_load": 0.5314, + "tier": "medium", + "size_floor": "low", + "raised_by_size": false, + "diff_coverage": 1.0, + "drivers": [ + "security surface", + "design decisions", + "mechanical" + ], + "usage": { + "input_tokens": 4565, + "output_tokens": 283 + }, + "latency_ms": 147, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "bugfix", + "confidence": 0.93, + "probabilities": { + "config": 0.0, + "dependency": 0.0, + "feature": 0.06, + "refactor": 0.0, + "docs": 0.0, + "tests": 0.0, + "bugfix": 0.94 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "security", + "confidence": 0.87, + "probabilities": { + "security": 0.9, + "deployment": 0.0, + "readability": 0.0, + "compatibility": 0.01, + "correctness": 0.09, + "tests": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.1, + "confidence": 0.85, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.9, + "2": 0.1 + } + }, + "credit": 0.55, + "adds_to_load": 0.0825 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.95, + "confidence": 0.93, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.04, + "2": 0.96 + } + }, + "credit": 0.975, + "adds_to_load": 0.1365 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 1.46, + "confidence": 0.3, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.54, + "2": 0.46 + } + }, + "credit": 0.27, + "adds_to_load": 0.0216 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.99 + }, + "credit": 0.99, + "adds_to_load": 0.1485 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.26 + }, + "credit": 0.74, + "adds_to_load": 0.0888 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.3 + }, + "credit": 0.3, + "adds_to_load": 0.033 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.13 + }, + "credit": 0.13, + "adds_to_load": 0.0117 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.08 + }, + "credit": 0.08, + "adds_to_load": 0.0064 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.97 + }, + "credit": 0.03, + "adds_to_load": 0.0024 + } + } + }, + { + "number": 73706, + "title": "Account for AgentOperator spend on failed runs and across retries", + "url": "https://github.com/apache/airflow/pull/73706", + "author": "Lee-W", + "changed_files": 17, + "additions": 2634, + "deletions": 117, + "review_load": 0.5057, + "tier": "high", + "size_floor": "high", + "raised_by_size": true, + "diff_coverage": 0.4706, + "drivers": [ + "design decisions", + "blast radius", + "breaking change" + ], + "usage": { + "input_tokens": 7679, + "output_tokens": 285 + }, + "latency_ms": 142, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "bugfix", + "confidence": 0.69, + "probabilities": { + "feature": 0.26, + "refactor": 0.0, + "dependency": 0.0, + "docs": 0.0, + "tests": 0.0, + "bugfix": 0.74, + "config": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.88, + "probabilities": { + "correctness": 0.9, + "compatibility": 0.07, + "tests": 0.03, + "deployment": 0.0, + "readability": 0.0, + "security": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.39, + "confidence": 0.42, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.61, + "2": 0.39 + } + }, + "credit": 0.695, + "adds_to_load": 0.1042 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 0.99, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 1.0, + "adds_to_load": 0.14 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 1.25, + "confidence": 0.62, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.75, + "2": 0.25 + } + }, + "credit": 0.375, + "adds_to_load": 0.03 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.12 + }, + "credit": 0.12, + "adds_to_load": 0.018 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.31 + }, + "credit": 0.69, + "adds_to_load": 0.0828 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.77 + }, + "credit": 0.77, + "adds_to_load": 0.0847 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.43 + }, + "credit": 0.43, + "adds_to_load": 0.0387 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.05 + }, + "credit": 0.05, + "adds_to_load": 0.004 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.96 + }, + "credit": 0.04, + "adds_to_load": 0.0032 + } + } + }, + { + "number": 73704, + "title": "Fix socket leaks and add missing request timeouts across providers", + "url": "https://github.com/apache/airflow/pull/73704", + "author": "TimurRakhmatullin86", + "changed_files": 9, + "additions": 57, + "deletions": 32, + "review_load": 0.505, + "tier": "medium", + "size_floor": "low", + "raised_by_size": false, + "diff_coverage": 1.0, + "drivers": [ + "security surface", + "blast radius", + "has tests" + ], + "usage": { + "input_tokens": 5588, + "output_tokens": 285 + }, + "latency_ms": 138, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "bugfix", + "confidence": 1.0, + "probabilities": { + "config": 0.0, + "feature": 0.0, + "bugfix": 1.0, + "dependency": 0.0, + "refactor": 0.0, + "docs": 0.0, + "tests": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.68, + "probabilities": { + "deployment": 0.0, + "security": 0.0, + "compatibility": 0.09, + "correctness": 0.73, + "readability": 0.0, + "tests": 0.18 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.65, + "confidence": 0.47, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.35, + "2": 0.65 + } + }, + "credit": 0.825, + "adds_to_load": 0.1237 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 0.95, + "confidence": 0.32, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.25, + "1": 0.55, + "2": 0.2 + } + }, + "credit": 0.475, + "adds_to_load": 0.0665 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 1.17, + "confidence": 0.74, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.83, + "2": 0.17 + } + }, + "credit": 0.415, + "adds_to_load": 0.0332 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.84 + }, + "credit": 0.84, + "adds_to_load": 0.126 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.52 + }, + "credit": 0.48, + "adds_to_load": 0.0576 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.09 + }, + "credit": 0.09, + "adds_to_load": 0.0099 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.08 + }, + "credit": 0.08, + "adds_to_load": 0.0072 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.04 + }, + "credit": 0.04, + "adds_to_load": 0.0032 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.03 + }, + "credit": 0.97, + "adds_to_load": 0.0776 + } + } + }, + { + "number": 73701, + "title": "Add durable reconnect option to EmrServerlessStartJobOperator", + "url": "https://github.com/apache/airflow/pull/73701", + "author": "Leondon9", + "changed_files": 4, + "additions": 360, + "deletions": 39, + "review_load": 0.4688, + "tier": "medium", + "size_floor": "low", + "raised_by_size": false, + "diff_coverage": 0.75, + "drivers": [ + "design decisions", + "mechanical", + "blast radius" + ], + "usage": { + "input_tokens": 5516, + "output_tokens": 284 + }, + "latency_ms": 314, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "feature", + "confidence": 1.0, + "probabilities": { + "refactor": 0.0, + "dependency": 0.0, + "feature": 1.0, + "bugfix": 0.0, + "docs": 0.0, + "tests": 0.0, + "config": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.86, + "probabilities": { + "deployment": 0.0, + "security": 0.0, + "correctness": 0.89, + "readability": 0.0, + "tests": 0.08, + "compatibility": 0.03 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.01, + "confidence": 0.98, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.99, + "2": 0.01 + } + }, + "credit": 0.505, + "adds_to_load": 0.0757 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.97, + "confidence": 0.95, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.03, + "2": 0.97 + } + }, + "credit": 0.985, + "adds_to_load": 0.1379 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 1.0, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.0, + "adds_to_load": 0.0 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.49 + }, + "credit": 0.49, + "adds_to_load": 0.0735 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.22 + }, + "credit": 0.78, + "adds_to_load": 0.0936 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.52 + }, + "credit": 0.52, + "adds_to_load": 0.0572 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.28 + }, + "credit": 0.28, + "adds_to_load": 0.0252 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.04 + }, + "credit": 0.04, + "adds_to_load": 0.0032 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.97 + }, + "credit": 0.03, + "adds_to_load": 0.0024 + } + } + }, + { + "number": 73723, + "title": "TS SDK: embed one source region per native Dag file", + "url": "https://github.com/apache/airflow/pull/73723", + "author": "pierrejeambrun", + "changed_files": 10, + "additions": 558, + "deletions": 121, + "review_load": 0.435, + "tier": "medium", + "size_floor": "medium", + "raised_by_size": false, + "diff_coverage": 0.8, + "drivers": [ + "design decisions", + "blast radius", + "breaking change" + ], + "usage": { + "input_tokens": 9195, + "output_tokens": 284 + }, + "latency_ms": 165, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "feature", + "confidence": 0.78, + "probabilities": { + "docs": 0.0, + "dependency": 0.0, + "feature": 0.8200000000000001, + "bugfix": 0.18, + "refactor": 0.0, + "tests": 0.0, + "config": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.74, + "probabilities": { + "deployment": 0.0, + "security": 0.0, + "correctness": 0.79, + "readability": 0.0, + "tests": 0.12, + "compatibility": 0.09 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.2, + "confidence": 0.7, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.8, + "2": 0.2 + } + }, + "credit": 0.6, + "adds_to_load": 0.09 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 1.0, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 1.0, + "adds_to_load": 0.14 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 1.0, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.0, + "adds_to_load": 0.0 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.05 + }, + "credit": 0.05, + "adds_to_load": 0.0075 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.3 + }, + "credit": 0.7, + "adds_to_load": 0.084 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.77 + }, + "credit": 0.77, + "adds_to_load": 0.0847 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.24 + }, + "credit": 0.24, + "adds_to_load": 0.0216 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.05 + }, + "credit": 0.05, + "adds_to_load": 0.004 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.96 + }, + "credit": 0.04, + "adds_to_load": 0.0032 + } + } + }, + { + "number": 73703, + "title": "[chart/v1-2x-test] Helm: Allow hostAliases and log groomer lifecycle hooks on the Dag processor (#73159)", + "url": "https://github.com/apache/airflow/pull/73703", + "author": "pranayyelugam", + "changed_files": 5, + "additions": 64, + "deletions": 0, + "review_load": 0.4309, + "tier": "medium", + "size_floor": "low", + "raised_by_size": false, + "diff_coverage": 1.0, + "drivers": [ + "design decisions", + "blast radius", + "mechanical" + ], + "usage": { + "input_tokens": 2825, + "output_tokens": 284 + }, + "latency_ms": 121, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "feature", + "confidence": 0.98, + "probabilities": { + "docs": 0.0, + "dependency": 0.0, + "feature": 0.98, + "refactor": 0.0, + "bugfix": 0.0, + "tests": 0.0, + "config": 0.02 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.49, + "probabilities": { + "deployment": 0.0, + "security": 0.0, + "correctness": 0.58, + "readability": 0.0, + "tests": 0.34, + "compatibility": 0.08 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.01, + "confidence": 0.99, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.99, + "2": 0.01 + } + }, + "credit": 0.505, + "adds_to_load": 0.0757 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.47, + "confidence": 0.2, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.12, + "1": 0.29, + "2": 0.59 + } + }, + "credit": 0.735, + "adds_to_load": 0.1029 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 1.31, + "confidence": 0.51, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.01, + "1": 0.67, + "2": 0.32 + } + }, + "credit": 0.345, + "adds_to_load": 0.0276 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.2 + }, + "credit": 0.2, + "adds_to_load": 0.03 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.37 + }, + "credit": 0.63, + "adds_to_load": 0.0756 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.37 + }, + "credit": 0.37, + "adds_to_load": 0.0407 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.08 + }, + "credit": 0.08, + "adds_to_load": 0.0072 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.86 + }, + "credit": 0.86, + "adds_to_load": 0.0688 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.97 + }, + "credit": 0.03, + "adds_to_load": 0.0024 + } + } + }, + { + "number": 73702, + "title": "Stop AWS Glue job run when a deferred task is cleared", + "url": "https://github.com/apache/airflow/pull/73702", + "author": "Leondon9", + "changed_files": 4, + "additions": 291, + "deletions": 3, + "review_load": 0.4093, + "tier": "low", + "size_floor": "low", + "raised_by_size": false, + "diff_coverage": 1.0, + "drivers": [ + "design decisions", + "mechanical", + "blast radius" + ], + "usage": { + "input_tokens": 7135, + "output_tokens": 285 + }, + "latency_ms": 199, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "bugfix", + "confidence": 0.48, + "probabilities": { + "config": 0.0, + "dependency": 0.0, + "feature": 0.45, + "refactor": 0.0, + "docs": 0.0, + "tests": 0.0, + "bugfix": 0.55 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.8, + "probabilities": { + "security": 0.0, + "deployment": 0.0, + "readability": 0.0, + "compatibility": 0.07, + "correctness": 0.84, + "tests": 0.09 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.01, + "confidence": 0.99, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.99, + "2": 0.01 + } + }, + "credit": 0.505, + "adds_to_load": 0.0757 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.99, + "confidence": 0.99, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.01, + "2": 0.99 + } + }, + "credit": 0.995, + "adds_to_load": 0.1393 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 1.0, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.0, + "adds_to_load": 0.0 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.26 + }, + "credit": 0.26, + "adds_to_load": 0.039 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.23 + }, + "credit": 0.77, + "adds_to_load": 0.0924 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.38 + }, + "credit": 0.38, + "adds_to_load": 0.0418 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.18 + }, + "credit": 0.18, + "adds_to_load": 0.0162 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.04 + }, + "credit": 0.04, + "adds_to_load": 0.0032 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.98 + }, + "credit": 0.02, + "adds_to_load": 0.0016 + } + } + }, + { + "number": 73724, + "title": "Fix Grid and Graph 500 errors for cyclic TaskGroup dependencies", + "url": "https://github.com/apache/airflow/pull/73724", + "author": "dheerajturaga", + "changed_files": 4, + "additions": 209, + "deletions": 12, + "review_load": 0.3743, + "tier": "low", + "size_floor": "low", + "raised_by_size": false, + "diff_coverage": 1.0, + "drivers": [ + "design decisions", + "blast radius", + "mechanical" + ], + "usage": { + "input_tokens": 5050, + "output_tokens": 285 + }, + "latency_ms": 168, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "bugfix", + "confidence": 1.0, + "probabilities": { + "bugfix": 1.0, + "docs": 0.0, + "tests": 0.0, + "config": 0.0, + "feature": 0.0, + "dependency": 0.0, + "refactor": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.92, + "probabilities": { + "compatibility": 0.01, + "deployment": 0.0, + "security": 0.0, + "tests": 0.06, + "correctness": 0.9299999999999999, + "readability": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.31, + "confidence": 0.52, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.01, + "1": 0.68, + "2": 0.31 + } + }, + "credit": 0.655, + "adds_to_load": 0.0983 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.99, + "confidence": 0.99, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.01, + "2": 0.99 + } + }, + "credit": 0.995, + "adds_to_load": 0.1393 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 1.8, + "confidence": 0.71, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.19, + "2": 0.81 + } + }, + "credit": 0.1, + "adds_to_load": 0.008 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.05 + }, + "credit": 0.05, + "adds_to_load": 0.0075 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.37 + }, + "credit": 0.63, + "adds_to_load": 0.0756 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.23 + }, + "credit": 0.23, + "adds_to_load": 0.0253 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.19 + }, + "credit": 0.19, + "adds_to_load": 0.0171 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.02 + }, + "credit": 0.02, + "adds_to_load": 0.0016 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.98 + }, + "credit": 0.02, + "adds_to_load": 0.0016 + } + } + }, + { + "number": 73713, + "title": "UI: Group digits of counters according to the selected locale", + "url": "https://github.com/apache/airflow/pull/73713", + "author": "shahar1", + "changed_files": 54, + "additions": 467, + "deletions": 131, + "review_load": 0.3726, + "tier": "high", + "size_floor": "high", + "raised_by_size": true, + "diff_coverage": 0.4444, + "drivers": [ + "blast radius", + "design decisions" + ], + "usage": { + "input_tokens": 10341, + "output_tokens": 284 + }, + "latency_ms": 183, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "feature", + "confidence": 0.99, + "probabilities": { + "refactor": 0.0, + "feature": 1.0, + "dependency": 0.0, + "bugfix": 0.0, + "docs": 0.0, + "config": 0.0, + "tests": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.71, + "probabilities": { + "readability": 0.0, + "tests": 0.15, + "security": 0.0, + "correctness": 0.77, + "deployment": 0.0, + "compatibility": 0.08 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.97, + "confidence": 0.96, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.03, + "2": 0.97 + } + }, + "credit": 0.985, + "adds_to_load": 0.1477 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.91, + "confidence": 0.87, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.03, + "1": 0.04, + "2": 0.9299999999999999 + } + }, + "credit": 0.955, + "adds_to_load": 0.1337 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 1.0, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.0, + "adds_to_load": 0.0 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.03 + }, + "credit": 0.03, + "adds_to_load": 0.0045 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.8 + }, + "credit": 0.2, + "adds_to_load": 0.024 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.07 + }, + "credit": 0.07, + "adds_to_load": 0.0077 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.54 + }, + "credit": 0.54, + "adds_to_load": 0.0486 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.04 + }, + "credit": 0.04, + "adds_to_load": 0.0032 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.96 + }, + "credit": 0.04, + "adds_to_load": 0.0032 + } + } + }, + { + "number": 73720, + "title": "[v3-3-test] Raise DeadlockImminentError for sync comms calls from a paused event loop thread (#73521)", + "url": "https://github.com/apache/airflow/pull/73720", + "author": "github-actions[bot]", + "changed_files": 2, + "additions": 98, + "deletions": 6, + "review_load": 0.3703, + "tier": "low", + "size_floor": "trivial", + "raised_by_size": false, + "diff_coverage": 1.0, + "drivers": [ + "design decisions", + "blast radius", + "mechanical" + ], + "usage": { + "input_tokens": 3412, + "output_tokens": 285 + }, + "latency_ms": 155, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "bugfix", + "confidence": 1.0, + "probabilities": { + "docs": 0.0, + "dependency": 0.0, + "refactor": 0.0, + "tests": 0.0, + "feature": 0.0, + "bugfix": 1.0, + "config": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.81, + "probabilities": { + "compatibility": 0.01, + "correctness": 0.84, + "deployment": 0.0, + "security": 0.0, + "tests": 0.15, + "readability": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.64, + "confidence": 0.46, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.35, + "2": 0.65 + } + }, + "credit": 0.82, + "adds_to_load": 0.123 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.92, + "confidence": 0.88, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.0, + "1": 0.08, + "2": 0.92 + } + }, + "credit": 0.96, + "adds_to_load": 0.1344 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 1.87, + "confidence": 0.81, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.12, + "2": 0.88 + } + }, + "credit": 0.065, + "adds_to_load": 0.0052 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.06 + }, + "credit": 0.06, + "adds_to_load": 0.009 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.36 + }, + "credit": 0.64, + "adds_to_load": 0.0768 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.08 + }, + "credit": 0.08, + "adds_to_load": 0.0088 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.11 + }, + "credit": 0.11, + "adds_to_load": 0.0099 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.02 + }, + "credit": 0.02, + "adds_to_load": 0.0016 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.98 + }, + "credit": 0.02, + "adds_to_load": 0.0016 + } + } + }, + { + "number": 73719, + "title": "Fix masked failure reason for deferrable EMR Serverless jobs", + "url": "https://github.com/apache/airflow/pull/73719", + "author": "Leondon9", + "changed_files": 4, + "additions": 103, + "deletions": 8, + "review_load": 0.3614, + "tier": "low", + "size_floor": "trivial", + "raised_by_size": false, + "diff_coverage": 1.0, + "drivers": [ + "design decisions", + "mechanical", + "blast radius" + ], + "usage": { + "input_tokens": 3672, + "output_tokens": 285 + }, + "latency_ms": 140, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "bugfix", + "confidence": 1.0, + "probabilities": { + "bugfix": 1.0, + "docs": 0.0, + "config": 0.0, + "feature": 0.0, + "dependency": 0.0, + "refactor": 0.0, + "tests": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.82, + "probabilities": { + "security": 0.0, + "compatibility": 0.02, + "tests": 0.13, + "deployment": 0.0, + "readability": 0.0, + "correctness": 0.85 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.01, + "confidence": 0.98, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.99, + "2": 0.01 + } + }, + "credit": 0.505, + "adds_to_load": 0.0757 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.67, + "confidence": 0.51, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.01, + "1": 0.31, + "2": 0.68 + } + }, + "credit": 0.835, + "adds_to_load": 0.1169 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 1.0, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.0, + "adds_to_load": 0.0 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.21 + }, + "credit": 0.21, + "adds_to_load": 0.0315 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.28 + }, + "credit": 0.72, + "adds_to_load": 0.0864 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.32 + }, + "credit": 0.32, + "adds_to_load": 0.0352 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.13 + }, + "credit": 0.13, + "adds_to_load": 0.0117 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.03 + }, + "credit": 0.03, + "adds_to_load": 0.0024 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.98 + }, + "credit": 0.02, + "adds_to_load": 0.0016 + } + } + }, + { + "number": 73718, + "title": "Fix merge_dicts crash when overwriting a non-dict value with a dict", + "url": "https://github.com/apache/airflow/pull/73718", + "author": "ccoliu", + "changed_files": 2, + "additions": 14, + "deletions": 2, + "review_load": 0.2962, + "tier": "low", + "size_floor": "trivial", + "raised_by_size": false, + "diff_coverage": 1.0, + "drivers": [ + "blast radius", + "mechanical", + "design decisions" + ], + "usage": { + "input_tokens": 1831, + "output_tokens": 285 + }, + "latency_ms": 141, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "bugfix", + "confidence": 1.0, + "probabilities": { + "feature": 0.0, + "bugfix": 1.0, + "docs": 0.0, + "config": 0.0, + "refactor": 0.0, + "dependency": 0.0, + "tests": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.81, + "probabilities": { + "security": 0.0, + "readability": 0.0, + "compatibility": 0.03, + "deployment": 0.0, + "correctness": 0.84, + "tests": 0.13 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.76, + "confidence": 0.64, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.02, + "1": 0.21, + "2": 0.77 + } + }, + "credit": 0.88, + "adds_to_load": 0.132 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 0.92, + "confidence": 0.82, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.1, + "1": 0.88, + "2": 0.02 + } + }, + "credit": 0.46, + "adds_to_load": 0.0644 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 1.0, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.0, + "adds_to_load": 0.0 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.03 + }, + "credit": 0.03, + "adds_to_load": 0.0045 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.38 + }, + "credit": 0.62, + "adds_to_load": 0.0744 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.12 + }, + "credit": 0.12, + "adds_to_load": 0.0132 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.05 + }, + "credit": 0.05, + "adds_to_load": 0.0045 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.02 + }, + "credit": 0.02, + "adds_to_load": 0.0016 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.98 + }, + "credit": 0.02, + "adds_to_load": 0.0016 + } + } + }, + { + "number": 73709, + "title": "Avoid repeated KubernetesExecutor pod deletion for duplicate events", + "url": "https://github.com/apache/airflow/pull/73709", + "author": "Sanjays2402", + "changed_files": 2, + "additions": 113, + "deletions": 30, + "review_load": 0.2934, + "tier": "low", + "size_floor": "trivial", + "raised_by_size": false, + "diff_coverage": 1.0, + "drivers": [ + "design decisions", + "blast radius", + "mechanical" + ], + "usage": { + "input_tokens": 4408, + "output_tokens": 285 + }, + "latency_ms": 152, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "bugfix", + "confidence": 1.0, + "probabilities": { + "refactor": 0.0, + "config": 0.0, + "tests": 0.0, + "bugfix": 1.0, + "docs": 0.0, + "feature": 0.0, + "dependency": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.89, + "probabilities": { + "deployment": 0.0, + "readability": 0.0, + "correctness": 0.91, + "tests": 0.08, + "security": 0.0, + "compatibility": 0.01 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.11, + "confidence": 0.8, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.01, + "1": 0.87, + "2": 0.12 + } + }, + "credit": 0.555, + "adds_to_load": 0.0833 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.51, + "confidence": 0.26, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.01, + "1": 0.48, + "2": 0.51 + } + }, + "credit": 0.755, + "adds_to_load": 0.1057 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 1.0, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.0, + "adds_to_load": 0.0 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.03 + }, + "credit": 0.03, + "adds_to_load": 0.0045 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.42 + }, + "credit": 0.58, + "adds_to_load": 0.0696 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.06 + }, + "credit": 0.06, + "adds_to_load": 0.0066 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.21 + }, + "credit": 0.21, + "adds_to_load": 0.0189 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.04 + }, + "credit": 0.04, + "adds_to_load": 0.0032 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.98 + }, + "credit": 0.02, + "adds_to_load": 0.0016 + } + } + }, + { + "number": 73717, + "title": "add bundle_name to dag processor timeouts metric", + "url": "https://github.com/apache/airflow/pull/73717", + "author": "samraj2k", + "changed_files": 3, + "additions": 16, + "deletions": 6, + "review_load": 0.2859, + "tier": "low", + "size_floor": "trivial", + "raised_by_size": false, + "diff_coverage": 1.0, + "drivers": [ + "blast radius", + "mechanical", + "design decisions" + ], + "usage": { + "input_tokens": 2288, + "output_tokens": 284 + }, + "latency_ms": 168, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "feature", + "confidence": 0.95, + "probabilities": { + "bugfix": 0.04, + "refactor": 0.0, + "docs": 0.0, + "feature": 0.96, + "config": 0.0, + "tests": 0.0, + "dependency": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.41, + "probabilities": { + "deployment": 0.0, + "compatibility": 0.08, + "tests": 0.4, + "security": 0.0, + "correctness": 0.52, + "readability": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.17, + "confidence": 0.73, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.82, + "2": 0.18 + } + }, + "credit": 0.585, + "adds_to_load": 0.0877 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 0.97, + "confidence": 0.69, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.12, + "1": 0.79, + "2": 0.09 + } + }, + "credit": 0.485, + "adds_to_load": 0.0679 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 1.0, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.0, + "adds_to_load": 0.0 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.03 + }, + "credit": 0.03, + "adds_to_load": 0.0045 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.39 + }, + "credit": 0.61, + "adds_to_load": 0.0732 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.21 + }, + "credit": 0.21, + "adds_to_load": 0.0231 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.23 + }, + "credit": 0.23, + "adds_to_load": 0.0207 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.08 + }, + "credit": 0.08, + "adds_to_load": 0.0064 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.97 + }, + "credit": 0.03, + "adds_to_load": 0.0024 + } + } + }, + { + "number": 73711, + "title": "Emit queued_duration metric when a task enters RUNNING via the execution API", + "url": "https://github.com/apache/airflow/pull/73711", + "author": "wufangyong973", + "changed_files": 2, + "additions": 89, + "deletions": 0, + "review_load": 0.2653, + "tier": "low", + "size_floor": "trivial", + "raised_by_size": false, + "diff_coverage": 1.0, + "drivers": [ + "design decisions", + "blast radius", + "mechanical" + ], + "usage": { + "input_tokens": 2683, + "output_tokens": 285 + }, + "latency_ms": 112, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "bugfix", + "confidence": 0.96, + "probabilities": { + "feature": 0.03, + "tests": 0.0, + "bugfix": 0.97, + "docs": 0.0, + "refactor": 0.0, + "dependency": 0.0, + "config": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.87, + "probabilities": { + "tests": 0.1, + "readability": 0.0, + "compatibility": 0.01, + "deployment": 0.0, + "security": 0.0, + "correctness": 0.89 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.04, + "confidence": 0.92, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.01, + "1": 0.94, + "2": 0.05 + } + }, + "credit": 0.52, + "adds_to_load": 0.078 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 1.2, + "confidence": 0.68, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.01, + "1": 0.78, + "2": 0.21 + } + }, + "credit": 0.6, + "adds_to_load": 0.084 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 2.0, + "confidence": 1.0, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.0, + "2": 1.0 + } + }, + "credit": 0.0, + "adds_to_load": 0.0 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.06 + }, + "credit": 0.06, + "adds_to_load": 0.009 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.42 + }, + "credit": 0.58, + "adds_to_load": 0.0696 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.09 + }, + "credit": 0.09, + "adds_to_load": 0.0099 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.12 + }, + "credit": 0.12, + "adds_to_load": 0.0108 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.03 + }, + "credit": 0.03, + "adds_to_load": 0.0024 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.98 + }, + "credit": 0.02, + "adds_to_load": 0.0016 + } + } + }, + { + "number": 73722, + "title": "Clarify max_db_retries doc wording to avoid off-by-one confusion", + "url": "https://github.com/apache/airflow/pull/73722", + "author": "sohamwaghe", + "changed_files": 1, + "additions": 2, + "deletions": 1, + "review_load": 0.2062, + "tier": "trivial", + "size_floor": "trivial", + "raised_by_size": false, + "diff_coverage": 1.0, + "drivers": [ + "has tests" + ], + "usage": { + "input_tokens": 1329, + "output_tokens": 284 + }, + "latency_ms": 181, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "docs", + "confidence": 0.98, + "probabilities": { + "docs": 0.99, + "tests": 0.0, + "config": 0.01, + "dependency": 0.0, + "feature": 0.0, + "bugfix": 0.0, + "refactor": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.47, + "probabilities": { + "security": 0.0, + "tests": 0.02, + "correctness": 0.55, + "deployment": 0.0, + "readability": 0.4, + "compatibility": 0.03 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 0.0, + "confidence": 1.0, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 1.0, + "1": 0.0, + "2": 0.0 + } + }, + "credit": 0.0, + "adds_to_load": 0.0 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 0.64, + "confidence": 0.46, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.36, + "1": 0.64, + "2": 0.0 + } + }, + "credit": 0.32, + "adds_to_load": 0.0448 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 1.45, + "confidence": 0.32, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.55, + "2": 0.45 + } + }, + "credit": 0.275, + "adds_to_load": 0.022 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.02 + }, + "credit": 0.02, + "adds_to_load": 0.003 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.59 + }, + "credit": 0.41, + "adds_to_load": 0.0492 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.04 + }, + "credit": 0.04, + "adds_to_load": 0.0044 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.04 + }, + "credit": 0.04, + "adds_to_load": 0.0036 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.02 + }, + "credit": 0.02, + "adds_to_load": 0.0016 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.03 + }, + "credit": 0.97, + "adds_to_load": 0.0776 + } + } + }, + { + "number": 73725, + "title": "Fix Elasticsearch and OpenSearch response wrapper bugs", + "url": "https://github.com/apache/airflow/pull/73725", + "author": "subhramit", + "changed_files": 4, + "additions": 32, + "deletions": 8, + "review_load": 0.1792, + "tier": "trivial", + "size_floor": "trivial", + "raised_by_size": false, + "diff_coverage": 1.0, + "drivers": [ + "blast radius" + ], + "usage": { + "input_tokens": 2884, + "output_tokens": 285 + }, + "latency_ms": 287, + "questions": { + "change_kind": { + "label": "change kind", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "bugfix", + "confidence": 1.0, + "probabilities": { + "tests": 0.0, + "feature": 0.0, + "config": 0.0, + "docs": 0.0, + "dependency": 0.0, + "bugfix": 1.0, + "refactor": 0.0 + } + }, + "credit": null, + "adds_to_load": null + }, + "review_focus": { + "label": "review focus", + "type": "choice", + "weight": null, + "inverted": false, + "returned": { + "type": "choice", + "choice": "correctness", + "confidence": 0.73, + "probabilities": { + "compatibility": 0.01, + "security": 0.0, + "readability": 0.0, + "deployment": 0.0, + "tests": 0.22, + "correctness": 0.77 + } + }, + "credit": null, + "adds_to_load": null + }, + "blast_radius": { + "label": "blast radius", + "type": "score", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "score", + "score": 1.15, + "confidence": 0.77, + "legend": { + "0": "Confined to one file, or to documentation", + "1": "Confined to one module or feature area", + "2": "Reaches shared code paths that many callers depend on" + }, + "probabilities": { + "0": 0.0, + "1": 0.85, + "2": 0.15 + } + }, + "credit": 0.575, + "adds_to_load": 0.0862 + }, + "design_decisions": { + "label": "design decisions", + "type": "score", + "weight": 0.14, + "inverted": false, + "returned": { + "type": "score", + "score": 0.27, + "confidence": 0.59, + "legend": { + "0": { + "None": "the same edit repeated, or a value changed" + }, + "1": "Local choices inside one function or file", + "2": "New abstractions, interfaces, or control flow" + }, + "probabilities": { + "0": 0.75, + "1": 0.23, + "2": 0.02 + } + }, + "credit": 0.135, + "adds_to_load": 0.0189 + }, + "description_quality": { + "label": "description quality", + "type": "score", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "score", + "score": 1.35, + "confidence": 0.47, + "legend": { + "0": "Empty, or says nothing about what changed", + "1": "Names what changed", + "2": "Explains what changed, why, and how the author verified it" + }, + "probabilities": { + "0": 0.0, + "1": 0.65, + "2": 0.35 + } + }, + "credit": 0.325, + "adds_to_load": 0.026 + }, + "security_surface": { + "label": "security surface", + "type": "noul", + "weight": 0.15, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.05 + }, + "credit": 0.05, + "adds_to_load": 0.0075 + }, + "mechanical": { + "label": "mechanical", + "type": "noul", + "weight": 0.12, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.87 + }, + "credit": 0.13, + "adds_to_load": 0.0156 + }, + "breaking_change": { + "label": "breaking change", + "type": "noul", + "weight": 0.11, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.14 + }, + "credit": 0.14, + "adds_to_load": 0.0154 + }, + "scope_creep": { + "label": "scope creep", + "type": "noul", + "weight": 0.09, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.07 + }, + "credit": 0.07, + "adds_to_load": 0.0063 + }, + "infra_surface": { + "label": "infra surface", + "type": "noul", + "weight": 0.08, + "inverted": false, + "returned": { + "type": "noul", + "noul": 0.02 + }, + "credit": 0.02, + "adds_to_load": 0.0016 + }, + "has_tests": { + "label": "has tests", + "type": "noul", + "weight": 0.08, + "inverted": true, + "returned": { + "type": "noul", + "noul": 0.98 + }, + "credit": 0.02, + "adds_to_load": 0.0016 + } + } + } + ] +} diff --git a/samples/pr-triage/fetch_prs.py b/samples/pr-triage/fetch_prs.py new file mode 100644 index 0000000..5d9ccbf --- /dev/null +++ b/samples/pr-triage/fetch_prs.py @@ -0,0 +1,531 @@ +"""Build a triage dataset from one repository's pull requests. + +Reads the GitHub REST API and writes one JSON file holding, per pull request, +the title, the description, the base branch, the per-file stats and the patch +for every changed file. That file is the dataset pr_triage.py sends to Jev, and +committing it makes a triage run reproducible without hitting GitHub again. + +Needs no Jev key. Reads GITHUB_TOKEN or GH_TOKEN, and falls back to +`gh auth token` when neither is set. Unauthenticated calls get 60 an hour, and +one pull request costs two of them, so a token is worth having. + +Usage: + uv run fetch_prs.py owner/repo # 10 most recent open PRs + uv run fetch_prs.py owner/repo --limit 25 # the 25 most recent + uv run fetch_prs.py owner/repo --since 2026-08-01 # opened on or after a date + uv run fetch_prs.py owner/repo --all # every open PR +""" + +import argparse +import datetime +import json +import logging +import os +import pathlib +import re +import subprocess # nosec B404 - reads a token from the gh CLI, list form only +import sys +import urllib.error +import urllib.parse +import urllib.request + +# Configure logging with basicConfig +logging.basicConfig( + level=logging.INFO, # Set the log level to INFO + # Define log message format + format="%(asctime)s,p%(process)s,{%(filename)s:%(lineno)d},%(levelname)s,%(message)s", +) +logger = logging.getLogger(__name__) + +API_ROOT: str = "https://api.github.com" + +# The version header GitHub asks callers to pin. Without it the API is free to +# change shape under the sample. +API_VERSION: str = "2022-11-28" + +FETCH_TIMEOUT_SECONDS: int = 30 + +# GitHub's maximum, and the fewest round trips per repository. +PER_PAGE: int = 100 + +# Stop paginating here rather than walking a repository with 4,000 open pull +# requests. --all means all of them up to this. +MAX_PAGES: int = 20 + +# Where the environment carries a token, in the order checked. +TOKEN_ENV_NAMES: tuple[str, ...] = ("GITHUB_TOKEN", "GH_TOKEN") + +GH_TOKEN_TIMEOUT_SECONDS: int = 10 + +# One generated lockfile diff runs to hundreds of thousands of characters and +# would dominate the dataset on disk. Cutting each patch here keeps a dataset +# readable, and pr_triage.py applies its own, smaller budget when it builds the +# state it sends. +MAX_PATCH_CHARS: int = 40000 + +# How many recent pull requests to take when the caller names no selector. +DEFAULT_LIMIT: int = 10 + +DATASET_DIR: pathlib.Path = pathlib.Path(__file__).parent / "data" + +# `owner/repo`, which is what the API path wants and what `gh` prints. +REPO_PATTERN: re.Pattern[str] = re.compile(r"^[\w.-]+/[\w.-]+$") + + +def _token_from_gh_cli() -> str | None: + """Ask the gh CLI for the token it already holds. + + Returns: + The token, or None when gh is missing or logged out. + """ + try: + result = subprocess.run( # nosec B603 B607 - hardcoded command, no user input + ["gh", "auth", "token"], + capture_output=True, + text=True, + timeout=GH_TOKEN_TIMEOUT_SECONDS, + check=True, + ) + except FileNotFoundError: + logger.info("No gh CLI on PATH, so calling GitHub unauthenticated") + return None + except subprocess.TimeoutExpired: + logger.warning("gh auth token timed out, so calling GitHub unauthenticated") + return None + except subprocess.CalledProcessError: + logger.info("gh CLI is not logged in, so calling GitHub unauthenticated") + return None + + logger.info("Read a GitHub token from the gh CLI") + return result.stdout.strip() or None + + +def _token() -> str | None: + """Find a GitHub token, preferring the environment over the gh CLI. + + Returns: + The token, or None to call the API unauthenticated. + """ + for name in TOKEN_ENV_NAMES: + value = os.environ.get(name) + if value: + logger.info(f"Read a GitHub token from {name}") + return value + return _token_from_gh_cli() + + +def _get_json( + url: str, + token: str | None, +) -> list | dict: + """Fetch one API URL and parse the body as JSON. + + Args: + url: A full https URL under API_ROOT. + token: Bearer token, or None for an unauthenticated call. + + Returns: + The parsed body. + + Raises: + SystemExit: On a 401, 403 or 404, each of which has one likely cause + worth naming. + """ + headers = { + "Accept": "application/vnd.github+json", + "X-GitHub-Api-Version": API_VERSION, + "User-Agent": "jev-samples-pr-triage", + } + if token: + headers["Authorization"] = f"Bearer {token}" + + request = urllib.request.Request(url, headers=headers) # nosec B310 - https-only, built from API_ROOT + try: + with urllib.request.urlopen(request, timeout=FETCH_TIMEOUT_SECONDS) as response: # nosec B310 - https-only, built from API_ROOT + return json.loads(response.read().decode("utf-8")) + except urllib.error.HTTPError as error: + if error.code == 404: + sys.exit( + f"GitHub returned 404 for {url}\n check the repo name, and that you can see it" + ) + if error.code in (401, 403): + remaining = error.headers.get("x-ratelimit-remaining") + if remaining == "0": + sys.exit( + "GitHub rate limit exhausted. Set GITHUB_TOKEN, or wait for the window to reset." + ) + sys.exit( + f"GitHub returned {error.code} for {url}\n the token is missing, expired, or lacks access" + ) + raise + + +def _matches_since( + pull: dict, + since: datetime.date | None, +) -> bool: + """Say whether a pull request was opened on or after a date. + + Args: + pull: One entry from the pulls list endpoint. + since: The cutoff, or None to accept everything. + + Returns: + True when the pull request is in range. + """ + if since is None: + return True + created = datetime.datetime.fromisoformat(pull["created_at"]).date() + return created >= since + + +def _list_pulls( + repo: str, + token: str | None, + state: str, + limit: int | None, + since: datetime.date | None, +) -> list[dict]: + """List pull requests newest first, stopping as soon as the selector is met. + + The pulls endpoint takes no date filter, so the date cut happens here. Newest + first means the first pull request older than the cutoff ends the walk. + + Args: + repo: The repository as `owner/repo`. + token: Bearer token, or None. + state: open, closed or all. + limit: How many to keep, or None for every one the selector allows. + since: Keep only pull requests opened on or after this date. + + Returns: + The list entries, newest first. + """ + kept: list[dict] = [] + for page in range(1, MAX_PAGES + 1): + query = urllib.parse.urlencode( + { + "state": state, + "sort": "created", + "direction": "desc", + "per_page": PER_PAGE, + "page": page, + } + ) + logger.info(f"Listing {state} pull requests for {repo}, page {page}") + batch = _get_json(f"{API_ROOT}/repos/{repo}/pulls?{query}", token) + + for pull in batch: + if not _matches_since(pull, since): + logger.info(f"Reached #{pull['number']}, opened before the cutoff, so stopping") + return kept + kept.append(pull) + if limit is not None and len(kept) >= limit: + return kept + + if len(batch) < PER_PAGE: # the last page + return kept + + logger.warning(f"Stopped at {MAX_PAGES} pages, so the dataset may be short") + return kept + + +def _pull_files( + repo: str, + number: int, + token: str | None, +) -> tuple[list[dict], bool]: + """Fetch the changed files for one pull request, with their patches. + + Args: + repo: The repository as `owner/repo`. + number: The pull request number. + token: Bearer token, or None. + + Returns: + Tuple of (files, truncated). Each file carries its path, status, line + counts and patch. Truncated says whether MAX_PAGES cut the list short, + which GitHub also does at 3,000 files. + """ + files: list[dict] = [] + for page in range(1, MAX_PAGES + 1): + query = urllib.parse.urlencode({"per_page": PER_PAGE, "page": page}) + batch = _get_json(f"{API_ROOT}/repos/{repo}/pulls/{number}/files?{query}", token) + + for entry in batch: + # A binary file, or one GitHub declined to diff, carries no patch. + patch = entry.get("patch") or "" + files.append( + { + "path": entry["filename"], + "previous_path": entry.get("previous_filename"), + "status": entry["status"], + "additions": entry["additions"], + "deletions": entry["deletions"], + "patch": patch[:MAX_PATCH_CHARS], + "patch_truncated": len(patch) > MAX_PATCH_CHARS, + "has_patch": bool(patch), + } + ) + + if len(batch) < PER_PAGE: + return files, False + + return files, True + + +def _pull_record( + repo: str, + entry: dict, + token: str | None, +) -> dict: + """Turn one list entry into the dataset record, fetching what it lacks. + + The list endpoint carries no line counts, so this reads the pull request + itself for `changed_files`, `additions` and `deletions`, then its files. + + Args: + repo: The repository as `owner/repo`. + entry: One entry from the pulls list endpoint. + token: Bearer token, or None. + + Returns: + One dataset record. + """ + number = entry["number"] + logger.info(f"Fetching #{number}: {entry['title'][:60]}") + detail = _get_json(f"{API_ROOT}/repos/{repo}/pulls/{number}", token) + files, files_truncated = _pull_files(repo, number, token) + + return { + "number": number, + "title": entry["title"], + "body": entry.get("body") or "", + "author": (entry.get("user") or {}).get("login", ""), + "created_at": entry["created_at"], + "updated_at": entry["updated_at"], + "draft": bool(entry.get("draft")), + "labels": [label["name"] for label in entry.get("labels", [])], + "base": entry["base"]["ref"], + "url": entry["html_url"], + "changed_files": detail["changed_files"], + "additions": detail["additions"], + "deletions": detail["deletions"], + "files": files, + "files_truncated": files_truncated, + } + + +def _selector_slug( + state: str, + limit: int | None, + since: datetime.date | None, +) -> str: + """Name the selector, so two datasets for one repo sit in separate files. + + Args: + state: open, closed or all. + limit: How many were kept, or None. + since: The date cutoff, or None. + + Returns: + A filename fragment. + """ + if since is not None: + return f"{state}-since-{since.isoformat()}" + if limit is None: + return f"{state}-all" + return f"{state}-latest-{limit}" + + +def _dataset_path( + repo: str, + state: str, + limit: int | None, + since: datetime.date | None, +) -> pathlib.Path: + """Name the dataset file after the repository and the selector. + + Args: + repo: The repository as `owner/repo`. + state: open, closed or all. + limit: How many were kept, or None. + since: The date cutoff, or None. + + Returns: + The path to write, inside DATASET_DIR. + """ + DATASET_DIR.mkdir(exist_ok=True) + slug = re.sub(r"[^a-z0-9]+", "-", repo.lower()).strip("-") + return DATASET_DIR / f"{slug}-{_selector_slug(state, limit, since)}.json" + + +def parse_repo(target: str) -> str: + """Read `owner/repo` out of whatever the caller passed. + + Accepts the bare form and a GitHub URL, so a pasted browser address works. + + Args: + target: `owner/repo` or an https GitHub URL naming one. + + Returns: + The repository as `owner/repo`. + + Raises: + SystemExit: If the target names no repository. + """ + cleaned = target.strip().rstrip("/") + if cleaned.startswith("https://github.com/"): + parts = cleaned.removeprefix("https://github.com/").split("/") + if len(parts) >= 2: + cleaned = f"{parts[0]}/{parts[1]}" + + if not REPO_PATTERN.match(cleaned): + sys.exit(f"expected owner/repo, got {target!r}") + return cleaned + + +def parse_since(value: str | None) -> datetime.date | None: + """Read an ISO date, or a plain day count to go back. + + Args: + value: `YYYY-MM-DD`, or a number of days such as `30`, or None. + + Returns: + The cutoff date, or None when the caller passed nothing. + + Raises: + SystemExit: If the value is neither a date nor a day count. + """ + if value is None: + return None + if value.isdigit(): + # UTC, because the created_at timestamps this gets compared against are + # UTC. A local midnight would move the cutoff by a day either side. + today = datetime.datetime.now(datetime.UTC).date() + return today - datetime.timedelta(days=int(value)) + try: + return datetime.date.fromisoformat(value) + except ValueError: + sys.exit(f"--since wants YYYY-MM-DD or a number of days, got {value!r}") + + +def build_dataset( + repo: str, + state: str = "open", + limit: int | None = DEFAULT_LIMIT, + since: datetime.date | None = None, + out: pathlib.Path | None = None, +) -> pathlib.Path: + """Fetch pull requests and write one dataset file. + + Args: + repo: The repository as `owner/repo`. + state: open, closed or all. + limit: How many of the most recent to keep, or None for every one the + selector allows. + since: Keep only pull requests opened on or after this date. + out: Where to write, or None to name the file after the selector. + + Returns: + The path written. + """ + token = _token() + entries = _list_pulls(repo, token, state, limit, since) + logger.info(f"Selected {len(entries)} pull requests, now reading each one") + + records = [_pull_record(repo, entry, token) for entry in entries] + dataset = { + "repo": repo, + "fetched_at": datetime.datetime.now(datetime.UTC).isoformat(timespec="seconds"), + "selector": { + "state": state, + "limit": limit, + "since": since.isoformat() if since else None, + }, + "pull_requests": records, + } + + path = out or _dataset_path(repo, state, limit, since) + path.write_text(json.dumps(dataset, indent=2) + "\n", encoding="utf-8") + size_kb = path.stat().st_size / 1024 + logger.info(f"Wrote {len(records)} pull requests to {path} ({size_kb:.0f} KB)") + return path + + +def main() -> None: + """Parse arguments and build the dataset.""" + parser = argparse.ArgumentParser( + description="Build a triage dataset from one repository's pull requests.", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Example usage: + # The 10 most recent open pull requests + uv run fetch_prs.py agentic-community/mcp-gateway-registry + + # The 25 most recent, or every open one + uv run fetch_prs.py owner/repo --limit 25 + uv run fetch_prs.py owner/repo --all + + # Everything opened on or after a date, or in the last 30 days + uv run fetch_prs.py owner/repo --since 2026-08-01 + uv run fetch_prs.py owner/repo --since 30 + + # Closed pull requests, to check the triage against what review found + uv run fetch_prs.py owner/repo --state closed --limit 20 + +Reads GITHUB_TOKEN or GH_TOKEN, and falls back to `gh auth token`. Needs no Jev +key: pr_triage.py does the Jev call against the file this writes. +""", + ) + parser.add_argument( + "repo", + help="Repository as owner/repo, or a GitHub URL naming one", + ) + parser.add_argument( + "--limit", + type=int, + default=DEFAULT_LIMIT, + help=f"How many of the most recent to take (default: {DEFAULT_LIMIT})", + ) + parser.add_argument( + "--all", + action="store_true", + help="Take every pull request the selector allows, ignoring --limit", + ) + parser.add_argument( + "--since", + help="Keep pull requests opened on or after YYYY-MM-DD, or in the last N days", + ) + parser.add_argument( + "--state", + default="open", + choices=("open", "closed", "all"), + help="Which pull requests to list (default: open)", + ) + parser.add_argument( + "--out", + type=pathlib.Path, + help="Where to write the dataset (default: data/-.json)", + ) + parser.add_argument( + "--debug", + action="store_true", + help="Turn on debug logging", + ) + args = parser.parse_args() + + if args.debug: + logging.getLogger().setLevel(logging.DEBUG) + + build_dataset( + repo=parse_repo(args.repo), + state=args.state, + limit=None if args.all else args.limit, + since=parse_since(args.since), + out=args.out, + ) + + +if __name__ == "__main__": + main() diff --git a/samples/pr-triage/pr_triage.py b/samples/pr-triage/pr_triage.py new file mode 100644 index 0000000..9d37d87 --- /dev/null +++ b/samples/pr-triage/pr_triage.py @@ -0,0 +1,1117 @@ +"""Triage a repository's open pull requests, one Jev call each. + +Reads a dataset built by fetch_prs.py, or builds one on the spot, and asks Jev +eleven questions about every pull request in it: two Choices, three Scores and +six Nouls. Nine of them carry a weight, and the weighted average is the review +load, which lands each pull request in a tier from trivial to high. + +The payload lives in questions.yml. The judgment lives here: the weighting, the +size floor that stops a 58-file pull request being called trivial, and the +thresholds behind each tier's advice. + +Usage: + uv run pr_triage.py owner/repo # 10 most recent open PRs + uv run pr_triage.py owner/repo --limit 25 # the 25 most recent + uv run pr_triage.py owner/repo --since 2026-08-01 # opened on or after a date + uv run pr_triage.py --dataset data/some-repo.json # replay a saved dataset + uv run pr_triage.py --dataset data/x.json --explain 1693 # one PR, every question +""" + +import argparse +import datetime +import json +import logging +import os +import pathlib +import re +import sys +import time + +import yaml +from typesafe_sdk import ( + Choice, + ChoiceAnswer, + Noul, + NoulAnswer, + Score, + ScoreAnswer, + SystemOneResponse, + TypeSafeClient, +) + +from fetch_prs import build_dataset, parse_repo, parse_since + +# Configure logging with basicConfig +logging.basicConfig( + level=logging.INFO, # Set the log level to INFO + # Define log message format + format="%(asctime)s,p%(process)s,{%(filename)s:%(lineno)d},%(levelname)s,%(message)s", +) +logger = logging.getLogger(__name__) + +# The model pin, the state budgets and every question. Sits beside this file so +# `uv run pr_triage.py` finds it from any working directory. +PAYLOAD_FILE: pathlib.Path = pathlib.Path(__file__).parent / "questions.yml" + +# The SDK reads the key from this variable. +API_KEY_ENV: str = "TYPESAFE_API_KEY" + +# Where to look when the environment has no key: beside the sample, then at the +# repo root. The sample reads one variable out of the file and logs the path it +# came from, never the value. +ENV_FILES: tuple[pathlib.Path, ...] = ( + pathlib.Path(__file__).parent / ".env", + pathlib.Path(__file__).parents[2] / ".env", +) + +# Every run writes one JSON report here, named for the repo and the selector. +REPORT_DIR: pathlib.Path = pathlib.Path(__file__).parent / "data" + +# The tiers, cheapest review first. Index order is what makes the size floor +# below able to raise a tier without a second table. +TIERS: tuple[str, ...] = ("trivial", "low", "medium", "high") + +# What review load buys which tier, highest floor first. The cuts come from the +# cost of being wrong at each one: calling a medium pull request low costs a +# reviewer an hour, and calling a high one medium costs an incident. +LOAD_FLOORS: tuple[tuple[float, str], ...] = ( + (0.62, "high"), + (0.42, "medium"), + (0.22, "low"), + (0.00, "trivial"), +) + +# The lowest tier a pull request of a given size can land in, whatever Jev +# returned. Jev sees a truncated diff, so a 58-file change can read as nine +# repeated edits and score as mechanical. Size is arithmetic, Python does it, +# and it only ever raises a tier. +# +# Each entry is (files, lines, floor): more than this many files, or more than +# this many lines changed, and the tier starts here. Highest floor first. +SIZE_FLOORS: tuple[tuple[int, int, str], ...] = ( + (30, 1500, "high"), + (12, 500, "medium"), + (4, 150, "low"), +) + +# What to do with a pull request in each tier. The advice is the point of the +# triage, so it sits next to the tier rather than in a README. +TIER_ADVICE: dict[str, str] = { + "trivial": "merge on a glance: read the title, skim the diff, check that CI is green", + "low": "one reviewer, one pass, no meeting", + "medium": "one reviewer who knows this area, reading the whole diff", + "high": "a human reads this line by line, and the author walks them through it", +} + +# A word for each band of a Noul probability, highest floor first. Jev returns +# one number and no separate confidence, so 0.5 says the diff never settled the +# question either way. +NOUL_WORDS: tuple[tuple[float, str], ...] = ( + (0.9, "yes"), + (0.7, "probably yes"), + (0.3, "unsettled"), + (0.1, "probably no"), + (0.0, "no"), +) + +# Confidence under this reads as a coin toss, and the table says so. Jev reports +# confidence for a Choice and a Score; a Noul near 0.5 gets the same treatment. +UNSURE_BELOW: float = 0.5 +UNSURE_BAND: tuple[float, float] = (0.35, 0.65) + +# Repeat calls on one pull request moved a load by up to 0.03, so a value this +# near a tier cut can land on either side of it between runs. Within this +# distance the tier column says so, and the unsure markers widen by the same +# amount, because over-flagging doubt costs a reader nothing. +DEADBAND: float = 0.02 + +# A question contributing at least this much of the load gets named as a driver. +# Below it the line turns into a list of everything, which names nothing. +DRIVER_FLOOR: float = 0.05 + +# Coverage is the share of changed files whose patch fit in the diff budget. +# Under this, Jev read a sample of the change rather than the change, and the +# output says so: a load of 0.37 across 24 of 54 files is a weaker claim than the +# same number across all of them. This is why the size floor exists. +LOW_COVERAGE_BELOW: float = 0.70 + +# How many drivers to name per pull request. +DRIVER_COUNT: int = 3 + +TRIAGE_HEADER: list[str] = ["PR", "Files", "Lines", "Kind", "Load", "Tier", "Review focus", "Title"] + +DETAIL_HEADER: list[str] = [ + "Key", + "Label", + "Type", + "Jev returned", + "Weight", + "Credit", + "Adds", + "Reading", +] + +# Titles longer than this get cut in the table. The grouped section below it +# prints every title whole. +MAX_TITLE_CHARS: int = 58 + + +def _load_payload() -> tuple[dict, dict]: + """Read the settings and the questions from questions.yml. + + Returns: + Tuple of (settings, specs). Settings holds the model, the three state + budgets and the input-token price; specs holds each question entry keyed + by id, in file order. + + Raises: + SystemExit: If the file is missing a key the request needs. + """ + payload = yaml.safe_load(PAYLOAD_FILE.read_text(encoding="utf-8")) + wanted = { + "model", + "max_description_chars", + "max_file_list_chars", + "max_diff_chars", + "input_usd_per_million", + "questions", + } + missing = wanted - payload.keys() + if missing: + sys.exit(f"{PAYLOAD_FILE.name}: missing {', '.join(sorted(missing))}") + + settings = {key: payload[key] for key in wanted - {"questions"}} + return settings, payload["questions"] + + +def _build_question( + question_id: str, + spec: dict, +) -> Choice | Score | Noul: + """Turn one questions.yml entry into the SDK object for its type. + + Args: + question_id: The key the entry sits under, used in error messages. + spec: The entry itself, holding type, instructions and any criteria. + + Returns: + A Choice, Score or Noul built from the entry. + + Raises: + SystemExit: If the entry names a type Jev does not have. + """ + kind = spec.get("type") + if kind == "noul": + return Noul(instructions=spec["instructions"]) + if kind == "choice": + return Choice(instructions=spec["instructions"], criteria=spec["criteria"]) + if kind == "score": + return Score(instructions=spec["instructions"], criteria=spec["criteria"]) + sys.exit(f"{PAYLOAD_FILE.name}: question {question_id} has unknown type {kind!r}") + + +def _key_from_env_files() -> str | None: + """Read TYPESAFE_API_KEY out of the first .env file that sets it. + + Handles the two shapes a key file takes in practice: `NAME=value` and + `export NAME=value`, with or without quotes. + + Returns: + The key, or None when no file sets it. + """ + for path in ENV_FILES: + if not path.exists(): + continue + for line in path.read_text(encoding="utf-8").splitlines(): + name, _, value = line.strip().removeprefix("export ").partition("=") + if name.strip() != API_KEY_ENV: + continue + logger.info(f"Read {API_KEY_ENV} from {path}") + return value.strip().strip("\"'") + return None + + +def _require_api_key() -> None: + """Put the key in the environment before anything reaches the network. + + The environment wins. A .env file beside the sample or at the repo root is + the fallback, so an activated virtualenv with no exports still runs. + + Raises: + SystemExit: If neither the environment nor a .env file has the key. + """ + if os.environ.get(API_KEY_ENV): + return + + key = _key_from_env_files() + if not key: + looked = ", ".join(str(path) for path in ENV_FILES) + sys.exit( + f"{API_KEY_ENV} is not set, and no key sits in {looked}.\n" + f" export {API_KEY_ENV}=your-key" + ) + os.environ[API_KEY_ENV] = key + + +def _file_list_text( + files: list[dict], + budget: int, +) -> str: + """List every changed path with its line counts, one per line. + + Cheap in tokens and dense in signal: the paths alone say whether a change is + docs, tests, infrastructure or product code, and they survive a diff that + got truncated. + + Args: + files: File records from the dataset. + budget: Character budget for the whole list. + + Returns: + The list, cut at the budget on a line boundary. + """ + lines = [ + f"{entry['status']:9} +{entry['additions']:<5} -{entry['deletions']:<5} {entry['path']}" + for entry in files + ] + text = "\n".join(lines) + if len(text) <= budget: + return text + + # Cut on a line boundary, so the last entry a reader sees is a whole path + # rather than half of one. + kept = text[:budget].rsplit("\n", 1)[0] + shown = kept.count("\n") + 1 + return f"{kept}\n... and {len(files) - shown} more paths" + + +def _diff_text( + files: list[dict], + budget: int, +) -> tuple[str, int]: + """Assemble the patches into one diff, fitting as many whole files as it can. + + Smallest patches first. Triage asks how far a change reaches and whether the + same edit repeats, and both of those want breadth: twelve files read in full + answer them, where one 5,000-line lockfile answers neither. The file list + above carries the paths that did not fit, and the state says how many. + + Args: + files: File records from the dataset. + budget: Character budget for the whole diff. + + Returns: + Tuple of (diff, files shown). + """ + with_patch = [entry for entry in files if entry["has_patch"]] + chunks: list[str] = [] + used = 0 + + for entry in sorted(with_patch, key=lambda item: len(item["patch"])): + chunk = f"diff --git a/{entry['path']} b/{entry['path']}\n{entry['patch']}\n" + if used + len(chunk) > budget: + break + chunks.append(chunk) + used += len(chunk) + + return "".join(chunks), len(chunks) + + +def _build_state( + repo: str, + pull: dict, + settings: dict, +) -> tuple[dict, float]: + """Build the state for one pull request, each part in its own named field. + + The counts go in as a sentence rather than as numbers to compare. Jev cannot + do arithmetic, so the sentence is context and every threshold on a count + happens in Python. + + Args: + repo: The repository as `owner/repo`. + pull: One pull request record from the dataset. + settings: Settings from questions.yml, holding the three budgets. + + Returns: + Tuple of (state, coverage). Coverage is the share of changed files whose + patch fit the budget, which says how much of the change Jev read. + """ + files = pull["files"] + diff, shown = _diff_text(files, settings["max_diff_chars"]) + omitted = len(files) - shown + coverage = shown / len(files) if files else 1.0 + scope = ( + f"{pull['changed_files']} files changed, " + f"{pull['additions']} lines added, {pull['deletions']} removed" + ) + if omitted > 0: + scope += ( + f". The diff below holds {shown} of those files; {omitted} are listed without a patch" + ) + + state = { + "repo": repo, + "base_branch": pull["base"], + "title": pull["title"], + # Treat the description as untrusted. An author who writes "trivial, + # please merge" moves these answers, so the state names what the text is + # rather than presenting it as fact. + "description_written_by_the_author": (pull["body"] or "(no description)")[ + : settings["max_description_chars"] + ], + "scope": scope, + "changed_files": _file_list_text(files, settings["max_file_list_chars"]), + "diff": diff or "(no textual diff: binary files, or none GitHub would render)", + } + return state, coverage + + +def _credit( + spec: dict, + answer: NoulAnswer | ChoiceAnswer | ScoreAnswer, +) -> float: + """Turn one answer into what it contributed toward review load, 0 to 1. + + A Score divides by its own top level. A Noul is its probability. Either one + flips when the entry sets `invert`, so a mechanical diff and a thorough + description lower the load instead of raising it. A Choice reads the `credit` + table the entry carries, and carries none today. + + Args: + spec: The question entry from questions.yml. + answer: The answer Jev returned for it. + + Returns: + Credit from 0 to 1, where 1 is the most review. + """ + if spec["type"] == "noul": + value = answer.noul + elif spec["type"] == "score": + value = answer.score / (len(answer.legend) - 1) + else: + value = float(spec.get("credit", {}).get(answer.choice, 0.0)) + + return 1.0 - value if spec.get("invert") else value + + +def _weighted(specs: dict) -> dict: + """Pick out the questions that carry a weight, in file order. + + Args: + specs: Question entries from questions.yml, keyed by id. + + Returns: + The weighted entries, keyed by id. + """ + return {qid: spec for qid, spec in specs.items() if spec.get("weight")} + + +def _review_load( + answers: dict, + specs: dict, +) -> float: + """Weight every weighted answer into one number, from 0 to 1. + + Jev cannot do arithmetic, so the weighting happens here. Dividing by the + weights actually present means an edited weight needs no rebalancing. + + Args: + answers: Answers keyed by question id, as returned by Jev. + specs: Question entries from questions.yml, keyed by id. + + Returns: + The review load, from 0 to 1. + """ + weighted = _weighted(specs) + total = sum(spec["weight"] for spec in weighted.values()) + earned = sum(spec["weight"] * _credit(spec, answers[qid]) for qid, spec in weighted.items()) + return earned / total + + +def _contribution( + spec: dict, + answer: NoulAnswer | ChoiceAnswer | ScoreAnswer, + total_weight: float, +) -> float: + """Say how much of the review load one answer accounts for. + + These sum to the load across every weighted question, so the Adds column of + the detail table adds up to the number at the bottom of it. + + Args: + spec: The question entry from questions.yml. + answer: The answer Jev returned for it. + total_weight: The weights of every weighted question, summed. + + Returns: + The share of the load, from 0 to the question's weight. + """ + return spec["weight"] * _credit(spec, answer) / total_weight + + +def _size_floor( + changed_files: int, + lines: int, +) -> str: + """Name the lowest tier a pull request of this size can land in. + + Args: + changed_files: How many files the pull request touches. + lines: Additions plus deletions. + + Returns: + A tier name, "trivial" when the change is small enough to have no floor. + """ + for files_over, lines_over, floor in SIZE_FLOORS: + if changed_files > files_over or lines > lines_over: + return floor + return TIERS[0] + + +def _tier( + load: float, + changed_files: int, + lines: int, +) -> tuple[str, bool]: + """Place one pull request in a tier, and say whether size did the placing. + + Args: + load: The review load, from 0 to 1. + changed_files: How many files the pull request touches. + lines: Additions plus deletions. + + Returns: + Tuple of (tier, raised by size). + """ + from_load = next(tier for floor, tier in LOAD_FLOORS if load >= floor) + floor = _size_floor(changed_files, lines) + + if TIERS.index(floor) > TIERS.index(from_load): + return floor, True + return from_load, False + + +def _near_a_cut(load: float) -> bool: + """Say whether a load sits close enough to a tier cut to move between runs. + + Args: + load: The review load, from 0 to 1. + + Returns: + True when the load is within DEADBAND of any cut in LOAD_FLOORS. + """ + return any(floor > 0.0 and round(abs(load - floor), 6) <= DEADBAND for floor, _ in LOAD_FLOORS) + + +def _drivers( + answers: dict, + specs: dict, +) -> list[str]: + """Name the questions that account for most of the review load. + + Args: + answers: Answers keyed by question id, as returned by Jev. + specs: Question entries from questions.yml, keyed by id. + + Returns: + Up to DRIVER_COUNT labels, largest contribution first. + """ + weighted = _weighted(specs) + total = sum(spec["weight"] for spec in weighted.values()) + ranked = sorted( + ( + (_contribution(spec, answers[qid], total), spec["label"]) + for qid, spec in weighted.items() + ), + reverse=True, + ) + return [label for share, label in ranked[:DRIVER_COUNT] if share >= DRIVER_FLOOR] + + +def _describe_noul(value: float) -> str: + """Turn one Noul probability into the word it stands for. + + Args: + value: The probability Jev returned for the statement, from 0 to 1. + + Returns: + The word for the band the probability falls in. + """ + return next(wording for floor, wording in NOUL_WORDS if value >= floor) + + +def _returned(answer: NoulAnswer | ChoiceAnswer | ScoreAnswer) -> str: + """Say what Jev returned for one question, in its own terms. + + Args: + answer: The answer Jev returned. + + Returns: + The choice and its confidence, the score over its top level and its + confidence, or the bare Noul probability. + """ + if answer.type == "noul": + return f"{answer.noul:.2f}" + if answer.type == "score": + top = len(answer.legend) - 1 + return f"{answer.score:.1f} / {top}, confidence {answer.confidence:.2f}" + return f"{answer.choice}, confidence {answer.confidence:.2f}" + + +def _reading( + spec: dict, + answer: NoulAnswer | ChoiceAnswer | ScoreAnswer, +) -> str: + """Read one answer in plain words, and flag the ones Jev was unsure about. + + Every word here comes out of the answer: the band a Noul probability falls + in, and the legend Jev returns with a Score. + + Args: + spec: The question entry from questions.yml. + answer: The answer Jev returned for it. + + Returns: + The reading, with "unsure" appended when Jev hedged. + """ + if spec["type"] == "noul": + wording = _describe_noul(answer.noul) + # Both markers widen by the deadband, so a value wobbling around the cut + # reads the same way on every run. + unsure = UNSURE_BAND[0] - DEADBAND <= answer.noul <= UNSURE_BAND[1] + DEADBAND + elif spec["type"] == "score": + # The SDK keys legend and probabilities by integer level, counting from 0 + # in the order the criteria were written. + nearest = min(round(answer.score), len(answer.legend) - 1) + wording = f'level {nearest} "{answer.legend[nearest]}"' + unsure = answer.confidence < UNSURE_BELOW + DEADBAND + else: + wording = answer.choice + unsure = answer.confidence < UNSURE_BELOW + DEADBAND + + return f"{wording}, unsure" if unsure else wording + + +def _print_padded( + header: list[str], + rows: list[list[str]], +) -> None: + """Print one table padded to its widest cell per column. + + The padding makes it readable in a terminal, and the pipes keep it valid + markdown, so the same text pastes into a pull request. + + Args: + header: Column titles. + rows: Cells per row, matching header. + """ + widths = [max(len(cell) for cell in column) for column in zip(header, *rows, strict=True)] + + def line(cells: list[str]) -> str: + padded = (cell.ljust(width) for cell, width in zip(cells, widths, strict=True)) + return f"| {' | '.join(padded)} |" + + print(line(header)) + print(f"| {' | '.join('-' * width for width in widths)} |") + for row in rows: + print(line(row)) + + +def _triage_one( + client: TypeSafeClient, + repo: str, + pull: dict, + questions: dict, + specs: dict, + settings: dict, +) -> dict: + """Ask Jev every question about one pull request, then judge the answers. + + Args: + client: The SDK client, reused across pull requests. + repo: The repository as `owner/repo`. + pull: One pull request record from the dataset. + questions: The SDK question objects, keyed by id. + specs: Question entries from questions.yml, keyed by id. + settings: Settings from questions.yml. + + Returns: + One triage result, holding the answers, the load, the tier and the cost. + """ + state, coverage = _build_state(repo, pull, settings) + started = time.perf_counter() + response: SystemOneResponse = client.system_one( + model=settings["model"], + state=state, + questions=questions, + ) + latency_ms = round((time.perf_counter() - started) * 1000) + + answers = response.answers + lines = pull["additions"] + pull["deletions"] + load = _review_load(answers, specs) + tier, raised_by_size = _tier(load, pull["changed_files"], lines) + logger.info(f"#{pull['number']}: load {load:.2f}, tier {tier}, {latency_ms} ms") + + return { + "pull": pull, + "response": response, + "answers": answers, + "lines": lines, + "load": load, + "tier": tier, + "raised_by_size": raised_by_size, + "coverage": coverage, + "drivers": _drivers(answers, specs), + "latency_ms": latency_ms, + } + + +def _triage_row(result: dict) -> list[str]: + """Build the triage table row for one pull request. + + Args: + result: One triage result. + + Returns: + Cells matching TRIAGE_HEADER. + """ + pull = result["pull"] + title = pull["title"] + if len(title) > MAX_TITLE_CHARS: + title = title[: MAX_TITLE_CHARS - 3] + "..." + + tier = result["tier"] + if result["raised_by_size"]: + tier += " (size)" + elif _near_a_cut(result["load"]): + tier += " (on a cut)" + + return [ + f"#{pull['number']}", + str(pull["changed_files"]), + f"+{pull['additions']}/-{pull['deletions']}", + result["answers"]["change_kind"].choice, + f"{result['load']:.2f}", + tier, + result["answers"]["review_focus"].choice, + title, + ] + + +def _print_triage_table(results: list[dict]) -> None: + """Print one row per pull request, heaviest first. + + Args: + results: Triage results, in dataset order. + """ + ordered = sorted(results, key=lambda item: item["load"], reverse=True) + _print_padded(TRIAGE_HEADER, [_triage_row(result) for result in ordered]) + + +def _print_groups(results: list[dict]) -> None: + """Print the pull requests grouped by tier, with the advice for each tier. + + Args: + results: Triage results, in dataset order. + """ + for tier in reversed(TIERS): + members = sorted( + (result for result in results if result["tier"] == tier), + key=lambda item: item["load"], + reverse=True, + ) + if not members: + continue + + print(f"\n{tier.upper()} ({len(members)}) -> {TIER_ADVICE[tier]}") + for result in members: + pull = result["pull"] + drivers = ", ".join(result["drivers"]) or "nothing above the driver floor" + size_note = " [size floor]" if result["raised_by_size"] else "" + print(f" #{pull['number']:<6} load {result['load']:.2f}{size_note} {pull['title']}") + print(f" {drivers}") + if result["coverage"] < LOW_COVERAGE_BELOW: + print( + f" read from {result['coverage']:.0%} of the changed files, " + "so the load is a read on part of the diff" + ) + print(f" {pull['url']}") + + +def _print_detail( + result: dict, + specs: dict, +) -> None: + """Print every question for one pull request, and what each one contributed. + + Args: + result: One triage result. + specs: Question entries from questions.yml, keyed by id. + """ + pull = result["pull"] + answers = result["answers"] + weighted = _weighted(specs) + total = sum(spec["weight"] for spec in weighted.values()) + + print(f"\n#{pull['number']} {pull['title']}") + print(f"{pull['url']}\n") + + rows = [] + for question_id, spec in specs.items(): + answer = answers[question_id] + weight = spec.get("weight") + kind = spec["type"] + (" (inverted)" if spec.get("invert") else "") + if not weight: # routes the review rather than sizing it + rows.append( + [f"`{question_id}`", spec["label"], kind, _returned(answer), "", "", "", "routes"] + ) + continue + rows.append( + [ + f"`{question_id}`", + spec["label"], + kind, + _returned(answer), + f"{weight:.2f}", + f"{_credit(spec, answer):.2f}", + f"{_contribution(spec, answer, total):.3f}", + _reading(spec, answer), + ] + ) + + _print_padded(DETAIL_HEADER, rows) + print( + f"\n**Review load {result['load']:.2f} / 1.00**, the Adds column summed over " + f"{len(weighted)} questions carrying {total:.2f} of weight.\n" + f"Size floor for {pull['changed_files']} files and {result['lines']} lines: " + f"{_size_floor(pull['changed_files'], result['lines'])}. " + f"Tier: **{result['tier']}**" + f"{', set by size rather than by load' if result['raised_by_size'] else ''}.\n" + f"Jev read the patches of {result['coverage']:.0%} of the changed files; the rest " + "reached it as paths and line counts.\n" + "Weights live in questions.yml, so raise the one you care about and re-run." + ) + + +def _cost_usd( + results: list[dict], + settings: dict, +) -> float: + """Price a whole run from its input tokens. + + TypeSafe bills input tokens only, at the rate questions.yml records. + + Args: + results: Triage results. + settings: Settings from questions.yml. + + Returns: + The cost in dollars. + """ + tokens = sum(result["response"].usage.input_tokens for result in results) + return tokens * settings["input_usd_per_million"] / 1_000_000 + + +def _print_totals( + results: list[dict], + specs: dict, + settings: dict, +) -> None: + """Print what the run cost and how long it took. + + Args: + results: Triage results. + specs: Question entries from questions.yml, keyed by id. + settings: Settings from questions.yml. + """ + tokens = sum(result["response"].usage.input_tokens for result in results) + latency = sum(result["latency_ms"] for result in results) + calls = len(results) + thin = sum(1 for result in results if result["coverage"] < LOW_COVERAGE_BELOW) + print( + f"\n{calls} pull requests, {len(specs)} questions each, one call apiece. " + f"{tokens:,} input tokens, {latency:,} ms total, " + f"{round(latency / calls):,} ms per call on average, " + f"${_cost_usd(results, settings):.5f} at ${settings['input_usd_per_million']} " + "per million input tokens." + ) + if thin: + print( + f"{thin} of {calls} had patches too large to send whole, so Jev read part of the " + "diff and the paths of the rest. Those are the ones the size floor guards." + ) + + +def _report_path( + repo: str, + selector: dict, +) -> pathlib.Path: + """Name the triage report after the repository and the selector. + + Args: + repo: The repository as `owner/repo`. + selector: The selector recorded in the dataset. + + Returns: + The path to write, inside REPORT_DIR. + """ + REPORT_DIR.mkdir(exist_ok=True) + slug = re.sub(r"[^a-z0-9]+", "-", repo.lower()).strip("-") + if selector.get("since"): + tail = f"since-{selector['since']}" + elif selector.get("limit") is None: + tail = "all" + else: + tail = f"latest-{selector['limit']}" + return REPORT_DIR / f"{slug}-{selector.get('state', 'open')}-{tail}-triage.json" + + +def _write_report( + repo: str, + selector: dict, + results: list[dict], + specs: dict, + settings: dict, +) -> pathlib.Path: + """Write one JSON report: every answer as Jev sent it, plus the judgment. + + Args: + repo: The repository as `owner/repo`. + selector: The selector recorded in the dataset. + results: Triage results. + specs: Question entries from questions.yml, keyed by id. + settings: Settings from questions.yml. + + Returns: + The path written. + """ + weighted = _weighted(specs) + total = sum(spec["weight"] for spec in weighted.values()) + counts: dict[str, int] = {tier: 0 for tier in TIERS} + for result in results: + counts[result["tier"]] += 1 + + report = { + "repo": repo, + "selector": selector, + "triaged_at": datetime.datetime.now(datetime.UTC).isoformat(timespec="seconds"), + "model": settings["model"], + "questions_asked": len(specs), + "tier_counts": counts, + "cost_usd": round(_cost_usd(results, settings), 8), + "pull_requests": [ + { + "number": result["pull"]["number"], + "title": result["pull"]["title"], + "url": result["pull"]["url"], + "author": result["pull"]["author"], + "changed_files": result["pull"]["changed_files"], + "additions": result["pull"]["additions"], + "deletions": result["pull"]["deletions"], + "review_load": round(result["load"], 4), + "tier": result["tier"], + "size_floor": _size_floor(result["pull"]["changed_files"], result["lines"]), + "raised_by_size": result["raised_by_size"], + "diff_coverage": round(result["coverage"], 4), + "drivers": result["drivers"], + "usage": result["response"].usage.model_dump(mode="json"), + "latency_ms": result["latency_ms"], + "questions": { + qid: { + "label": spec["label"], + "type": spec["type"], + "weight": spec.get("weight"), + "inverted": bool(spec.get("invert")), + "returned": result["answers"][qid].model_dump(mode="json"), + "credit": round(_credit(spec, result["answers"][qid]), 4) + if spec.get("weight") + else None, + "adds_to_load": round(_contribution(spec, result["answers"][qid], total), 4) + if spec.get("weight") + else None, + } + for qid, spec in specs.items() + }, + } + for result in sorted(results, key=lambda item: item["load"], reverse=True) + ], + } + + path = _report_path(repo, selector) + path.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") + return path + + +def _load_dataset(path: pathlib.Path) -> dict: + """Read a dataset written by fetch_prs.py. + + Args: + path: The dataset file. + + Returns: + The parsed dataset. + + Raises: + SystemExit: If the file is missing or holds no pull requests. + """ + if not path.exists(): + sys.exit(f"no dataset at {path}\n build one: uv run fetch_prs.py owner/repo") + + dataset = json.loads(path.read_text(encoding="utf-8")) + if not dataset.get("pull_requests"): + sys.exit(f"{path} holds no pull requests") + + logger.info( + f"Read {len(dataset['pull_requests'])} pull requests from {path}, " + f"fetched {dataset.get('fetched_at', 'at an unrecorded time')}" + ) + return dataset + + +def triage( + dataset: dict, + explain: int | None = None, + verbose: bool = False, +) -> None: + """Triage every pull request in a dataset and print the result. + + Args: + dataset: A dataset as written by fetch_prs.py. + explain: A pull request number to print every question for, or None. + verbose: Print the raw answers for every pull request too. + """ + settings, specs = _load_payload() + _require_api_key() + + repo = dataset["repo"] + pulls = dataset["pull_requests"] + questions = {qid: _build_question(qid, spec) for qid, spec in specs.items()} + client = TypeSafeClient() + + logger.info(f"Asking Jev {len(specs)} questions about each of {len(pulls)} pull requests") + results = [_triage_one(client, repo, pull, questions, specs, settings) for pull in pulls] + + if verbose: + for result in results: + print(f"\nRaw response for #{result['pull']['number']}:") + print(json.dumps(result["response"].model_dump(mode="json"), indent=2)) + + print(f"\n## Triage: {repo}, {len(results)} pull requests\n") + _print_triage_table(results) + print( + "\nLoad is the weighted average of nine questions, 0 to 1. Tier comes from that load, " + f"raised when size demands it: over {SIZE_FLOORS[-1][0]} files or " + f"{SIZE_FLOORS[-1][1]} lines cannot be trivial, over {SIZE_FLOORS[0][0]} files or " + f"{SIZE_FLOORS[0][1]} lines is high whatever Jev returned." + ) + _print_groups(results) + + if explain is not None: + wanted = [result for result in results if result["pull"]["number"] == explain] + if not wanted: + print(f"\n#{explain} is not in this dataset, so there is nothing to explain.") + else: + _print_detail(wanted[0], specs) + + _print_totals(results, specs, settings) + print(f"Report: {_write_report(repo, dataset.get('selector', {}), results, specs, settings)}") + + +def main() -> None: + """Parse arguments, get a dataset, and run the triage.""" + parser = argparse.ArgumentParser( + description="Triage a repository's pull requests, one Jev call each.", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Example usage: + # The 10 most recent open pull requests, fetched and triaged + uv run pr_triage.py agentic-community/mcp-gateway-registry + + # Every open one, or everything opened in the last 30 days + uv run pr_triage.py owner/repo --all + uv run pr_triage.py owner/repo --since 30 + + # Replay a dataset already on disk, with no GitHub calls + uv run pr_triage.py --dataset data/owner-repo-open-all.json + + # Print every question and what it contributed for one pull request + uv run pr_triage.py --dataset data/owner-repo-open-all.json --explain 1693 + +The questions, the model pin and the state budgets live in questions.yml. The +tiers, the size floor and the advice per tier live in pr_triage.py. + +Reads TYPESAFE_API_KEY from the environment, or from .env beside this file or at +the repo root. Fetching also reads GITHUB_TOKEN, GH_TOKEN, or `gh auth token`. +""", + ) + parser.add_argument( + "repo", + nargs="?", + help="Repository as owner/repo, or a GitHub URL. Omit it when passing --dataset", + ) + parser.add_argument( + "--dataset", + type=pathlib.Path, + help="Triage a dataset already on disk instead of fetching one", + ) + parser.add_argument( + "--limit", + type=int, + default=10, + help="How many of the most recent to fetch (default: 10)", + ) + parser.add_argument( + "--all", + action="store_true", + help="Fetch every pull request the selector allows, ignoring --limit", + ) + parser.add_argument( + "--since", + help="Fetch pull requests opened on or after YYYY-MM-DD, or in the last N days", + ) + parser.add_argument( + "--state", + default="open", + choices=("open", "closed", "all"), + help="Which pull requests to fetch (default: open)", + ) + parser.add_argument( + "--explain", + type=int, + help="Print every question and what it contributed, for one pull request number", + ) + parser.add_argument( + "--debug", + action="store_true", + help="Turn on debug logging", + ) + parser.add_argument( + "--verbose", + action="store_true", + help="Pretty print the raw JSON Jev returned for every pull request", + ) + args = parser.parse_args() + + if args.debug: + logging.getLogger().setLevel(logging.DEBUG) + + if args.dataset: + dataset = _load_dataset(args.dataset) + elif args.repo: + path = build_dataset( + repo=parse_repo(args.repo), + state=args.state, + limit=None if args.all else args.limit, + since=parse_since(args.since), + ) + dataset = _load_dataset(path) + else: + parser.error("pass a repo to fetch, or --dataset to replay one") + + triage(dataset, args.explain, args.verbose) + + +if __name__ == "__main__": + main() diff --git a/samples/pr-triage/pyproject.toml b/samples/pr-triage/pyproject.toml new file mode 100644 index 0000000..2a6b438 --- /dev/null +++ b/samples/pr-triage/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "pr-triage" +version = "0.1.0" +description = "Triage a repository's pull requests into review tiers, one Jev call each" +requires-python = ">=3.11" +dependencies = [ + "pyyaml>=6.0,<7.0", + "typesafe-sdk>=0.7.0,<1.0.0", +] + +[tool.ruff] +line-length = 100 +target-version = "py311" diff --git a/samples/pr-triage/questions.yml b/samples/pr-triage/questions.yml new file mode 100644 index 0000000..9c7e081 --- /dev/null +++ b/samples/pr-triage/questions.yml @@ -0,0 +1,149 @@ +# Everything this sample sends to Jev, plus the weights it triages with: the +# model pin, the state budgets, and all eleven questions. +# +# Nine questions carry a `weight`, and review load is the weighted average of +# what they returned. Load runs 0 to 1, where 1 means a human should read every +# line. The weights here sum to 1, and the sample divides by their total anyway, +# so an edited weight needs no rebalancing. +# +# Weights say what a missed problem costs. Security surface and blast radius +# outrank everything, because a wrong merge there is an incident rather than a +# revert. Design decisions come next, because that is the part review exists for. +# A missing test and a thin description cost least: they slow a reviewer down +# without hiding a fault. +# +# `change_kind` and `review_focus` carry no weight. They route the review rather +# than sizing it. +# +# Adding a question means adding an entry here. Nothing else changes. + +# The SDKs default to jev-latest, and a silent upgrade would move answers under +# thresholds calibrated against an older version. +model: jev-1.13.0 + +# One budget per state field, so a 5,000-line lockfile diff cannot crowd out the +# description and a rambling description cannot crowd out the diff. These sum to +# about 8,000 tokens. Eleven questions each see the whole state plus themselves, +# which keeps every one of them inside the 32,000-token per-question limit. +max_description_chars: 4000 +max_file_list_chars: 4000 +max_diff_chars: 24000 + +# TypeSafe's published price for input tokens, September 2026. Output tokens are +# counted and not charged, and the reports use this to price each run. +input_usd_per_million: 0.042 + +# Each question asks about one thing, so no answer has to cover two claims. The +# order here is the order the sample prints them. +questions: + # No weight: this one names what kind of change it is, which reads next to the + # tier rather than feeding it. A docs change and a schema migration can carry + # the same load for different reasons. + change_kind: + type: choice + label: change kind + instructions: What kind of change this pull request makes + criteria: + docs: Documentation, comments, or README text + tests: Test files, fixtures, or test infrastructure + dependency: Dependency versions, lockfiles, or build tooling + config: Configuration, deployment manifests, or CI workflows + bugfix: A correction to behaviour that was wrong + feature: New behaviour that did not exist before + refactor: Rearranged code that behaves the same way afterward + + # No weight either: this one says where a reviewer should start. + review_focus: + type: choice + label: review focus + instructions: What a reviewer should check first on this pull request + criteria: + correctness: Whether the new logic does what it claims + security: Authentication, authorization, secrets, or input handling + compatibility: Whether existing callers and deployments keep working + tests: Whether the change is covered, and whether the tests assert anything + deployment: Whether this rolls out and rolls back safely + readability: Naming, structure, and whether the next reader follows it + + # Each score rubric runs 0 to 2 with evenly spaced steps, so dividing by the + # top level gives an honest fraction. + blast_radius: + type: score + label: blast radius + weight: 0.15 + instructions: How far the effects of this change reach through the codebase + criteria: + - Confined to one file, or to documentation + - Confined to one module or feature area + - Reaches shared code paths that many callers depend on + + design_decisions: + type: score + label: design decisions + weight: 0.14 + instructions: How many judgment calls a reviewer would have to agree with + criteria: + - None: the same edit repeated, or a value changed + - Local choices inside one function or file + - New abstractions, interfaces, or control flow + + # Inverted: a good description lowers the load, because the reviewer starts + # with the author's reasoning instead of reconstructing it from the diff. + description_quality: + type: score + label: description quality + weight: 0.08 + invert: true + instructions: How well the description accounts for the change + criteria: + - Empty, or says nothing about what changed + - Names what changed + - Explains what changed, why, and how the author verified it + + security_surface: + type: noul + label: security surface + weight: 0.15 + instructions: The change touches authentication, authorization, secrets, tokens, or input validation + + # Inverted: a mechanical diff is the cheapest kind to merge, because checking + # one instance checks them all. + # + # Asking whether the change is "simple" asks Jev to judge difficulty, which + # varies by reader. This asks about the shape of the diff instead, which the + # diff settles. + mechanical: + type: noul + label: mechanical + weight: 0.12 + invert: true + instructions: The diff repeats one kind of edit, with no separate decision per file + + breaking_change: + type: noul + label: breaking change + weight: 0.11 + instructions: The change alters a public API, config key, command-line flag, or stored data shape that existing callers depend on + + # A diff doing more than it says costs a reviewer their bearings, and it is the + # one problem the description cannot warn them about. + scope_creep: + type: noul + label: scope creep + weight: 0.09 + instructions: The diff changes things the title and description never mention + + infra_surface: + type: noul + label: infra surface + weight: 0.08 + instructions: The change touches deployment, infrastructure, container images, or CI configuration + + # Inverted: tests that exercise the change lower the load, because the reviewer + # can read what the author expected instead of guessing at it. + has_tests: + type: noul + label: has tests + weight: 0.08 + invert: true + instructions: The diff adds or updates tests that exercise the behaviour it changes diff --git a/samples/pr-triage/uv.lock b/samples/pr-triage/uv.lock new file mode 100644 index 0000000..5325257 --- /dev/null +++ b/samples/pr-triage/uv.lock @@ -0,0 +1,328 @@ +version = 1 +revision = 3 +requires-python = ">=3.11" +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "python_full_version < '3.12' or sys_platform != 'emscripten'", +] + +[[package]] +name = "annotated-types" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" }, +] + +[[package]] +name = "anyio" +version = "4.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna", marker = "python_full_version < '3.12' or sys_platform != 'emscripten'" }, + { name = "typing-extensions", marker = "(python_full_version < '3.15' and sys_platform != 'emscripten') or (python_full_version < '3.12' and sys_platform == 'emscripten')" }, +] +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" } +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" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore2" +version = "2.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11", marker = "python_full_version < '3.12' or sys_platform != 'emscripten'" }, + { name = "truststore", marker = "python_full_version < '3.12' or sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/f3/1db7aa2bc2524062192bb0e0323969492d1883152a232fe36eea65f4e35c/httpcore2-2.13.1.tar.gz", hash = "sha256:e0aa977abe17e69a3b820a24542a6fa88702676d83880b8d194dcd18408e5103", size = 68071, upload-time = "2026-09-23T07:47:22.372Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/ba/a4568248771ce81957bfb7cc600264a40fbcda092391ee1c415c50be4bea/httpcore2-2.13.1-py3-none-any.whl", hash = "sha256:e1e05d4f25f7d7d496bfb96748f6f4b67657b03da069b3a68c36069f3db73d0a", size = 83423, upload-time = "2026-09-23T07:47:19.365Z" }, +] + +[[package]] +name = "httpx2" +version = "2.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "sys_platform != 'emscripten'" }, + { name = "httpcore2", marker = "sys_platform != 'emscripten'" }, + { name = "httpx2-jsfetch", marker = "python_full_version >= '3.12' and sys_platform == 'emscripten'" }, + { name = "idna" }, + { name = "truststore", marker = "sys_platform != 'emscripten'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/44/474bef2a0e9d90f1715d32cb98b0738695ca17ba324095fb2497ed7fbd59/httpx2-2.13.1.tar.gz", hash = "sha256:e48744a19e3af5ee48313d0ce5fe941d5422fae5705ea922a4aabf94d7800dfa", size = 100405, upload-time = "2026-09-23T07:47:23.052Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/9c/6fe8931fd9f381042a9e4c7d5a7b4cbf7016b252bec0c99a49fce42c3326/httpx2-2.13.1-py3-none-any.whl", hash = "sha256:6dff50fabc270ee5fd25d845d0b078ed20564579744d6d962850975996d2f9a4", size = 95597, upload-time = "2026-09-23T07:47:20.995Z" }, +] + +[[package]] +name = "httpx2-jsfetch" +version = "1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/0e5636363151a2a1795e0a77617168b9ca438e1748ec05fc9b5687f93d64/httpx2_jsfetch-1.0.tar.gz", hash = "sha256:70a0e3eabfef7cce5ad9c629f7d01ca05e418f586646f4ddf14782e4c1454c60", size = 6872, upload-time = "2026-08-07T00:13:07.492Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl", hash = "sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32", size = 6382, upload-time = "2026-08-07T00:13:06.567Z" }, +] + +[[package]] +name = "idna" +version = "3.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/08/8eea9d4b8302028f3abb2c0813953f7aec26d33b7a8960ed760e65ff29fa/idna-3.20.tar.gz", hash = "sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44", size = 216463, upload-time = "2026-09-17T14:11:04.752Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" }, +] + +[[package]] +name = "pr-triage" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "pyyaml" }, + { name = "typesafe-sdk" }, +] + +[package.metadata] +requires-dist = [ + { name = "pyyaml", specifier = ">=6.0,<7.0" }, + { name = "typesafe-sdk", specifier = ">=0.7.0,<1.0.0" }, +] + +[[package]] +name = "pydantic" +version = "2.13.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/ef/fc4f868f4e2cee79f863883abffceff107875f569b848507319842d2a681/pydantic-2.13.5.tar.gz", hash = "sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08", size = 845750, upload-time = "2026-08-28T14:04:00.916Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/47/c95ffc2009878c7aac0c5e08528022dcb885933252a88b5f170058014464/pydantic-2.13.5-py3-none-any.whl", hash = "sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73", size = 472589, upload-time = "2026-08-28T14:03:59.136Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/f9/8a06bea35ef8daf588f707784c973a7046e0034c8d8cfb08828eeffb8b75/pydantic_core-2.46.5.tar.gz", hash = "sha256:10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc", size = 472262, upload-time = "2026-08-28T10:01:31.677Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/b6/81d2d19ea0be2c03664381b59f65fa72fc7969decedae00bc2c4ad835708/pydantic_core-2.46.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a1dee1b804ff4d11c663636cf15d2ea47e9f79cd56c033fb1cbf08924842a48f", size = 2074737, upload-time = "2026-08-28T09:57:57.711Z" }, + { url = "https://files.pythonhosted.org/packages/0c/18/b70da8300e292df4099684ea11b1958043580d2f50d2dc8bf7e542bdd84a/pydantic_core-2.46.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d625a186a65201c23a9e3b8ed9c47e90a026e03256608cc91851c6709096844f", size = 1921751, upload-time = "2026-08-28T09:57:59.265Z" }, + { url = "https://files.pythonhosted.org/packages/e7/1a/0d590341b6ffa4b4aca83508e6b8db4761aaeacfc15a25ca3815876d4797/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f8507560a9284e1370bb048ed4282012fbef4e8d109875b95e884d228552061", size = 1948231, upload-time = "2026-08-28T09:58:00.678Z" }, + { url = "https://files.pythonhosted.org/packages/7d/1d/02eb35761c51f2f7b1b042d6ab4cda6600f0c8c88a2243b3f734376201e5/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f93c5fe914d75fbec9a49209b00da5f08e9e467d69da2b1510c81940cfd10be", size = 2020708, upload-time = "2026-08-28T09:58:02.267Z" }, + { url = "https://files.pythonhosted.org/packages/4a/ea/f86073830e35d508cc8ddf9c3d9e6e6840fcb88d34bf726b0b4710186f27/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aca6c767f552b21b10f774aeac128e828eafb796adfa1b666a18bf6321453c3a", size = 2194914, upload-time = "2026-08-28T09:58:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/bb/d7/fc36240d7791ce90939e51608568c33bfdae26202016f9770c229a487d86/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:701b2e04b560eeb4bddf7a25ab8ca476176e34fdbd9a0e18196f0d12d4685f0b", size = 2235622, upload-time = "2026-08-28T09:58:05.516Z" }, + { url = "https://files.pythonhosted.org/packages/cf/bc/3fa2d76b83162820a17da7f645b28d1cba99fc8e1e5fc6517067ec450fa1/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49776eab08766a08dfff7012f8b422dcd7e25e43b316eedf0477c24fcfa84b7c", size = 2062091, upload-time = "2026-08-28T09:58:07.135Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/095d557bb492c90cd8a70a6dd048bf793d433d03d86c81c11e912e4cd049/pydantic_core-2.46.5-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:a2468d93d181667a7abd66e1b64bb9f76f361b0fef8faddf687456453576f5ee", size = 2089904, upload-time = "2026-08-28T09:58:08.814Z" }, + { url = "https://files.pythonhosted.org/packages/24/98/7b76b1ad10a19a617a52aaa1d80e159115af939b095e86f8e756fd52e0df/pydantic_core-2.46.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:53feb344243bb9510a9dec7bf3cf1b64d88a98af5dc7872a5160465f8b198c8e", size = 2132244, upload-time = "2026-08-28T09:58:10.435Z" }, + { url = "https://files.pythonhosted.org/packages/20/32/7d6ca365fadba186a0c8f85de1a701663bce81efd309d9479be58687622f/pydantic_core-2.46.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cd5214352ae68f3b5e9af7768bdc5253695ee069675db3480518420b3be881f2", size = 2143901, upload-time = "2026-08-28T09:58:12.033Z" }, + { url = "https://files.pythonhosted.org/packages/f8/09/eb9a6aa57f22fd1541a9c0aa2a1f3aeef3ec65347d33e10a6da2f43e0ee9/pydantic_core-2.46.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:9432f3598db432cb51c5b37fdbf29a60fcccc79e30d37a05022776a6bc4ab689", size = 2299425, upload-time = "2026-08-28T09:58:13.614Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f9/548a5bb9d4ba8cd26e26daf48052236f6b38bb61e7b7241fbc3c995719eb/pydantic_core-2.46.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8feeac04b5794e513e710af2f9c87d49f31a6dc47967bb264a1fed61a8989bec", size = 2318566, upload-time = "2026-08-28T09:58:15.199Z" }, + { url = "https://files.pythonhosted.org/packages/4a/20/06454d18834c02c406c9133f1a3b485305fd9ee984f9636c2f730bef6a9d/pydantic_core-2.46.5-cp311-cp311-win32.whl", hash = "sha256:892a881d5f68c2b9ea304b7a6c2c60d9343df578a311b0f86b94bc8f1ffe8129", size = 1954258, upload-time = "2026-08-28T09:58:16.813Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c2/718b9deb4b72453b5d8c7447a3b14cb77bef36917ef5f514e0948a4096a0/pydantic_core-2.46.5-cp311-cp311-win_amd64.whl", hash = "sha256:40375c2d05acec10323e45dfe2077ac44bc74659008614af5069034e2cfc781c", size = 2041030, upload-time = "2026-08-28T09:58:18.288Z" }, + { url = "https://files.pythonhosted.org/packages/67/ea/c1d1a5b72d6e1ff7f377a4d9199f6591f095beb5b409a8a5d89f7238d939/pydantic_core-2.46.5-cp311-cp311-win_arm64.whl", hash = "sha256:28a6a556cd3b6066bea827857f9d9cce027c96f776e512f544a581f9e42161f8", size = 2009234, upload-time = "2026-08-28T09:58:19.929Z" }, + { url = "https://files.pythonhosted.org/packages/82/3f/76358795aa7a8c6d4f36e2cb828ad1c90ee118e1393a9281664f5aade9d4/pydantic_core-2.46.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b9fe6fb92520e3fd61f2e49000b6911b188824f089b75973ea06d6267f0b476d", size = 2076516, upload-time = "2026-08-28T09:58:21.576Z" }, + { url = "https://files.pythonhosted.org/packages/db/50/26b091836076ce4cb2fac264186936acc069e0595772cfd02a563bc4761a/pydantic_core-2.46.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a39ac25a9a2fa4072efdb429833c4a4c8009a51ff9eea3eeae131713cd27991e", size = 1922874, upload-time = "2026-08-28T09:58:23.766Z" }, + { url = "https://files.pythonhosted.org/packages/09/f0/2a8ce3849e299d44e2d2c196b6082643a3235565a735cb51db7a6261f614/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fdc8b93a41521988916eeaa271173fcca7fa0803d62f87675aac8dcec1c8e29", size = 1951772, upload-time = "2026-08-28T09:58:25.435Z" }, + { url = "https://files.pythonhosted.org/packages/87/46/ac0dc8bdd9e6048183a14eb127764e7ad9240021c17513074a4711b0e31e/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b98134087d9de723658d17a42c7d0da8d6e2ef08015dee7dc93889047315f5e4", size = 2031832, upload-time = "2026-08-28T09:58:27.102Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c2/339de5bef7be36301a2231eaa52e62163742c2281f11b5f4892bc79785cd/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e652ab17569c94bff5475520f907b7148b8c24036a8ebbe5cf7cf7493d28579a", size = 2208645, upload-time = "2026-08-28T09:58:28.948Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a0/9ff22b797724262da14427abaed4dd1d864a139693fc5e7809114376a716/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d925f3d9afd05a8c0fb3a1031463a8d59ebe5e2afad297e29c78be19e13b4e62", size = 2265935, upload-time = "2026-08-28T09:58:30.625Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a4/eb9409ec0736e50aa70a412f16c204ed149516846912f7e6724d4c73ee53/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fc5be0abd4a407e200d844b404e33639a554e7bd0d448e7b9ae181be4789ac2", size = 2066284, upload-time = "2026-08-28T09:58:32.289Z" }, + { url = "https://files.pythonhosted.org/packages/c0/02/7f6156ffc926857f1c37c07d9a388682865a81830ab6a1b637082c25e399/pydantic_core-2.46.5-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:816ff0a6550ffc06c098ccd2e0698600f9aa7da192a79eaa6f9af504a35db869", size = 2105889, upload-time = "2026-08-28T09:58:33.986Z" }, + { url = "https://files.pythonhosted.org/packages/92/b1/e781d357ebe09fc929f995700f1b3503e8897f1cece183ecb1300d4d67e9/pydantic_core-2.46.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c7ea57fc63aa7da93a1bd2d644e6577befae10c52c4e36377635eea1056a74f5", size = 2158006, upload-time = "2026-08-28T09:58:35.647Z" }, + { url = "https://files.pythonhosted.org/packages/70/0a/644597d84ab400e50609c192120b85c9681c22d3a20461b9060a79be0a7a/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:efd62a42486f1bda5d24cb4f63d15a3c7768375fe83d36f9417b4ad7a2fb20b3", size = 2158408, upload-time = "2026-08-28T09:58:37.38Z" }, + { url = "https://files.pythonhosted.org/packages/1e/ee/ca3b7b3a4b3769ffe9ce9432a7c9be755de9593a46d3b0d54d0409323e44/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:2bc9419666990c06d7397831f2126a1ecc3594aaa3ff7de5bf2d066802f4e07b", size = 2309609, upload-time = "2026-08-28T09:58:39.22Z" }, + { url = "https://files.pythonhosted.org/packages/ce/52/39fa1f451486019524ca685020390e7ca351832fd874530ba30c8628e6dc/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:18a09e1e1011b462f2e32774f25859ef1223d5c2b0546a633cf56654710721e0", size = 2342618, upload-time = "2026-08-28T09:58:40.89Z" }, + { url = "https://files.pythonhosted.org/packages/81/5e/468fc630568c61dcef3cd47ad32ffbeed9af643f49208d1ea86ab4f890c4/pydantic_core-2.46.5-cp312-cp312-win32.whl", hash = "sha256:5cb482e9e84c851f4e623fe4acc1ced89168cf1fe18f7089db4548c8f5bbb65b", size = 1939475, upload-time = "2026-08-28T09:58:42.591Z" }, + { url = "https://files.pythonhosted.org/packages/cf/c9/4c19f41b84cf6b622a72fbeed7665b25d47a187d68d47d0d430c07f23268/pydantic_core-2.46.5-cp312-cp312-win_amd64.whl", hash = "sha256:5e81740c09e310f5aa5cbd3e434a01c154d4bef93241c7877b39f211d2b78ba8", size = 2043140, upload-time = "2026-08-28T09:58:44.272Z" }, + { url = "https://files.pythonhosted.org/packages/af/dd/0c1a050299147c746e5256db16d645ab5efd4f78c59937d581a0524e74a2/pydantic_core-2.46.5-cp312-cp312-win_arm64.whl", hash = "sha256:f7b0ec93a2893de856652154d73b7ba622f26fa97726487dcac373de5f4c6084", size = 1997729, upload-time = "2026-08-28T09:58:46.13Z" }, + { url = "https://files.pythonhosted.org/packages/f5/37/5abe39a8372a61d3dc3c1338fc504281c01b32fdb3169cd7187153b56d3e/pydantic_core-2.46.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:b7ca9034437b6022f941f4857459562ee00a560b97e7cce8a0ec5a74fc6766e0", size = 2075885, upload-time = "2026-08-28T09:58:47.856Z" }, + { url = "https://files.pythonhosted.org/packages/21/43/6323b1f8b217780454c61304bcd2b38ae4762f50754414124603ccc90bb2/pydantic_core-2.46.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f332f0e72a5a0400141f830744e141bf9f97917878dbe968669e8a7fefea78ff", size = 1922768, upload-time = "2026-08-28T09:58:49.58Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a3/c05ca796e1197618a774b01e596aeedfefc2f7d8c01ae3054e910b120e8a/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:193375f3548919d3f0b60936ca113ada3e38f264f91b9b8e0508efaad57be931", size = 1951241, upload-time = "2026-08-28T09:58:51.511Z" }, + { url = "https://files.pythonhosted.org/packages/68/32/33bc39ac705c52cffc908e8389f9754fdb208aea5c69cceddf4eb3ce99af/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:79bdfa52f843137045b2d081cc05c120ba6665d29b7559c2c47690906f39279f", size = 2031975, upload-time = "2026-08-28T09:58:53.166Z" }, + { url = "https://files.pythonhosted.org/packages/b0/70/2333e885c0f6a67bc105c5916965dac9b57f2718ee20d81d1a06a4ebdc13/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:24922243639cbdac66c75fcb6fd6495a9cb52b213d62f9a0d16f0310b1ff8038", size = 2208542, upload-time = "2026-08-28T09:58:55.017Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ea/296debfb4264207bbda5936133892e027c0a58875ad53ebd512fba8ec3a2/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c76fe65e607be28c7fd4d56fc3c42b1583aa058ce3408b7ad0fd540171d31f9f", size = 2264692, upload-time = "2026-08-28T09:58:56.767Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f2/9e4de77a6271e07a76d2d58b11c091a979c191ed2939bf80067568b369d2/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f7b393a8b3da82f5c1fc0751e6d01ac6c55b93c18226a60bdfba4a724efafd1", size = 2066633, upload-time = "2026-08-28T09:58:58.531Z" }, + { url = "https://files.pythonhosted.org/packages/8d/db/f9e9d0c97445987b2084823d5c240de88087338f04fc2cfaa2df186b8049/pydantic_core-2.46.5-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:7ac031912d54f3d83ef3b3eb98dfabc1608802e2202263d25957eeed40b94761", size = 2105235, upload-time = "2026-08-28T09:59:00.421Z" }, + { url = "https://files.pythonhosted.org/packages/07/c5/79169b047b3b2c3e99e04bc76372af9637e0bf6db638274fa927df96369e/pydantic_core-2.46.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:837b396ca3d7b74091ca623f6cbd8351bd42d670a79c2683e79fb089f06a2de5", size = 2157367, upload-time = "2026-08-28T09:59:02.442Z" }, + { url = "https://files.pythonhosted.org/packages/26/b5/ba6057afb7c291bd449f51b867f95aef2072941c4ce4e5c31d6ffd132d3b/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:5ee239d575f80b08eca11f6e20f90c4c695de7825c67eefe6091fbf20dda648e", size = 2158420, upload-time = "2026-08-28T09:59:04.2Z" }, + { url = "https://files.pythonhosted.org/packages/6e/28/2057abecaafdc22912afa819603a51f0a62d40643b7c4871c51721fea9be/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:e80675d75ae2cd14372cb65cad5400d9347a3d3f6c13000183f22dfd027283ed", size = 2309588, upload-time = "2026-08-28T09:59:06.048Z" }, + { url = "https://files.pythonhosted.org/packages/71/9d/881156dc404e27479c4246128d73538464cab4a239bec61995e227644c30/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9c4b71f10dd532fb7a5cbc8f58707779e64f03a258c2bf8bfbaecfcd9970b519", size = 2341866, upload-time = "2026-08-28T09:59:08.539Z" }, + { url = "https://files.pythonhosted.org/packages/5a/38/d66f443a259f84d13babdceae568e572b0ed26da17ca5d0a649ebb110a67/pydantic_core-2.46.5-cp313-cp313-win32.whl", hash = "sha256:97bf8de4d541598c94a59344eeb988a94c08ff76b5723c41f6567ec18c7892ea", size = 1938580, upload-time = "2026-08-28T09:59:10.402Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1e/1d5371213f4cc9a7ed70c0bfcc7911de22311ee99a662a56077d7292d2ac/pydantic_core-2.46.5-cp313-cp313-win_amd64.whl", hash = "sha256:15f4a94963c95accac15b7b657bb177d3ad82bb90b0d0526d9a9b85079925db5", size = 2041980, upload-time = "2026-08-28T09:59:12.396Z" }, + { url = "https://files.pythonhosted.org/packages/5a/48/4222d90b1c67568bace4dec6dca6271449c66de3595d72b6d098f5fde597/pydantic_core-2.46.5-cp313-cp313-win_arm64.whl", hash = "sha256:d22a945598fb91236b4dd793a6e42e4f3dd7740bb5aace5ebd7d4c08d13bb575", size = 1997213, upload-time = "2026-08-28T09:59:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8a/14596f2a8367da50cf7cbac48169ee5d9c8e11d486a3b527082384630c72/pydantic_core-2.46.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c1c43ad4339643d70ebb8124e1305a7dab423001eff58bb41a0f731adbc98355", size = 2074081, upload-time = "2026-08-28T09:59:16.141Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d5/d8a4eb6d6c7f66b91dd37c576d76e9e60fba900caf5372c17bcf949febc2/pydantic_core-2.46.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1a353f84de772f423b5ffb11d7ae352fbbef0f446f3c0b0af0f8236d7233606e", size = 1920497, upload-time = "2026-08-28T09:59:18.065Z" }, + { url = "https://files.pythonhosted.org/packages/8e/26/092079428f86e927e030b2c0ced87df69dbb1c875cdeaa67bf42ea2be746/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5086029a57366b8cf81b130a43908738095c270c21a8d7f0e8bdfdb89718e2f3", size = 1952130, upload-time = "2026-08-28T09:59:20.476Z" }, + { url = "https://files.pythonhosted.org/packages/08/c3/8ec0e290a9ebaebd64047bf5fda94be835c6b1551b02437e4b76778fbcd7/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46c25dda9d092a06c08db76ffe0a197107904d0dfac653f7d5306bbcd6d6119c", size = 2026371, upload-time = "2026-08-28T09:59:22.227Z" }, + { url = "https://files.pythonhosted.org/packages/01/72/4fd20ad520fb8da0157f95b27a7eb05a72790ef08138e7701ac972c342ea/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37ea7b83c935e5b0d68c9449b82651accf78a10828b2c02b2f2d9e9496446c21", size = 2202822, upload-time = "2026-08-28T09:59:24.277Z" }, + { url = "https://files.pythonhosted.org/packages/31/b0/d16e0771206b29314f0d52198b720be21e8a99ab2bf11e3bc0d7c9cebdff/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e64e88d5585bea9ce95861079de72006c7fa6d3df4e3a3b65ba31eb979c15c9f", size = 2262756, upload-time = "2026-08-28T09:59:26.608Z" }, + { url = "https://files.pythonhosted.org/packages/2c/9b/59634b7ac631c63b2a37760eb6943af3e29573d6b59a4abc5e7f019d4cee/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d510bac3ee52247af28ed4bb18a1e799f040ac60fd2bf5ccd4c92f1fbe786f", size = 2068352, upload-time = "2026-08-28T09:59:29.044Z" }, + { url = "https://files.pythonhosted.org/packages/08/7c/570abb1ad2155348dc754ea91be22e5aaa18eb6d69a6068f7c6f2679a6ed/pydantic_core-2.46.5-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:a2a5e1d0ff29adddc9f6d6821a66302e4493f8ca898b715b6b1182c2c201ea0a", size = 2104777, upload-time = "2026-08-28T09:59:30.95Z" }, + { url = "https://files.pythonhosted.org/packages/8e/25/5bf74adc65a1ac5b7be3f6cb0bcb5433615c1598a801c19d830d84c98ded/pydantic_core-2.46.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03b9666e41e35d8909852ba191a0607520f81b74eaf12ccf8737005dbb313821", size = 2156312, upload-time = "2026-08-28T09:59:32.604Z" }, + { url = "https://files.pythonhosted.org/packages/90/6a/2ef38830675e050121040618135564ed56b860b45433b02d9b4ebece46f3/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:a91c17edf6eea2402cb5457b4c89e99bc5ed1004aa34c4adf1d4258c1a5c22c2", size = 2150067, upload-time = "2026-08-28T09:59:34.453Z" }, + { url = "https://files.pythonhosted.org/packages/90/ef/a7dbb03a14a64c2a4621f989c615ed9a892535a6cad938fc27079f919d80/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b49924c73a235e969511bf2aabdff3beebf9820931f646c80274d5d780010c47", size = 2304516, upload-time = "2026-08-28T09:59:36.194Z" }, + { url = "https://files.pythonhosted.org/packages/68/f8/6bb4c4b80e8a6fde1904c64a51c62a1d04fcdfa3ea521a66b2ddefa1d885/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:2cbd9a5eff05e51c447c34dfa4632145b26b09120cf04bd0c871e44c1a5e1c9a", size = 2335223, upload-time = "2026-08-28T09:59:37.931Z" }, + { url = "https://files.pythonhosted.org/packages/2a/80/f46b8c681195190b2c1f1c7c0a81abce60663e987613e09ef64d433dd96b/pydantic_core-2.46.5-cp314-cp314-win32.whl", hash = "sha256:2d5d76654becf5efd62c9e51c3756c67b49498b0c9a40884934c40807adbd074", size = 1934827, upload-time = "2026-08-28T09:59:39.836Z" }, + { url = "https://files.pythonhosted.org/packages/f7/3c/60674207246bc0a4009d2391b7c7251c7159f279c8d2ab8aae8ef46f3dee/pydantic_core-2.46.5-cp314-cp314-win_amd64.whl", hash = "sha256:fa10ef4112775900e7a0661068635eb67b2ab824fbde764de6e0e21982a93db0", size = 2042648, upload-time = "2026-08-28T09:59:41.792Z" }, + { url = "https://files.pythonhosted.org/packages/69/0c/117c562c7c1babdf44576b72a5e496906506c93690387ecfbca7c729ae2e/pydantic_core-2.46.5-cp314-cp314-win_arm64.whl", hash = "sha256:045ab3b6d308439e32b81cc173bba5b9018bc6ed896afd0c65b3b009b1699af5", size = 1989652, upload-time = "2026-08-28T09:59:43.702Z" }, + { url = "https://files.pythonhosted.org/packages/e8/66/9336ae58f9eb68c41d121894e52c4c89eccb07eb8f602a04ee9c3f37736a/pydantic_core-2.46.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8816f3d218beb4b787de5c9759c259b8fa61f9dec42dc7811f320a33771778b7", size = 2065829, upload-time = "2026-08-28T09:59:45.364Z" }, + { url = "https://files.pythonhosted.org/packages/c5/02/bc19b47a96c2d3109760711acf22369e56bd7e405ca52f7ade164d2ead57/pydantic_core-2.46.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bce57638e08ac148e5778cce7feb968307a727d66f8e2274a543d0cf0c9ad6a3", size = 1905716, upload-time = "2026-08-28T09:59:47.18Z" }, + { url = "https://files.pythonhosted.org/packages/52/a4/70b47c0509923dd98ccfed04fb3e32ea3849c82a0ff2205bb41009b43c00/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:976e1128455aa595ea04c79ccfedff1aaeab96ee013fcc916bed120c4f0ad94f", size = 1934216, upload-time = "2026-08-28T09:59:49.241Z" }, + { url = "https://files.pythonhosted.org/packages/52/ab/aa03b65f7bb198585edf806b906c3223ecf1795543e39e23aec4cce27ad2/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b891faeedeafba41b2983e5001a81b6a915b69544c7e7570d1989ce1c36ac7", size = 2010635, upload-time = "2026-08-28T09:59:51.692Z" }, + { url = "https://files.pythonhosted.org/packages/3c/8b/0da06343f30b84ec549aafd309c6456223d5dc8bd36af504c573faad561d/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f194189415698233dd1114a093a9b56e61e2c57e11b469be3b0506f46f0771c", size = 2209369, upload-time = "2026-08-28T09:59:53.582Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5b/844c4defaa34a3df66eb9257087d121d70c201298b96abdf9f492fc2f1bf/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82a36973cf8a2ef5406f4fe2edbf8ed0c99629535d959e0b100c76a32535a111", size = 2253238, upload-time = "2026-08-28T09:59:55.484Z" }, + { url = "https://files.pythonhosted.org/packages/f4/64/a4e536cb16d7f61a7fd3120b46c577fc7fa7325992f69c4f52bc786d77d8/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdbb78909f52b981d3b2d56b97328d71eb0b974c36bd77c920123a7ebb192829", size = 2065740, upload-time = "2026-08-28T09:59:58.038Z" }, + { url = "https://files.pythonhosted.org/packages/5f/75/aaa38c6bc2d085f6605b34eabdc6a8a4e0b2e61fc9c8e6e52b28e97b3125/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:52e24eacdb536cade636aa90fb851835222becff8484b7001fdc78cb0290f2aa", size = 2087425, upload-time = "2026-08-28T09:59:59.898Z" }, + { url = "https://files.pythonhosted.org/packages/55/ae/fcab4cfc39aba3689e1d20c8b5250ad280957022c09af2ed9cd585602a5e/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:37ae34309d7bd8c0d61ab839668058f2a7962ea1fc51d105d2db228fe0618034", size = 2139306, upload-time = "2026-08-28T10:00:03.057Z" }, + { url = "https://files.pythonhosted.org/packages/2d/f4/f1d03a4bc9d9acbc62f4d742b8a319af52f71885079868b2ff8e48a651ee/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:0cdbada856a1c69a7624a64d3d9aefe79300bd6ef827b43a4f265010b9b55184", size = 2144589, upload-time = "2026-08-28T10:00:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/83/f3/7a53bb1356de514a4cd295f25b6ac39237895620c0462d2592b76c16e114/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:545f26c504b27c3758439a5e6d9349931f0a04f855668d5fe323c89e82300a38", size = 2288882, upload-time = "2026-08-28T10:00:07.931Z" }, + { url = "https://files.pythonhosted.org/packages/cd/94/5a81583660c175c59d49ffb09f4b3a44debeaf86a19fca664ae1cdd9ee32/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:ff218293c9c806138dca139765e3b067621be52bcd93cdc14c7711be7ddc90a9", size = 2335210, upload-time = "2026-08-28T10:00:10.177Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9f/5d685c2693b972d1a59c998586e8823712b66603aeff47ee60a4bdaafd37/pydantic_core-2.46.5-cp314-cp314t-win32.whl", hash = "sha256:97cf3eb53a8cccacf9d46686a0926186c9bfb5574f2ed66d3639d5fe117cd3a9", size = 1921180, upload-time = "2026-08-28T10:00:12.35Z" }, + { url = "https://files.pythonhosted.org/packages/70/12/5c94ee16d65a37a15f9e869f5e6256df111154491173801a4c5e800ab548/pydantic_core-2.46.5-cp314-cp314t-win_amd64.whl", hash = "sha256:d2f9fc07a8042a8f95925b35c4f04f469707c981fc33245b6ca187cf5d2dd290", size = 2020515, upload-time = "2026-08-28T10:00:14.774Z" }, + { url = "https://files.pythonhosted.org/packages/63/19/67830dda664e6bdf9285ee2e40f355d0d7d6b92aa0c42e8d217bb8d33d36/pydantic_core-2.46.5-cp314-cp314t-win_arm64.whl", hash = "sha256:acf8a67ba51f4ca9ddbd0e6b3000a65ac51ab734661778b3e7ba64d99a710f2f", size = 1989276, upload-time = "2026-08-28T10:00:16.984Z" }, + { url = "https://files.pythonhosted.org/packages/af/1e/ecca01fce348f7e8afa9572441ff6f7d1cc70d21e4859f33944d10877e1e/pydantic_core-2.46.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:c14ad3bdc85ee7f318742c457ca3968a92126d144b15721c759033bfb06296c2", size = 2075342, upload-time = "2026-08-28T10:00:51.353Z" }, + { url = "https://files.pythonhosted.org/packages/1f/4c/af80c7a8032dfc897040ad5cb772bebde529a381186499e6e29987f23f8c/pydantic_core-2.46.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0bddb4020d8f04175865ccd17eff3040874fc11fb593f424edb452653b4b947c", size = 1907219, upload-time = "2026-08-28T10:00:53.438Z" }, + { url = "https://files.pythonhosted.org/packages/be/3e/54d89e2b092e778716bf6153634ef479e955f48c261090be23aa1e0fb0b5/pydantic_core-2.46.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2471fd51c61c610e1dcf7de44d7299283661654d11264ab4802b303368d69c47", size = 1953393, upload-time = "2026-08-28T10:00:55.58Z" }, + { url = "https://files.pythonhosted.org/packages/ea/89/828ee90cda28ce17bdefaa3a6eaf74fe430e113295a10e6126beca559d6c/pydantic_core-2.46.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b10ec717381bdbfafef34607824db4c91de69ff085e4fca3b2af91b4fa17e68a", size = 2099024, upload-time = "2026-08-28T10:00:57.794Z" }, + { url = "https://files.pythonhosted.org/packages/df/dd/053c2e4303f791f3b8f8a14ab0b22008e8eb21d868c0c90b4f9be705b76a/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:013d6f3483d81e02e7c328831808f336c8596ee33b4bd4026b9ffb1e960b8942", size = 2062540, upload-time = "2026-08-28T10:01:00.318Z" }, + { url = "https://files.pythonhosted.org/packages/d7/dd/a18df751a5e37dd51bfad7f68e766999125bebe68c9e1d10a493ad01bd63/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:e9c134bb666dd54b778b9fc0d2b50cbb7f979b9e3716f26a88c9ab3b6fc1dd0f", size = 1902040, upload-time = "2026-08-28T10:01:02.529Z" }, + { url = "https://files.pythonhosted.org/packages/b7/13/01d40f9d07ce8a779fd6e0bd8ad4fba91309500dd67b869e2e219d261a6d/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:347ec774390c87326a2e4929d58d3f7e8763a104d5d35f4cd595a4c952366433", size = 1967479, upload-time = "2026-08-28T10:01:05.004Z" }, + { url = "https://files.pythonhosted.org/packages/fa/04/c81d4841331c2178b6fb09ae225425e110ed72d990c9fe556c4ec03d1013/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e24d8f05fa2d28513d94e877e9c75ad66175376209b3977f916e240e623193c", size = 2111034, upload-time = "2026-08-28T10:01:07.345Z" }, + { url = "https://files.pythonhosted.org/packages/20/21/22102e9950b3049526d20e811b95396508377d87651edd2b80d2b3d28659/pydantic_core-2.46.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ab4b66edffb32d9e951efb3814bd104b8367a7501b81b955cacb5726d897389f", size = 2071333, upload-time = "2026-08-28T10:01:09.636Z" }, + { url = "https://files.pythonhosted.org/packages/d8/18/87aefa427d191e6d3ab1447f1efc1cdcac86af1069239b133e8a0fd7f7c9/pydantic_core-2.46.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:337639ba62a11acde6ef3aeb08c8ea755f8ef1fe5e513356c0f36a2b0d7568b0", size = 1912713, upload-time = "2026-08-28T10:01:12.285Z" }, + { url = "https://files.pythonhosted.org/packages/1f/93/fd89e9ad49b1805ca94d24ce1088b7d305f05c35ffafcedb9819d03588a0/pydantic_core-2.46.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:413a717a410d0c817ef5b786a059415550b3794e1d0c2abffd9efb93a3d9f7b4", size = 2090926, upload-time = "2026-08-28T10:01:15.19Z" }, + { url = "https://files.pythonhosted.org/packages/6f/45/8e59dab6acf8d35f02f0a958980074f31038968bdb2c983fcae9d1efee03/pydantic_core-2.46.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e449def1945a462c464331254e5a44fca7c3b4f9aedf59ec2f50f8066dd8e25", size = 2131303, upload-time = "2026-08-28T10:01:17.937Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a5/e1d4dc5180dd887a9522efc1f8716b8692b7606b1d3273d7862eaf66be44/pydantic_core-2.46.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:a445486499897b88a7d6c310c88ed64dd37b1b59bfd7ae9107490bbb362f47d6", size = 2145128, upload-time = "2026-08-28T10:01:20.694Z" }, + { url = "https://files.pythonhosted.org/packages/c2/d7/ad493864a7fb21c0c4df98f965e2db430cb25a9d7369b5778d5016c09fd9/pydantic_core-2.46.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:2d330aaba8621b1edcec8ae2c4050f63b84ccf6d98723a8f212e9684713abf0e", size = 2294560, upload-time = "2026-08-28T10:01:23.495Z" }, + { url = "https://files.pythonhosted.org/packages/02/8e/b41c84c913f29973a268e6c2b5bbf13c95adb9956c126d10da11ba3b2bef/pydantic_core-2.46.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b6acfb46a814762367fb7ba0828b0a17d441b92ce249a0e007474c9072662dda", size = 2317531, upload-time = "2026-08-28T10:01:26.334Z" }, + { url = "https://files.pythonhosted.org/packages/db/1d/068464f23075f66a8f1b806935e9cd9363ee446636ea70d2c22ee8659dbf/pydantic_core-2.46.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d0a24b40877af2de4950252be9d21eaf7fb07660f3c2cae1f56c6b599ada5266", size = 2140686, upload-time = "2026-08-28T10:01:28.947Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "tenacity" +version = "9.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, +] + +[[package]] +name = "truststore" +version = "0.10.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" }, +] + +[[package]] +name = "typesafe-sdk" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx2" }, + { name = "pydantic" }, + { name = "pydantic-core" }, + { name = "tenacity" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/d4/14d9a00c759c126c814f7cfc0b2a760768214cda0708a9af49577b321ee3/typesafe_sdk-0.7.1.tar.gz", hash = "sha256:495ce9d81733b4f95edcb156e95042a7ec4bfd59972a1150ffa8ce1e11f85a54", size = 23062, upload-time = "2026-09-21T15:57:53.579Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/72/7e57648d27e45260b04762dd2393a81604b516a282fc5efe94cdf90ab971/typesafe_sdk-0.7.1-py3-none-any.whl", hash = "sha256:9d04eee13b5f64bbbe6f8e96cf40e3f39ca4efc934a1f4f39490cb9a43e9d2ad", size = 36366, upload-time = "2026-09-21T15:57:52.314Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.4" +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" } +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" }, +]