diff --git a/.claude/skills/summarize/SKILL.md b/.claude/skills/summarize/SKILL.md new file mode 100644 index 0000000..2bd81ca --- /dev/null +++ b/.claude/skills/summarize/SKILL.md @@ -0,0 +1,374 @@ +--- +name: summarize +description: "Summarize a /swe benchmark run for a given repo/problem/model triple. Reports artifact completion status (github-issue.md, lld.md, review.md, testing.md), error signals captured during the run, and a token-and-themes summary derived from the matching session JSONL files under ~/.claude/projects/. Output is a single markdown report saved next to the artifacts." +license: Apache-2.0 +metadata: + author: Amit Arora + version: "1.1" +--- + +# Summarize Skill + +Use this skill **after** a `/swe` benchmark run to evaluate how that run went. It performs two passes: + +1. **Artifact pass** - check the `benchmarks/swe-benchmark-data/{repo-name}/{problem-name}/{model-name}/` folder and report which of the four expected files exist, are non-empty, and are well-formed; surface any error/diagnostic content found in those files. +2. **Session pass** - read the JSONL files from `~/.claude/projects//` for the session(s) that produced the artifacts, and summarize token usage (per model, broken down by input / output / cache-create / cache-read), tool-call mix, error events, and recurring themes from user prompts and assistant turns. + +The result is written as a single markdown report next to the artifacts and printed back to the user. + +**This skill stops at reporting. It does not modify the artifacts, re-run `/swe`, or open issues.** + +## Workflow + +1. **Gather Inputs** - Capture `--repo`, `--problem`, and `--model` (never guess them); optionally `--session-id` to narrow the JSONL pass +2. **Locate Artifacts Folder** - Resolve `benchmarks/swe-benchmark-data/{repo}/{problem}/{model}/` +3. **Artifact Pass** - Check existence, size, and quality of `github-issue.md`, `lld.md`, `review.md`, `testing.md` +4. **Locate Session JSONL Files** - Resolve `~/.claude/projects//*.jsonl` and filter by session/time +5. **Session Pass** - Aggregate token usage, tool calls, errors, and themes +6. **Write Report** - Save `summary.md` next to the artifacts and print it +7. **Present Summary & Seek Guidance** + +--- + +## Step 1: Gather Inputs + +**NEVER guess `--repo`, `--problem`, or `--model`.** Either the user passes them as command-line parameters or you ask explicitly. Do not infer them from the current working directory, recent files, or memory. + +### 1.1 Required Inputs + +1. **`--repo`** (kebab-case, e.g. `mcp-gateway-registry`) - matches the `{repo-name}` folder under `benchmarks/swe-benchmark-data/`. +2. **`--problem`** (kebab-case, e.g. `remove-faiss`) - matches the `{problem-name}` folder. +3. **`--model`** (kebab-case, e.g. `claude-opus-4-7`) - matches the `{model-name}` folder. + +If any are missing, ask explicitly: + +> The `/summarize` skill requires three inputs that I will not guess. Please provide: +> 1. `--repo ` +> 2. `--problem ` +> 3. `--model ` + +### 1.2 Optional Inputs + +- **`--session-id `** - limit the session pass to a specific JSONL file. If not given, the skill picks every JSONL whose `cwd` matches this repository and whose `gitBranch` / time window overlaps with the artifact mtimes. +- **`--since `** / **`--until `** - further narrow the session pass. +- **`--out `** - override the default report path (`summary.md` inside the artifacts folder). + +## Step 2: Locate the Artifacts Folder + +Resolve the artifact folder using the repo root - never hardcode `/home/ubuntu/...`: + +```bash +REPO_ROOT="$(git rev-parse --show-toplevel)" +ART_DIR="$REPO_ROOT/benchmarks/swe-benchmark-data/{repo}/{problem}/{model}" +``` + +If `$ART_DIR` does not exist, stop and report it as a hard failure: there is nothing to summarize. + +## Step 3: Artifact Pass + +For each of the four expected artifacts, record: + +| Field | How to Compute | +|-------|----------------| +| Exists | `test -f "$ART_DIR/"` | +| Size (bytes) | `stat -c %s "$ART_DIR/"` | +| Lines | `wc -l < "$ART_DIR/"` | +| Last modified | `stat -c %y "$ART_DIR/"` | +| Looks well-formed | Heuristic checks below | + +### Expected Files and Heuristics + +| File | Heuristic for "looks well-formed" | +|------|-----------------------------------| +| `github-issue.md` | Contains `## Title`, `## Description`, and at least one `### Acceptance Criteria` checkbox | +| `lld.md` | Contains `# Low-Level Design`, a `Table of Contents`, and at least the `Architecture`, `Data Models`, and `File changes` headings | +| `review.md` | Contains all five reviewers (`Pixel`, `Byte`, `Circuit`, `Cipher`, `Sage`) and a `Verdict:` line for each | +| `testing.md` | Contains `# Testing Plan`, `## 1. Functional Tests`, and a `## 6. Test Execution Checklist` | + +### Error Signals to Capture + +While scanning each artifact, also surface lines that suggest the run was incomplete: + +- Lines containing `TODO`, `TBD`, `FIXME`, `{...}` placeholder fences left in by the model +- Lines starting with `Error:`, `Traceback`, or `# Aborted` +- Headings present in the template but with empty bodies (e.g. a `### Strengths` immediately followed by another heading) + +Record up to 10 such hits per file with line numbers. + +### Output of the Artifact Pass + +Build an in-memory dict shaped like: + +```json +{ + "artifacts": { + "github-issue.md": {"exists": true, "bytes": 1840, "lines": 56, "well_formed": true, "issues": []}, + "lld.md": {"exists": true, "bytes": 12930, "lines": 410, "well_formed": true, "issues": [{"line": 312, "snippet": "TODO: pick algorithm"}]}, + "review.md": {"exists": true, "bytes": 4220, "lines": 130, "well_formed": false, "issues": [{"line": 88, "snippet": "Verdict: NEEDS REVISION"}]}, + "testing.md": {"exists": false} + } +} +``` + +## Step 4: Locate Session JSONL Files + +Claude Code stores per-project session transcripts under `~/.claude/projects//*.jsonl`. The encoded directory is the absolute project path with `/` replaced by `-` and a leading `-` (e.g. `/home/ubuntu/repos/sample-claude-code-multi-model` becomes `-home-ubuntu-repos-sample-claude-code-multi-model`). + +The encoded path is derived from `git rev-parse --show-toplevel`, not necessarily from the current working directory. This means if you run from a subdirectory (e.g. `repo/bedrock`), the encoded path will still be for the repo root. + +### Session File Selection Algorithm + +1. **Get the repo root and encoded path:** + ```bash + REPO_ROOT="$(git rev-parse --show-toplevel)" + ENCODED="$(echo "$REPO_ROOT" | sed 's|/|-|g')" + SESSIONS_DIR="$HOME/.claude/projects/$ENCODED" + ``` + +2. **Check for subdirectory sessions:** Also check for sessions in directories that start with `$ENCODED-` (these represent runs from subdirectories): + ```bash + # For REPO_ROOT=/home/ubuntu/repos/sample-claude-code-multi-model + # ENCODED=-home-ubuntu-repos-sample-claude-code-multi-model + # Also check: -home-ubuntu-repos-sample-claude-code-multi-model-bedrock + SHADOW_DIRS=$(ls -d ${SESSIONS_DIR}-* 2>/dev/null || true) + ``` + +3. **Build list of session directories:** + - Primary: `$SESSIONS_DIR` + - Shadow (subdirectory runs): Any `${SESSIONS_DIR}-*` directories + +4. **Find matching sessions:** + - If `--session-id ` is provided, use only that session + - Otherwise, list all `*.jsonl` files in all session directories + - Filter to sessions whose timestamps overlap with artifact mtimes (1-hour grace window) + +5. **Verify session by cwd (if needed):** + - Check the `cwd` field in user events to ensure it matches the repo path + - If multiple sessions overlap in time, use the one with matching `cwd` + +If no JSONL file matches, mark the session pass as `Not Available` in the report and continue - the artifact pass alone is still useful. + +## Step 5: Session Pass + +Each line in a session JSONL is a JSON object. The relevant types are: + +| `type` | What it carries | What we extract | +|--------|-----------------|-----------------| +| `assistant` | `message.usage`, `message.model`, `message.content` (text + tool_use blocks) | Tokens (input / output / cache-create / cache-read), model id, tool calls | +| `user` | `message.content` (text or tool_result) | User prompt text; tool_result error strings | +| `system` | System reminders, hook output | Error / warning lines | +| `attachment` | File attachments | Filenames referenced | + +Pseudocode for the aggregation: + +```python +import json +from collections import Counter, defaultdict +import re + +totals = defaultdict(lambda: { + "input_tokens": 0, + "output_tokens": 0, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0, + "messages": 0, +}) +tool_calls = Counter() +errors = [] +user_prompts = [] + +for path in selected_jsonl_files: + with open(path) as f: + for line in f: + obj = json.loads(line) + t = obj.get("type") + msg = obj.get("message") or {} + if t == "assistant" and isinstance(msg, dict): + model = msg.get("model", "unknown") + u = msg.get("usage") or {} + bucket = totals[model] + bucket["input_tokens"] += u.get("input_tokens", 0) or 0 + bucket["output_tokens"] += u.get("output_tokens", 0) or 0 + bucket["cache_creation_input_tokens"] += ( + u.get("cache_creation_input_tokens", 0) or 0 + ) + bucket["cache_read_input_tokens"] += ( + u.get("cache_read_input_tokens", 0) or 0 + ) + bucket["messages"] += 1 + for block in msg.get("content", []) or []: + if isinstance(block, dict) and block.get("type") == "tool_use": + tool_calls[block.get("name", "?")] += 1 + elif t == "user" and isinstance(msg, dict): + content = msg.get("content") + if isinstance(content, str): + user_prompts.append(content) + elif isinstance(content, list): + for block in content: + if isinstance(block, dict): + if block.get("type") == "text": + user_prompts.append(block.get("text", "")) + elif block.get("type") == "tool_result" and block.get("is_error"): + errors.append(str(block.get("content", ""))[:500]) + elif t == "system": + text = obj.get("content") or obj.get("text") or "" + if any(s in text for s in ("Error", "error", "failed", "Traceback")): + errors.append(text[:500]) +``` + +### Themes + +Extract recurring topics from `user_prompts`: + +1. Lowercase, strip punctuation, drop stopwords. +2. Take the top 15 most frequent 1-3 word phrases. (Use a simple counter; no NLP libs required.) +3. Cluster verbs ("update", "remove", "add", "review", "ask") to label intent. + +Themes go in the report as a short bullet list, e.g.: + +``` +- folder structure / repo layout (12 mentions) +- gitignore / submodule discussion (5 mentions) +- "do not guess" inputs (4 mentions) +- skill rewrite (testing, lld, review) (4 mentions) +``` + +## Step 6: Write the Report + +Save `summary.md` next to the artifacts (or to `--out` if provided): + +```markdown +# Summary: {repo} / {problem} / {model} + +*Created: {iso-timestamp}* +*Artifacts: `benchmarks/swe-benchmark-data/{repo}/{problem}/{model}/`* + +## Run Status + +| Artifact | Exists | Bytes | Lines | Well-formed | Issues | +|----------|--------|-------|-------|-------------|--------| +| github-issue.md | yes | 1840 | 56 | yes | 0 | +| lld.md | yes | 12930 | 410 | yes | 1 | +| review.md | yes | 4220 | 130 | no | 3 | +| testing.md | no | - | - | - | - | + +**Overall:** {GREEN / YELLOW / RED} - {one-line headline} + +### Issues Captured in Artifacts + +- `lld.md:312` `TODO: pick algorithm` +- `review.md:88` `Verdict: NEEDS REVISION` +- ... + +## Session Token Usage + +| Model | Messages | Input | Output | Cache Create | Cache Read | Total | +|-------|----------|-------|--------|--------------|------------|-------| +| claude-opus-4-7 | 97 | 12,403 | 28,192 | 145,200 | 312,800 | 498,595 | +| claude-haiku-4-5 | 8 | 432 | 980 | 0 | 0 | 1,412 | +| **All models** | 105 | 12,835 | 29,172 | 145,200 | 312,800 | 500,007 | + +*Cache hit ratio:* `cache_read / (cache_read + input)` = 96.2% + +## Tool Call Mix + +| Tool | Count | +|------|-------| +| Bash | 41 | +| Read | 33 | +| Edit | 14 | +| Agent | 5 | +| Write | 4 | +| Grep | 3 | + +## Errors and Warnings + +- `tool_result is_error: ENOENT: no such file ...` (truncated) +- `system: Error: pre-commit hook failed - ruff check exited 1` +- ... + +## Themes from User Prompts + +- folder structure / repo layout (12 mentions) +- "do not guess" inputs (4 mentions) +- skill rewrite (4 mentions) +- ... + +## Sessions Included + +| Session | First event | Last event | Lines | +|---------|-------------|------------|-------| +| 9afad8af-a121-4047-b07b-fb8b8556eb55 | 2026-06-05T15:43Z | 2026-06-05T17:10Z | 257 | +| 6e8e145a-b043-4722-af15-68841a6853cb | 2026-06-05T17:11Z | 2026-06-05T17:55Z | 88 | + +## Next Steps + +- {Auto-generated: e.g. "Re-run /swe to regenerate testing.md (missing)."} +- {e.g. "Resolve the NEEDS REVISION verdict in review.md before implementing."} +``` + +### Overall Status Rule + +Pick `GREEN`, `YELLOW`, or `RED` using these thresholds: + +| Status | Rule | +|--------|------| +| GREEN | All four artifacts exist, all four well-formed, no errors in session pass | +| YELLOW | All four artifacts exist but at least one has issues, OR session pass shows non-fatal errors | +| RED | One or more artifacts missing, OR a fatal error stopped the run | + +## Step 7: Present Summary & Seek Guidance + +After writing `summary.md`, print the report (or a tightened version) back to the user and ask: + +1. Should I open the relevant artifact(s) so you can inspect the issues yourself? +2. Should I diff this run against another `{model-name}` run for the same problem? (e.g. `claude-opus-4-7` vs `claude-sonnet-4-6`) +3. Anything else you want me to dig into from the session JSONL files (e.g. specific tool errors, specific time window)? + +Do not modify any artifact, re-run `/swe`, open issues, or commit anything unless the user explicitly authorizes it as a separate request. + +--- + +## Important Guidelines + +### Read-Only Posture +- This skill is **strictly read-only** with respect to the artifact folder and the session JSONL files. Never edit them. +- The only file the skill writes is `summary.md` in the artifacts folder (or the `--out` override). + +### Hard Stops +1. **Do not guess inputs.** If `--repo`, `--problem`, or `--model` are missing, ask. +2. **Do not hardcode `/home/ubuntu/...`.** Use `git rev-parse --show-toplevel` and `$HOME`. +3. **Do not modify the artifact files.** Reading them is the whole point. +4. **Do not implement code, run tests, or open PRs.** Out of scope. + +### Session JSONL Caveats +- Token counts come from `message.usage`. Only `assistant` events carry it. User events do not. +- A single session may span multiple models if `/model` was used mid-run; group totals by `message.model`. +- `cache_creation_input_tokens` and `cache_read_input_tokens` are real billable tokens; include them in the totals. +- The `web_search_requests` / `web_fetch_requests` under `usage.server_tool_use` are billable separately - call them out if non-zero. +- **Important:** When the session was run from a subdirectory, the session directory may be encoded differently (e.g. `-home-ubuntu-repos-sample-claude-code-multi-model-bedrock` instead of `-home-ubuntu-repos-sample-claude-code-multi-model`). Always check shadow directories. + +### Heuristics, Not Verdicts +The "well-formed" checks are heuristics. If a heuristic fails, report the failure transparently rather than declaring the artifact bad. The user is the final judge. + +## Example Usage + +User: "/summarize --repo mcp-gateway-registry --problem remove-faiss --model claude-opus-4-7" + +1. Resolve `$ART_DIR=benchmarks/swe-benchmark-data/mcp-gateway-registry/remove-faiss/claude-opus-4-7/` +2. Confirm `$ART_DIR` exists +3. Artifact pass: stat each of the four expected files; record sizes, line counts, well-formed checks, and any TODO/error snippets +4. Resolve `$SESSIONS_DIR=$HOME/.claude/projects/-home-ubuntu-repos-sample-claude-code-multi-model/` +5. If no matching sessions found, also check `$HOME/.claude/projects/-home-ubuntu-repos-sample-claude-code-multi-model-*/` +6. Session pass: walk every `*.jsonl` whose timestamps overlap the artifact mtimes; aggregate tokens by model, count tool calls, capture errors, extract themes +7. Write `$ART_DIR/summary.md` +8. Print the report and ask whether to diff against another model + +--- + +## Constraints + +- **Read-only over artifacts and JSONL** - the only output write is `summary.md`. +- **No execution** of the cloned `repo/` tree. +- **No emojis, clever code, or em-dashes** in any output. +- **Naming**: always "Amazon Bedrock" (never "AWS Bedrock"). diff --git a/.claude/skills/swe/SKILL.md b/.claude/skills/swe/SKILL.md new file mode 100644 index 0000000..2104959 --- /dev/null +++ b/.claude/skills/swe/SKILL.md @@ -0,0 +1,714 @@ +--- +name: swe +description: "End-to-end Software Engineering skill that benchmarks how well a given LLM can take a problem from idea to a complete design package. Creates structured documentation under benchmarks/swe-benchmark-data/{repo-name}/{problem-name}/{model-name}/ with a GitHub issue spec, low-level design (LLD), expert review, and testing plan, so multiple models can be compared on the same problem within the same repository. The skill stops at design and review - it does NOT implement the change." +license: Apache-2.0 +metadata: + author: Amit Arora + version: "2.1" +--- + +# Software Engineering (SWE) Skill + +Use this skill when the user wants to evaluate how a particular LLM performs on a software-engineering problem. Every run is treated as a benchmark: the problem is the input, the model is the contestant, and the artifacts (`github-issue.md`, `lld.md`, `review.md`, `testing.md`) are the output that can be compared across models. + +**This skill stops at design.** It does not write or modify production code, run tests, open PRs, or commit anything. The deliverable is the four artifact files described below. + +## Workflow + +1. **Gather Requirements** - Detect the active model and confirm it; ask for the GitHub URL; ask for tag-vs-main; confirm the task; locate or clone the target repo with user approval +2. **Quick Codebase Review** - Explore the codebase to understand structure +3. **Create Benchmark Folder** - Create `benchmarks/swe-benchmark-data/{repo-name}/{problem-name}/{model-name}/` directory +4. **Write GitHub Issue** - Create `github-issue.md` with the issue specification +5. **Deep Codebase Analysis** - Thoroughly explore relevant code +6. **Write Low-Level Design** - Create `lld.md` with technical details +7. **Expert Review** - Create `review.md` with multi-persona feedback +8. **Write Testing Plan** - Create `testing.md` with functional, backwards-compat, UX, deployment, and E2E tests +9. **Present Summary & Seek Guidance** - Present the four artifacts and ask for direction + +--- + +## Step 1: Gather Requirements + +**NEVER guess the repo URL, the tag, or the task description.** All of them must come from the user. Do not infer them from session context, the current working directory, recent files, or memory. + +The skill MUST do four things in this exact order at the very start of every run, even when some values were passed in as parameters. Confirm what was passed; ask for what is missing. Do not move past Step 1 until all four have an explicit user-confirmed answer. + +### 1.0 Announce and confirm the model first + +Before anything else, the skill must figure out which model is currently driving this session and tell the user, then ask the user to confirm or override. + +How to figure it out: + +- Look at the system context for the active model id (e.g. a line like "You are powered by the model named Opus 4.7 (1M context). The exact model ID is us.anthropic.claude-opus-4-7[1m]."). +- Pick the canonical model name from the ID. Strip vendor/region prefixes (`us.anthropic.`), drop bracketed context-window suffixes (`[1m]`), and use kebab-case. Examples: `us.anthropic.claude-opus-4-7[1m]` -> `claude-opus-4-7`; `claude-sonnet-4-6` -> `claude-sonnet-4-6`; `claude-haiku-4-5-20251001` -> `claude-haiku-4-5`. +- If you cannot determine the model from system context, do not invent one - tell the user you could not detect it and ask. + +Then announce and confirm: + +> I am using **`{detected-model-name}`** for this run. This will be used as the `{model-name}` folder under the benchmark directory. +> +> Is that correct, or would you like to use a different name? (Reply with the name in kebab-case, e.g. `claude-opus-4-7`, `claude-sonnet-4-6`, `gpt-5`.) + +Wait for confirmation. Only after the user confirms (or supplies an override) lock in `{model-name}` and continue. Do **not** ask for the model again later in Step 1.5; remove that question from the remaining clarifications since it has already been settled here. + +### 1.1 Question 1 - GitHub repo URL + +**Always ask first.** This is the canonical identifier of the target repository; everything else (folder names, clone commands, README rows) is derived from it. + +> **Q1.** What is the GitHub URL of the repository you want to benchmark? +> Example: `https://github.com/agentic-community/mcp-gateway-registry` + +If the user provided `--repo ` (or any equivalent param), echo the URL back and ask for confirmation rather than skipping the question. + +From the URL, derive: +- `{repo-name}` = the basename of the URL with `.git` stripped (kebab-case as-is, e.g. `mcp-gateway-registry`). +- `{owner}` = the path segment before the repo name (used only in messages, never inferred for anything else). + +State the derived `{repo-name}` back to the user before continuing. + +### 1.2 Question 2 - Git tag or main + +**Ask second, only after Q1 is answered.** + +> **Q2.** Which version should I check out? +> 1. A specific git tag (e.g. `1.24.4`) - recommended for reproducible benchmarks. +> 2. `main` - latest commit on the default branch. + +Record the answer as `{ref}`. If the user picked a tag, `{ref}` is the tag name. If the user picked main, `{ref}` is `main`. + +### 1.3 Question 3 - Confirm the task + +**Ask third, only after Q1 and Q2 are answered.** + +> **Q3.** Is this your task? +> "{the task description the user originally provided, or a placeholder if none was provided}" + +If the user passed `--task `, repeat it verbatim and ask for yes/no confirmation. If they did not provide a task at all, ask them to describe it now and then confirm: + +> What task should the model attempt against this repo? Example: "remove FAISS from the codebase and documentation". + +Once the user confirms the task wording, derive a kebab-case `{problem-name}` from it (e.g. "remove FAISS from the codebase" -> `remove-faiss`) and **confirm the derived name with the user one more time** before creating any folders. + +### 1.4 Locate or Clone the Target Repository + +Now that `{repo-name}`, `{ref}`, and `{problem-name}` are settled, resolve paths. All paths are expressed relative to the repository root via `git rev-parse --show-toplevel` - never hardcode absolute paths. Let: + +```bash +REPO_ROOT="$(git rev-parse --show-toplevel)" +BENCH_DIR="$REPO_ROOT/benchmarks/swe-benchmark-data" +``` + +1. **Check locally first.** Look for the cloned source at `$BENCH_DIR/{repo-name}/repo/`. If it exists and is a valid git checkout, confirm the ref: + + ```bash + git -C "$BENCH_DIR/{repo-name}/repo" describe --tags --exact-match # for tags + # or + git -C "$BENCH_DIR/{repo-name}/repo" rev-parse --abbrev-ref HEAD # for main + ``` + + If the local checkout is at the wrong ref, tell the user and ask whether to re-clone (Option A: delete `repo/` and re-clone at `{ref}`) or keep the existing checkout. + +2. **If the path does not exist, announce the clone command and ask for approval before running it.** Use `$BENCH_DIR` (not an absolute path): + + > I will run the following clone command. Approve to proceed? + > ``` + > REPO_ROOT="$(git rev-parse --show-toplevel)" + > BENCH_DIR="$REPO_ROOT/benchmarks/swe-benchmark-data" + > mkdir -p "$BENCH_DIR/{repo-name}" + > git -C "$BENCH_DIR/{repo-name}" clone --branch {ref} --depth 1 {url} repo + > ``` + + Only run the clone after the user approves. After cloning, append a row to the benchmark README's "Benchmark Repositories" section if one is not already there (URL, ref, local path, artifact path). + +### 1.5 Remaining Clarifying Questions + +Once the model, URL, ref, task, and the local checkout are settled, gather the rest. Do **not** re-ask which model is being benchmarked - that was already settled in Step 1.0. + +1. What problem does this solve? +2. Who are the users/consumers? +3. Are there any constraints (language, framework, environment, deadlines)? +4. What is the expected scope (small/medium/large)? + +## Step 2: Quick Codebase Review + +Before creating any design documents, perform a quick exploration of the codebase to understand: + +1. **Project Structure** - top-level layout, source roots, config files +2. **Related Components** - existing features similar to the one being designed +3. **Entry Points** - main scripts, CLIs, or app entrypoints + +This quick review takes 5-10 minutes and helps you ask better clarifying questions and avoid proposing designs that conflict with existing architecture. + +## Step 3: Create Benchmark Folder + +All artifacts live under a top-level `benchmarks/` directory. Within it, every run gets its own `{repo-name}/{problem-name}/{model-name}/` subfolder so multiple models can be benchmarked on the same problem within a given repository and compared side-by-side. + +The target repository's source code is **not** stored here. It is cloned locally at a specific tag by each contributor following the instructions in `benchmarks/swe-benchmark-data/README.md`, into a `repo/` subdirectory under `{repo-name}/`. The `repo/` checkout is gitignored so it is never committed. + +### Folder Structure + +``` +benchmarks/ +└── swe-benchmark-data/ + ├── README.md # Lists target repos, tags, and tasks to benchmark + └── {repo-name}/ + ├── repo/ # Cloned source (gitignored - cloned by contributor) + ├── {problem-name}/ + │ └── {model-name}/ + │ ├── github-issue.md # GitHub issue specification + │ ├── lld.md # Low-level design document + │ ├── review.md # Expert review document + │ └── testing.md # Testing plan (functional, backwards-compat, UX, deployment, E2E) + └── {next-problem-name}/ + └── ... +``` + +Conventions: + +- Use kebab-case for `{repo-name}` and match the upstream repository name (e.g. `mcp-gateway-registry`). The list of supported `{repo-name}` values, their upstream URLs, and the tag to clone are all defined in `benchmarks/swe-benchmark-data/README.md`. +- Source code for the target lives at `benchmarks/swe-benchmark-data/{repo-name}/repo/`. If that path does not exist, stop and direct the user to the setup instructions in the benchmark README - do not run `/swe` against a repo that has not been cloned. +- Use kebab-case for `{problem-name}` (e.g. `remove-faiss`, `remove-efs-from-terraform-aws-ecs`). Prefer the exact name listed in the benchmark README's task table. +- Use kebab-case for `{model-name}` and prefer the canonical model id (e.g. `claude-opus-4-7`, `claude-sonnet-4-6`, `claude-haiku-4-5`, `gpt-5`). +- The same `{repo-name}/{problem-name}` folder will accumulate one subfolder per model that has attempted it - do not delete sibling model folders. + +### Pre-existing Artifacts: Confirm Before Overwriting + +Before writing any artifact, check whether the target `{model-name}/` folder already contains any of `github-issue.md`, `lld.md`, `review.md`, or `testing.md`. If **one or more** of them exist, **stop and ask the user** what to do. Never silently overwrite. + +Concretely: + +1. List which of the four files already exist (with size and last-modified time, so the user can see they're real prior work). +2. Present the choices clearly and wait for the user's answer: + + > The following artifacts already exist at `benchmarks/swe-benchmark-data/{repo}/{problem}/{model}/`: + > - `lld.md` (12.6 KB, modified 2026-06-04) + > - `review.md` (4.1 KB, modified 2026-06-04) + > + > How would you like to proceed? + > 1. **Delete all four files first**, then run a clean `/swe` pass (recommended for a fresh benchmark). + > 2. **Overwrite in place** as each artifact is regenerated (existing files get replaced one by one). + > 3. **Append a suffix** to the model folder (e.g. `claude-opus-4-7-run2/`) and write the new run there, leaving the prior run intact. + > 4. **Abort** - keep everything as-is and exit the skill. + +3. Only proceed once the user picks an option: + - **Option 1 (delete first):** remove the four files (and only those four; do not touch sibling folders or the cloned `repo/`). Print the `rm` commands you ran. + - **Option 2 (overwrite in place):** continue, overwriting each artifact when its step writes the file. + - **Option 3 (append suffix):** ask the user to confirm the suffix, then create `{model-name}-{suffix}/` and treat that as the new target folder for the rest of the run. + - **Option 4 (abort):** stop the skill cleanly, do not modify anything, do not create empty folders. + +Even if all four files are present and the user picks option 2, do not "merge" with prior content - each new step writes the new artifact end-to-end. The prior file is replaced, not edited in place. + +If `benchmarks/swe-benchmark-data/{repo-name}/{problem-name}/{model-name}/` exists but is **empty**, no confirmation is needed; proceed normally. + +Example for the same problem solved by two models inside the same repository: + +``` +benchmarks/swe-benchmark-data/mcp-gateway-registry/remove-faiss/ +├── claude-opus-4-7/ +│ ├── github-issue.md +│ ├── lld.md +│ ├── review.md +│ └── testing.md +└── claude-sonnet-4-6/ + ├── github-issue.md + ├── lld.md + ├── review.md + └── testing.md +``` + +## Step 4: Write GitHub Issue (github-issue.md) + +Create a comprehensive GitHub issue specification. This is the artifact that would be filed against the upstream repo to track the task. + +### Template + +```markdown +# GitHub Issue: {Feature / Task Title} + +## Title +{concise title for the issue} + +## Labels +- {appropriate labels: enhancement, bug, refactor, infra, docs, etc.} + +## Description + +### Problem Statement +{What problem does this solve? Why is it needed?} + +### Proposed Solution +{High-level description of the solution} + +### User Stories +- As a {user type}, I want to {action} so that {benefit} + +### Acceptance Criteria +- [ ] {Criterion 1} +- [ ] {Criterion 2} + +### Out of Scope +- {What is explicitly NOT included} + +### Dependencies +- {Any dependent issues or external dependencies} + +### Related Issues +- #{issue numbers if any} +``` + +## Step 5: Deep Codebase Analysis + +**CRITICAL:** Before writing the LLD, you MUST thoroughly understand all relevant code in the cloned `repo/`. A design that ignores existing patterns will fail when an implementer picks it up. + +### What to Analyze + +1. **Existing Models and Data Structures** - Pydantic models, dataclasses, schemas +2. **Service / Business Logic Patterns** - how logic is organized, error handling, logging, caching +3. **Route / CLI / Entrypoint Patterns** - request/response shapes, argparse layouts +4. **Storage / IO Layer** - persistence, file IO, network calls +5. **Configuration and Constants** - env vars, settings classes, feature flags +6. **Existing Tests** - testing patterns, fixtures, mocking conventions + +### How to Analyze + +Use the Agent tool with `subagent_type=Explore` for thorough investigation. Read actual code, not just file names. Note TODOs and known issues. + +### Document Your Findings + +Capture in your LLD: +- Key files reviewed +- Patterns identified +- Integration points for the new change +- Constraints or limitations discovered + +## Step 6: Write Low-Level Design (lld.md) + +Create a detailed technical design document. This is the most critical document - it should contain enough detail for an entry-level developer to implement the change later. + +```markdown +# Low-Level Design: {Feature Name} + +*Created: {date}* +*Author: Claude* +*Status: Draft* + +## Table of Contents +1. [Overview](#overview) +2. [Codebase Analysis](#codebase-analysis) +3. [Architecture](#architecture) +4. [Data Models](#data-models) +5. [API / CLI Design](#api--cli-design) +6. [Configuration Parameters](#configuration-parameters) +7. [New Dependencies](#new-dependencies) +8. [Implementation Details](#implementation-details) +9. [Observability](#observability) +10. [Scaling Considerations](#scaling-considerations) +11. [File Changes](#file-changes) +12. [Testing Strategy](#testing-strategy) +13. [Alternatives Considered](#alternatives-considered) +14. [Rollout Plan](#rollout-plan) + +## Overview +### Problem Statement +{Detailed problem description} + +### Goals +- {Goal 1} + +### Non-Goals +- {What this design explicitly does NOT address} + +## Codebase Analysis + +### Key Files Reviewed + +| File/Directory | Purpose | Relevance to This Change | +|----------------|---------|--------------------------| +| `{path}` | {Description} | {How it relates} | + +### Existing Patterns Identified +1. **Pattern Name**: {Description} + - Files: `{file1}`, `{file2}` + - How a future implementer should follow this: {How} + +### Integration Points + +| Component | Integration Type | Details | +|-----------|------------------|---------| +| {Existing component} | {Extends/Uses/Depends on} | {Specific details} | + +### Constraints and Limitations Discovered +- {Constraint}: {How it affects the design} + +## Architecture + +### System Context Diagram +{ASCII diagram showing how this fits into the overall system} + +### Sequence Diagram +{Show the flow of requests/data} + +### Component Diagram +{Show internal components and their relationships} + +## Data Models + +### New Models +```python +class NewModel(BaseModel): + """Description.""" + + field_name: str = Field( + ..., + description="What this field represents", + min_length=1, + max_length=100 + ) +``` + +### Model Changes +{Changes to existing models} + +## API / CLI Design + +### New Endpoints / Commands +**Description:** {What it does} + +**Request / Invocation:** +```bash +uv run python -m {module} --param value +``` + +**Expected Response / Output:** +```json +{ "id": "123", "status": "success" } +``` + +**Error Cases:** +- 400 / nonzero exit: {when} + +## Configuration Parameters + +### New Environment Variables + +| Variable Name | Type | Default | Required | Description | +|---------------|------|---------|----------|-------------| +| `FEATURE_ENABLED` | bool | `true` | No | Enable/disable the feature | + +### Settings / Config Class Updates +```python +feature_enabled: bool = Field( + default=True, + description="Enable/disable feature X" +) +``` + +### Deployment Surface Checklist +List every surface where this parameter must appear (`.env.example`, `docker-compose.yml`, Terraform vars, Helm values, etc.) so an implementer can tick them off later. + +## New Dependencies + +| Package | Version | Purpose | +|---------|---------|---------| +| `package-name` | `latest` | {Why needed} | + +If no new dependencies are required, explicitly state: "This change uses only existing dependencies." + +## Implementation Details + +### Step-by-Step Plan (for a future implementer) + +#### Step 1: {First Step} +**File:** `path/to/file.py` +**Lines:** {approximate line numbers or "new file"} + +```python +def new_function( + param1: str, + param2: int +) -> dict: + """Description.""" + if not param1: + raise ValueError("param1 is required") + return {"status": "success", "data": process(param1, param2)} +``` + +### Error Handling +{How errors should be handled} + +### Logging +{What should be logged and at what level} + +## Observability +### Tracing / Metrics / Logging Points +{Spans, metrics, key log events} + +## Scaling Considerations +- Current load assumptions +- Horizontal scaling +- Bottlenecks +- Caching strategy + +## File Changes + +### New Files + +| File Path | Description | +|-----------|-------------| +| `src/feature/new_module.py` | {What it does} | + +### Modified Files + +| File Path | Lines | Change Description | +|-----------|-------|--------------------| +| `src/main.py` | ~50 | {What changes} | + +### Estimated Lines of Code + +| Category | Lines | +|----------|-------| +| New code | ~{X} | +| New tests | ~{X} | +| Modified code | ~{X} | +| **Total** | **~{X}** | + +## Testing Strategy +{Pointer to testing.md - the full plan lives there} + +## Alternatives Considered + +### Alternative 1: {Name} +**Description:** ... +**Pros / Cons:** ... +**Why Rejected:** ... + +### Comparison Matrix + +| Criteria | Chosen | Alt 1 | Alt 2 | +|----------|--------|-------|-------| +| Complexity | Low | Med | High | + +## Rollout Plan +- Phase 1: Implementation (out of scope for this skill) +- Phase 2: Testing +- Phase 3: Deployment + +## Open Questions +- {Unresolved} + +## References +- {Docs / similar implementations} +``` + +## Step 7: Expert Review (review.md) + +Create a review document with feedback from multiple expert personas: + +| Role | Reviewer | Focus | +|------|----------|-------| +| Frontend Engineer | Pixel | UI/UX, components, state, API integration | +| Backend Engineer | Byte | API design, data models, business logic, performance | +| SRE/DevOps Engineer | Circuit | Deployment, monitoring, scaling, infrastructure | +| Security Engineer | Cipher | AuthN/AuthZ, validation, OWASP, data protection | +| SMTS (Overall) | Sage | Architecture, code quality, maintainability | + +For each reviewer, capture: +- **Strengths** observed in the design +- **Concerns** identified +- **New libraries / infra dependencies** required (with justification) +- **Better alternatives considered** +- **Recommendations** +- **Questions for author** +- **Verdict:** APPROVED / APPROVED WITH CHANGES / NEEDS REVISION + +End with a Review Summary table and Next Steps. Reviews must be realistic, identifying actual issues rather than just praise. + +## Step 8: Write Testing Plan (testing.md) + +Create a comprehensive testing plan with **executable, copy-pasteable tests** covering every externally observable change. A future implementer should be able to walk through this document and verify the change works end-to-end without inventing test cases. + +### When Each Test Category Applies + +| Category | Include When | +|----------|--------------| +| Functional Tests (CLI / curl) | Change adds/modifies any HTTP endpoint or CLI command | +| Backwards Compatibility Tests | Change touches an existing endpoint, schema, CLI command, default, or model | +| UX Tests | Change adds/modifies any UI surface (web UI, CLI output, error messages) | +| Deployment Surface Tests (Docker, ECS, Helm) | Change adds/modifies any config parameter on any surface | +| E2E Tests | Change adds a workflow that spans multiple endpoints or services | + +Always include the heading for each category. If a category does not apply, replace the body with: `**Not Applicable** - {one-line justification}`. + +### Testing Plan Template (high level) + +```markdown +# Testing Plan: {Feature Name} + +*Created: {date}* +*Related LLD: `./lld.md`* +*Related Issue: `./github-issue.md`* + +## Overview +### Scope of Testing +{1-2 sentences describing what is being tested and why} + +### Prerequisites +- [ ] {Service running} +- [ ] {Auth tokens / fixtures available} + +### Shared Variables +```bash +export REGISTRY_URL="http://localhost" +export ACCESS_TOKEN=$(jq -r '.access_token' .oauth-tokens/ingress.json) +``` + +## 1. Functional Tests +### 1.1 curl / HTTP Tests +{One subsection per new or modified endpoint with command, expected status, expected response, assertions, and a negative case} + +### 1.2 CLI Tests +{One subsection per new or modified CLI command with exact invocation and expected output} + +## 2. Backwards Compatibility Tests +{Pre-change request shapes still accepted; CLI without new flags behaves as before; defaults preserve prior behavior} + +## 3. UX Tests +{Web UI flows; CLI output / error message clarity} + +## 4. Deployment Surface Tests +### 4.1 Docker wiring +### 4.2 Terraform / ECS wiring +### 4.3 Helm / EKS wiring +### 4.4 Deploy and verify +### 4.5 Rollback verification + +## 5. End-to-End API Tests +{Multi-step scenarios that exercise full business workflows} + +## 6. Test Execution Checklist +- [ ] Section 1 (Functional) passes +- [ ] Section 2 (Backwards Compat) verified or marked Not Applicable +- [ ] Section 3 (UX) verified or marked Not Applicable +- [ ] Section 4 (Deployment) verified or marked Not Applicable +- [ ] Section 5 (E2E) verified or marked Not Applicable +- [ ] Unit tests added under `tests/unit/` +- [ ] Integration tests added under `tests/integration/` +- [ ] `uv run pytest tests/` passes with no regressions +``` + +### Guidance for Generating testing.md + +1. Make tests copy-pasteable. Match the env var conventions used by existing scripts. +2. Cover every new endpoint and every new CLI command described in the LLD. +3. Anchor deployment tests on concrete files - reference exact Terraform/Helm/Docker file paths. +4. Mark Not Applicable explicitly. Do not silently omit sections. +5. Align with backwards-compat rules. Pre-change shapes must still be tested. +6. Do not invent endpoints or flags. Every URL, flag, and Terraform variable must exist in the LLD or codebase. + +## Step 9: Present Summary & Seek Guidance + +After producing the four artifacts, present a clear summary to the user. **Do not implement, run tests, push, commit, or open a PR.** This skill ends at delivery of the design package. + +```markdown +## Delivery Summary + +### Documents Created + +| Document | Location | Description | +|----------|----------|-------------| +| GitHub Issue | `benchmarks/swe-benchmark-data/{repo}/{problem}/{model}/github-issue.md` | Issue specification | +| Low-Level Design | `benchmarks/swe-benchmark-data/{repo}/{problem}/{model}/lld.md` | Technical design | +| Expert Review | `benchmarks/swe-benchmark-data/{repo}/{problem}/{model}/review.md` | Multi-persona review | +| Testing Plan | `benchmarks/swe-benchmark-data/{repo}/{problem}/{model}/testing.md` | All test categories | + +### Review Verdicts + +| Reviewer | Verdict | Blockers | Key Recommendations | +|----------|---------|----------|---------------------| +| Frontend (Pixel) | {verdict} | {count} | {summary} | +| Backend (Byte) | {verdict} | {count} | {summary} | +| SRE (Circuit) | {verdict} | {count} | {summary} | +| Security (Cipher) | {verdict} | {count} | {summary} | +| SMTS (Sage) | {verdict} | {count} | {summary} | + +### Configuration Parameters Proposed + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `PARAM_NAME` | type | value | {description} | + +### New Dependencies Proposed + +| Package | Type | Required By | +|---------|------|-------------| +| `package-name` | Python | Backend | + +### Estimated Effort (for a future implementer) + +| Category | Lines of Code | +|----------|---------------| +| New code | ~{X} | +| Tests | ~{X} | +| Modified | ~{X} | +| **Total** | **~{X}** | +``` + +### Seeking Guidance + +After the summary, ask the user: + +1. Are there any blockers from the expert review you want me to address by revising the design? +2. Would you like me to refine any artifact (e.g. expand a specific LLD section, add more test cases, tighten the issue spec)? +3. Should I open the GitHub issue against the upstream repo using `github-issue.md`? + +Do not implement code, run tests, push, commit, or open a PR until the user explicitly authorizes it as a separate request. + +--- + +## Important Guidelines + +### Design Principles +- Favor simple designs over unnecessary complexity +- Prefer straightforward code over clever solutions +- Design for maintainability by entry-level developers +- Add observability from the start, not as an afterthought + +### Documentation Quality +1. **Be Thorough**: The LLD should be detailed enough that someone unfamiliar with the codebase can implement it +2. **Use Diagrams**: ASCII diagrams help visualize the design +3. **Include Code**: Show actual or pseudo-code for key functions +4. **Specify Files**: Always mention which files to create/modify and approximate line numbers +5. **Consider All Aspects**: Think about error handling, logging, testing, and deployment +6. **Expert Reviews**: Make the reviews realistic - identify actual issues, not just praise + +### Hard Stops +1. **Do not implement code.** This skill produces design artifacts only. +2. **Do not run tests, linters, or builds against `repo/`.** Read the code, do not execute it. +3. **Do not commit, push, or open a PR.** +4. **Do not modify the cloned `repo/` tree.** It is gitignored input, not a workspace. + +## Example Usage + +User: "Run task 1 for mcp-gateway-registry with claude-opus-4-7." + +1. Look up task 1 in `benchmarks/swe-benchmark-data/README.md` (`remove-faiss`). Confirm `repo-name = mcp-gateway-registry`, `problem-name = remove-faiss`, `model-name = claude-opus-4-7`. Check that `benchmarks/swe-benchmark-data/mcp-gateway-registry/repo/` exists; if it does not, ask the user for the GitHub URL and tag, announce the clone command, and wait for approval. +2. Quick codebase review of `repo/` to find every FAISS reference (imports, dependencies, configs, docs) +3. Create `benchmarks/swe-benchmark-data/mcp-gateway-registry/remove-faiss/claude-opus-4-7/` +4. Write `github-issue.md` describing the FAISS removal task (problem, scope, acceptance criteria, out-of-scope) +5. Deep code analysis of FAISS usage and the maintained replacement +6. Write `lld.md` covering: files to edit, dependency removals, doc updates, fallback path +7. Write `review.md` with backend, SRE, security, and SMTS verdicts +8. Write `testing.md` with import-removal greps, backwards-compat tests, and a build/test pass plan +9. Present the four-artifact summary and ask whether to refine anything or open the GitHub issue upstream + +When the same problem is later run with a different model (e.g. `claude-sonnet-4-6`), repeat the workflow into `benchmarks/swe-benchmark-data/mcp-gateway-registry/remove-faiss/claude-sonnet-4-6/`. The two folders make per-model artifacts directly comparable inside the same repository. + +--- + +## Constraints + +- **No implementation.** Stop at the four design artifacts. +- **No execution.** Do not run pytest, ruff, mypy, terraform, docker, or any build/test command against the cloned `repo/`. Reading the code is fine; running it is out of scope. +- **No emojis, clever code, or em-dashes** in any output. +- **Naming**: always "Amazon Bedrock" (never "AWS Bedrock"). +- **Best Practices**: design recommendations should follow `CLAUDE.md` (logging, Pydantic, modularity) so that a future implementer's work will pass review. diff --git a/.gitignore b/.gitignore index f7e7db9..3718d6c 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,10 @@ ONE_PAGER.md .env .env.* .scratchpad/ + +# Benchmark target repositories are cloned locally by contributors at a specific tag. +# Keep the README and the generated artifacts tracked, but never commit the cloned source. +benchmarks/swe-benchmark-data/*/repo/ __pycache__/ *.pyc credentials.json @@ -25,3 +29,4 @@ security-review-findings.xlsx .litellm.pid .mantle-token .DS_Store +.venv/ diff --git a/README.md b/README.md index 53ec2d0..fe36539 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![License: MIT-0](https://img.shields.io/badge/License-MIT--0-yellow.svg)](LICENSE) [![Bedrock](https://img.shields.io/badge/Amazon-Bedrock-blue)](https://docs.aws.amazon.com/bedrock/latest/userguide/models-endpoint-availability.html) -[![Models: 43+](https://img.shields.io/badge/Models-43%2B%20from%2012%20providers-orange)](./) +[![Models: 45+](https://img.shields.io/badge/Models-45%2B%20from%2012%20providers-orange)](./) > **This is sample code intended for demonstration and learning purposes only.** > It is not meant for production use. Review and harden all scripts, configurations, @@ -10,15 +10,26 @@ ## Overview -[Claude Code](https://docs.anthropic.com/en/docs/claude-code) is Anthropic's -command-line coding agent. By default it talks to Anthropic's own models. This -repository shows how to run Claude Code against **any foundation model on Amazon -Bedrock** — including non-Anthropic models such as Qwen, DeepSeek, Kimi, Mistral, -and others — so you can pick the model that best fits each task instead of being -limited to one provider. +This repository does **two** things, in this order: -It does this without modifying Claude Code. Claude Code speaks the Anthropic -Messages API; most Bedrock models speak the OpenAI Chat Completions API. A small +1. **Run [Claude Code](https://docs.anthropic.com/en/docs/claude-code) against + non-Anthropic models.** Claude Code is Anthropic's command-line coding agent; + by default it talks only to Anthropic's own models. Here it's wired up to + any of 45 foundation models on Amazon Bedrock (Qwen, DeepSeek, Kimi, MiniMax, + Mistral, GPT-OSS, GLM, Gemma, Nemotron, Palmyra, plus the 7 native Anthropic + models), or to any open-source model you self-host on an EC2 GPU instance. +2. **Measure how well each of those models actually does coding work.** Once you + can swap models freely, the next question is: which model is good enough for + which task? The repo ships two complementary evaluation modes — a per-task + *Software Engineering* benchmark you point at any GitHub repo, and a single- + function *HumanEval* benchmark with published cross-model results. + +The first half is plumbing; the second is what makes the plumbing decision-grade. + +### How it runs Claude Code on non-Anthropic models + +Without modifying Claude Code. Claude Code speaks the Anthropic Messages API; +most Bedrock models speak the OpenAI Chat Completions API. A small [LiteLLM](https://github.com/BerriAI/litellm) proxy sits in between and translates the two, so Claude Code "just works" with whichever model you select. Native Anthropic models on Bedrock are called directly, with no proxy. @@ -27,36 +38,32 @@ Two deployment paths are provided: | Path | Models | Cost Model | Best For | |------|--------|------------|----------| -| [**Bedrock**](bedrock/) | 43 models from 12 providers | Pay-per-token | Model variety, zero infrastructure | +| [**Bedrock**](bedrock/) | 45 models from 12 providers | Pay-per-token | Model variety, zero infrastructure | | [**Self-Hosted (EC2)**](self-hosted/) | Any Ollama/vLLM model | Fixed hourly GPU cost | Data sovereignty, air-gapped, unlimited tokens | -**What you get:** - -- Run Claude Code with **43 Bedrock models** (5 native Anthropic + 38 third-party via Bedrock), or any open-source model you self-host on EC2 -- A one-command **LiteLLM proxy** that handles Anthropic↔OpenAI translation, tool calling, and streaming -- An interactive **model picker** and per-model launch scripts -- A reproducible **HumanEval benchmark** to compare model quality before you route work to a cheaper model (see [below](#benchmark)) +### How it measures the models -## Why this repo exists +Two evaluation modes ship with the repo. Pick the one that matches the question +you're trying to answer: -A coding agent session is token-heavy: tool calls, file reads, edits, and reasoning -steps all consume input and output tokens. On Amazon Bedrock, frontier models cost -roughly **5–20x more per token** than the cheapest non-Anthropic models on the same -endpoint (see the cost columns in the [Benchmark](#benchmark) table). Running every -task on a frontier model is therefore the most expensive default; running every -task on the cheapest model risks worse output. The interesting question is how -much quality you actually lose by routing routine tasks to a cheaper model — and -that depends on the task and the model. +| Mode | What it measures | Where the work lives | +|------|------------------|----------------------| +| **[SWE skill](#evaluation-1--swe-skill-real-world-tasks)** (real-world tasks) | Can the model take a real software-engineering problem in a real repo from idea to a complete design package — GitHub issue spec, low-level design, expert review, testing plan? | [.claude/skills/swe/](.claude/skills/swe/) → produces artifacts under [benchmarks/swe-benchmark-data/](benchmarks/swe-benchmark-data/) | +| **[HumanEval](#evaluation-2--humaneval-single-function-pass1)** (single-function pass@1) | On 164 small self-contained Python tasks, does the model emit a function body that passes the hidden unit tests? | [bedrock/benchmark/humaneval_runner.py](bedrock/benchmark/humaneval_runner.py) | -This repository exists to make that question answerable with data, not opinion: +> **"SWE" here means software engineering in general — not [SWE-bench](https://www.swebench.com/), +> the specific benchmark dataset.** The skill in this repo lets you run any model +> against any task in any repo of your choosing. It is a *harness*, not a fixed +> benchmark set. Compare results across models on the same task, or compare a +> single model across tasks of varying difficulty. -- It lets Claude Code run against **any** of 43 Bedrock models, not just Anthropic ones, so the same agent harness can be measured across the cost range -- It includes a **HumanEval pass@1 benchmark** that re-runs all 164 tasks through the agent for each model, with per-token cost listed alongside -- It also supports a **self-hosted EC2 path** for the case where the per-token model is the wrong cost shape (very high volume, or data must stay in your VPC) +**What you get end to end:** -The benchmark numbers below are evidence, not advertising — single-run pass@1 on -164 small Python tasks. Use them as a starting point and run your own evaluation -on workloads that look like yours before routing real traffic. +- Run Claude Code with **45 Bedrock models** (7 native Anthropic + 38 third-party via Bedrock), or any open-source model you self-host on EC2 +- A one-command **LiteLLM proxy** that handles Anthropic↔OpenAI translation, tool calling, and streaming +- An interactive **model picker** and per-model launch scripts +- A **`/swe` skill** for repo-grounded SWE benchmarking, plus a **`/summarize`** skill for after-action reporting (token usage, errors, themes per run) +- A reproducible **HumanEval benchmark** with cross-model pass@1 + per-token-cost numbers ## Architecture @@ -80,7 +87,7 @@ on workloads that look like yours before routing real traffic. │ Bedrock │ │ Bedrock │ │ │ │ │ ├──────────────┤ ├────────────────┤ - │ 5 Anthropic │ │ 38 third-party │ + │ 7 Anthropic │ │ 38 third-party │ │ │ │ │ │ • Opus │ │ • Qwen │ │ • Sonnet │ │ • Kimi │ @@ -117,7 +124,78 @@ Claude Code is pointed at `localhost`; the SSH tunnel transparently forwards every request to Ollama on the EC2 instance. No public ingress, no API keys — the only network path in is SSH. -## Benchmark +## Why this repo exists, briefly + +A coding agent session is token-heavy: tool calls, file reads, edits, and +reasoning steps all consume input and output tokens. On Amazon Bedrock, frontier +models cost roughly **5–20× more per token** than the cheapest non-Anthropic +models on the same endpoint. Running every task on a frontier model is the most +expensive default; running every task on the cheapest model risks worse output. + +The interesting question is *how much quality you actually lose* by routing +routine tasks to a cheaper model — and that depends on the task and the model. +The two evaluation modes below exist to make that question answerable with +data, not opinion. + +## Evaluation 1 — SWE skill (real-world tasks) + +The `/swe` skill runs Claude Code (backed by whichever model you've selected) +through a real software-engineering task in a real repository, and lands four +artifacts on disk that capture the model's reasoning end-to-end. The artifacts +are designed to be read by either a human reviewer or a separate LLM-as-judge. + +**Pipeline per run:** + +``` +{any-github-repo} ──► /swe ──► benchmarks/swe-benchmark-data/ + └─ {repo-name}/ + └─ {problem-name}/ + └─ {model-name}/ + ├─ github-issue.md # spec + ├─ lld.md # design + ├─ review.md # critique + └─ testing.md # test plan +``` + +The skill **stops at design**. It does not modify production code, run tests, +or open PRs. Whether the design is any good is a downstream evaluation step you +control: read the artifacts yourself, or feed them to another LLM judge. + +A second skill, `/summarize`, runs *after* `/swe` and produces a per-run report +covering artifact completeness, error signals from the session, token usage +broken down by model and cache type, and recurring themes from the conversation. +Useful when you're comparing many model+task combinations and don't want to eyeball +every transcript. + +### Worked example: `mcp-gateway-registry` + +The repo ships a worked example so you can see the harness producing real +artifacts before pointing it at your own code. The example target is +[agentic-community/mcp-gateway-registry](https://github.com/agentic-community/mcp-gateway-registry) +at tag `1.24.4`, with two problems scoped: + +| Problem | What the model has to do | +|---|---| +| `remove-faiss` | Find and remove every FAISS reference (imports, deps, config, docs) and verify nothing breaks | +| `remove-efs-from-terraform-aws-ecs` | Strip EFS out of `terraform/aws-ecs/` (file system, mount targets, security groups, task-definition mounts), keep `terraform validate` and `terraform plan` green | + +Two models already have artifacts on the first problem +(`qwen.qwen3-coder-next` and `qwen.qwen3-coder-480b-a35b-instruct`) so you can +compare them side-by-side without running the skill yourself. Setup, task +descriptions, and per-model invocation steps are in +[benchmarks/swe-benchmark-data/README.md](benchmarks/swe-benchmark-data/README.md). + +> **The example repo is the example, not the contract.** `/swe` works against +> any GitHub URL — clone the target you actually care about, write the task +> description, and run. + +> **Important — "SWE" ≠ [SWE-bench](https://www.swebench.com/).** This skill +> evaluates a model on *whatever problem you give it in whatever repo you point +> it at*, and the output is artifacts you grade. SWE-bench is a fixed dataset +> of GitHub issues with hidden test patches that grade themselves. The two are +> complementary, not interchangeable. + +## Evaluation 2 — HumanEval (single-function pass@1) We measured model quality on the public [HumanEval](https://github.com/openai/human-eval) benchmark (164 tasks), driving each task through Claude Code backed by each model @@ -137,13 +215,11 @@ the cost. Prices are on-demand Standard-tier rates for US East from the time of writing. Full method, caveats, and reproduce steps in [bedrock/README.md](bedrock/README.md#benchmark-humaneval). -> **These numbers are NOT comparable to [SWE-bench](https://www.swebench.com/).** -> HumanEval measures whether a model can write a single small Python function -> from a docstring; SWE-bench measures whether an agent can resolve a real -> multi-file GitHub issue against a large codebase. Frontier models score 95%+ -> on HumanEval but only 40–80% on SWE-bench. Use HumanEval as a quick quality -> signal for picking a routing tier, and SWE-bench (or your own production -> traffic) for evaluating end-to-end agent capability. +> **HumanEval is single-function code generation, not agentic editing.** +> Frontier models score 95%+ on HumanEval but only 40–80% on SWE-bench. +> Use HumanEval as a quick quality signal for picking a routing tier; use the +> SWE skill above (or your own production traffic) when you need to know whether +> a model can actually navigate a real codebase. ## Prerequisites @@ -157,20 +233,34 @@ time of writing. Full method, caveats, and reproduce steps in ## Get Started -Pick a path and follow its README — each one has full setup, configuration, and -a worked example: +Pick a path that matches what you're trying to do. + +**Just want to run a non-Anthropic model through Claude Code?** - **[bedrock/README.md](bedrock/README.md)** — Bedrock path. Start the LiteLLM - proxy and run Claude Code against any of the 43 models with `claude-model.sh`. + proxy and run Claude Code against any of the 45 models with `claude-model.sh`. - **[self-hosted/README.md](self-hosted/README.md)** — Self-hosted path. Provision a GPU instance, install Ollama, open an SSH tunnel, and run Claude Code against a model in your VPC. +**Want to benchmark a model on a real repo task?** + +- **[benchmarks/swe-benchmark-data/README.md](benchmarks/swe-benchmark-data/README.md)** — + Set up the example target (`mcp-gateway-registry`) or any GitHub repo of your + choosing, then invoke `/swe` from Claude Code. The skill produces four + artifacts per (problem, model) pair, ready for human or LLM-judge review. + +**Want the published HumanEval cross-model numbers?** + +- See the [Evaluation 2 — HumanEval](#evaluation-2--humaneval-single-function-pass1) + table above; full method and reproduce steps in + [bedrock/README.md](bedrock/README.md#benchmark-humaneval). + ## Comparison | | Bedrock | Self-Hosted (EC2) | |---|---|---| -| **Models** | 43 from 12 providers | Any GGUF/HF model | +| **Models** | 45 from 12 providers | Any GGUF/HF model | | **Pricing** | Per-token ($0.15-$15/M) | Per-hour ($0.84-$4.60/hr GPU) | | **Setup time** | 5 minutes | 15-20 minutes | | **Latency** | Varies by model (a few sec to minutes/task) | Depends on GPU + model size | @@ -190,16 +280,29 @@ claude-code-multi-model/ ├── SUPPORT.md ├── THIRD_PARTY Third-party dependency attributions ├── .github/ Issue and pull-request templates -├── bedrock/ ← Bedrock path (38 third-party + 5 Anthropic) -│ ├── README.md Full Bedrock setup guide + benchmark +├── .claude/ ← Claude Code skills shipped with the repo +│ └── skills/ +│ ├── swe/ /swe — drive a model through a SWE task on any repo +│ └── summarize/ /summarize — post-run report for a /swe attempt +├── benchmarks/ ← Output of /swe runs (the SWE evaluation mode) +│ └── swe-benchmark-data/ +│ ├── README.md Worked example + how to add your own target repo +│ └── mcp-gateway-registry/ +│ ├── repo/ (gitignored — contributor clones source here) +│ ├── remove-faiss/ +│ │ └── {model}/ github-issue.md, lld.md, review.md, testing.md +│ └── remove-efs-from-terraform-aws-ecs/ +├── bedrock/ ← Bedrock path (38 third-party + 7 Anthropic) +│ ├── README.md Full Bedrock setup guide + HumanEval benchmark +│ ├── pyproject.toml uv-managed deps for proxy + benchmark │ ├── scripts/ setup-proxy.sh, claude-model.sh, mantle-token.sh │ ├── config/ litellm-config.yaml, claude-proxy-settings.json │ └── benchmark/ HumanEval runner (humaneval_runner.py) + pass@1 results └── self-hosted/ ← EC2 self-hosted path (Ollama/vLLM) ├── README.md Full EC2 setup guide ├── SETUP-GUIDE.md Step-by-step GPU instance provisioning - ├── scripts/ setup.sh, run.sh, tunnel.sh - └── config/ litellm-config.yaml, model configs + ├── scripts/ ec2-setup.sh, claude-local.sh, tunnel.sh, bench.sh + └── config/ settings.template.json ``` ## See Also diff --git a/bedrock/README.md b/bedrock/README.md index 6d843c9..60c8535 100644 --- a/bedrock/README.md +++ b/bedrock/README.md @@ -1,14 +1,14 @@ # Claude Code Multi-Model on Amazon Bedrock -[![License: MIT-0](https://img.shields.io/badge/License-MIT--0-yellow.svg)](LICENSE) +[![License: MIT-0](https://img.shields.io/badge/License-MIT--0-yellow.svg)](../LICENSE) [![Bedrock](https://img.shields.io/badge/Amazon-Bedrock-blue)](https://docs.aws.amazon.com/bedrock/latest/userguide/models-endpoint-availability.html) -[![Models: 43](https://img.shields.io/badge/Models-43%20from%2012%20providers-orange)](./) +[![Models: 45](https://img.shields.io/badge/Models-45%20from%2012%20providers-orange)](./) > **This is sample code intended for demonstration and learning purposes only.** > It is not meant for production use. Review and harden all scripts, configurations, > and IAM permissions before using in any production or sensitive environment. -Run [Claude Code](https://docs.anthropic.com/en/docs/claude-code) with **any of 43 +Run [Claude Code](https://docs.anthropic.com/en/docs/claude-code) with **any of 45 foundation models on Amazon Bedrock** — not just Anthropic models. A LiteLLM proxy translates Claude Code's Anthropic Messages API to the OpenAI Chat Completions API that Bedrock's OpenAI-compatible third-party models speak, so you can route routine tasks to @@ -49,32 +49,36 @@ across models. **Why this endpoint?** Amazon Bedrock exposes a unified OpenAI-compatible endpoint for its non-Anthropic models. All 38 models support tool calling and streaming natively — no per-model configuration needed. -## Supported Models (43 total) +## Supported Models (45 total) -### Anthropic (5 — native Bedrock, no proxy) +> Pass the **raw Bedrock model ID** to `--model`. No aliases — what you type is what hits Bedrock. -| Alias | Model | Best For | -|-------|-------|----------| -| `claude-opus` | Claude Opus 4.6 | Flagship reasoning, complex tasks | -| `claude-sonnet` | Claude Sonnet 4.6 | Balanced speed/quality | -| `claude-haiku` | Claude Haiku 4.5 | Fast, lightweight tasks | -| `claude-opus-4.5` | Claude Opus 4.5 | Previous gen flagship | -| `claude-sonnet-4.5` | Claude Sonnet 4.5 | Previous gen balanced | +### Anthropic (7 — native Bedrock, no proxy) + +| Bedrock Model ID | Model | Best For | +|------------------|-------|----------| +| `us.anthropic.claude-opus-4-8` | Claude Opus 4.8 | Newest flagship | +| `us.anthropic.claude-opus-4-7` | Claude Opus 4.7 | Flagship | +| `us.anthropic.claude-opus-4-6-v1` | Claude Opus 4.6 | Flagship, strong reasoning | +| `us.anthropic.claude-sonnet-4-6` | Claude Sonnet 4.6 | Balanced speed/quality | +| `us.anthropic.claude-haiku-4-5-20251001-v1:0` | Claude Haiku 4.5 | Fast, lightweight tasks | +| `us.anthropic.claude-opus-4-5-20251101-v1:0` | Claude Opus 4.5 | Previous gen flagship | +| `us.anthropic.claude-sonnet-4-5-20250929-v1:0` | Claude Sonnet 4.5 | Previous gen balanced | ### Third-Party (38 — via LiteLLM proxy → Amazon Bedrock) -| Provider | Models | Aliases | -|----------|--------|---------| -| **Qwen** (7) | Coder Next, Coder 480B, Coder 30B, 235B, 32B, VL 235B, Next 80B | `qwen-coder-next`, `qwen-coder-480b`, `qwen-coder-30b`, `qwen-235b`, `qwen-32b`, `qwen-vl-235b`, `qwen-next-80b` | -| **DeepSeek** (2) | V3.2, V3.1 | `deepseek-v3`, `deepseek-v3.1` | -| **Mistral** (8) | Devstral 123B, Large 3 675B, Magistral Small, Ministral 14B/8B/3B, Voxtral Small/Mini | `devstral-123b`, `mistral-large-3`, `magistral-small`, `ministral-14b`, `ministral-8b`, `ministral-3b`, `voxtral-small-24b`, `voxtral-mini-3b` | -| **Moonshot AI** (2) | Kimi K2.5, K2 Thinking | `kimi-k2.5`, `kimi-k2-thinking` | -| **MiniMax** (3) | M2, M2.1, M2.5 | `minimax-m2`, `minimax-m2.1`, `minimax-m2.5` | -| **NVIDIA** (4) | Nemotron Super 120B, Nano 30B/12B/9B | `nemotron-super-120b`, `nemotron-nano-30b`, `nemotron-nano-12b`, `nemotron-nano-9b` | -| **OpenAI** (4) | GPT OSS 120B/20B, Safeguard 120B/20B | `gpt-oss-120b`, `gpt-oss-20b`, `gpt-oss-safeguard-120b`, `gpt-oss-safeguard-20b` | -| **Z.AI** (4) | GLM 5, 4.7, 4.7 Flash, 4.6 | `glm-5`, `glm-4.7`, `glm-4.7-flash`, `glm-4.6` | -| **Google** (3) | Gemma 3 27B/12B/4B | `gemma-3-27b`, `gemma-3-12b`, `gemma-3-4b` | -| **Writer** (1) | Palmyra Vision 7B | `palmyra-vision-7b` | +| Provider | Models | Bedrock Model IDs | +|----------|--------|-------------------| +| **Qwen** (7) | Coder Next, Coder 480B, Coder 30B, 235B, 32B, VL 235B, Next 80B | `qwen.qwen3-coder-next`, `qwen.qwen3-coder-480b-a35b-instruct`, `qwen.qwen3-coder-30b-a3b-instruct`, `qwen.qwen3-235b-a22b-2507`, `qwen.qwen3-32b`, `qwen.qwen3-vl-235b-a22b-instruct`, `qwen.qwen3-next-80b-a3b-instruct` | +| **DeepSeek** (2) | V3.2, V3.1 | `deepseek.v3.2`, `deepseek.v3.1` | +| **Mistral** (8) | Devstral 123B, Large 3 675B, Magistral Small, Ministral 14B/8B/3B, Voxtral Small/Mini | `mistral.devstral-2-123b`, `mistral.mistral-large-3-675b-instruct`, `mistral.magistral-small-2509`, `mistral.ministral-3-14b-instruct`, `mistral.ministral-3-8b-instruct`, `mistral.ministral-3-3b-instruct`, `mistral.voxtral-small-24b-2507`, `mistral.voxtral-mini-3b-2507` | +| **Moonshot AI** (2) | Kimi K2.5, K2 Thinking | `moonshotai.kimi-k2.5`, `moonshotai.kimi-k2-thinking` | +| **MiniMax** (3) | M2, M2.1, M2.5 | `minimax.minimax-m2`, `minimax.minimax-m2.1`, `minimax.minimax-m2.5` | +| **NVIDIA** (4) | Nemotron Super 120B, Nano 30B/12B/9B | `nvidia.nemotron-super-3-120b`, `nvidia.nemotron-nano-3-30b`, `nvidia.nemotron-nano-12b-v2`, `nvidia.nemotron-nano-9b-v2` | +| **OpenAI** (4) | GPT OSS 120B/20B, Safeguard 120B/20B | `openai.gpt-oss-120b`, `openai.gpt-oss-20b`, `openai.gpt-oss-safeguard-120b`, `openai.gpt-oss-safeguard-20b` | +| **Z.AI** (4) | GLM 5, 4.7, 4.7 Flash, 4.6 | `zai.glm-5`, `zai.glm-4.7`, `zai.glm-4.7-flash`, `zai.glm-4.6` | +| **Google** (3) | Gemma 3 27B/12B/4B | `google.gemma-3-27b-it`, `google.gemma-3-12b-it`, `google.gemma-3-4b-it` | +| **Writer** (1) | Palmyra Vision 7B | `writer.palmyra-vision-7b` | > **Note:** Meta Llama, Amazon Nova, and DeepSeek R1 are available on Bedrock but **not** through the OpenAI-compatible endpoint — they lack tool calling support required by Claude Code. @@ -106,11 +110,10 @@ not harness artifacts. > Bedrock also offers Priority, Flex, and Batch tiers at different prices. > > **Sonnet versions:** The table uses **Claude Sonnet 4.6** -> (`us.anthropic.claude-sonnet-4-6`), which is what the `claude-sonnet` alias in -> [scripts/claude-model.sh](scripts/claude-model.sh) pins. For reference, Claude -> Code's *built-in* default Sonnet alias (no explicit pin) resolves to **Sonnet -> 4.5** and scored 99.4% (163/164) in a separate run — single-run pass@1 varies -> by a few tasks between versions and runs, so treat the two as comparable. +> (`us.anthropic.claude-sonnet-4-6`). For reference, Claude Code's *built-in* +> default Sonnet alias (no explicit `--model` flag) resolves to **Sonnet 4.5** +> and scored 99.4% (163/164) in a separate run — single-run pass@1 varies by +> a few tasks between versions and runs, so treat the two as comparable. **Reproduce:** @@ -118,7 +121,7 @@ not harness artifacts. cd benchmark # Start the proxy first (for the non-Anthropic models) ../scripts/setup-proxy.sh -python3 humaneval_runner.py --models claude-sonnet,qwen-coder-30b,kimi-k2.5,qwen-coder-next,deepseek-v3 --all +python3 humaneval_runner.py --models us.anthropic.claude-sonnet-4-6,qwen.qwen3-coder-30b-a3b-instruct,moonshotai.kimi-k2.5,qwen.qwen3-coder-next,deepseek.v3.2 --all ``` Raw results (per-task CSV + summary) are saved under `benchmark/results/`. @@ -140,6 +143,7 @@ standard `pass@1` method. - **AWS Account** with Bedrock model access enabled - **AWS CLI** configured (`aws configure` or IAM role/SSO) - **Python 3.9+** (for LiteLLM proxy and token generation) +- **[uv](https://docs.astral.sh/uv/getting-started/installation/)** for Python dependency management - **Claude Code CLI** installed ([docs](https://docs.anthropic.com/en/docs/claude-code)) ## Quick Start @@ -147,43 +151,64 @@ standard `pass@1` method. ### 1. Clone and setup ```bash -git clone https://github.com/shekharprateek/claude-code-multi-model-bedrock.git -cd claude-code-multi-model-bedrock +git clone https://github.com/aws-samples/sample-claude-code-multi-model.git +cd sample-claude-code-multi-model/bedrock chmod +x scripts/*.sh ``` -### 2. Use Anthropic models (no proxy needed) +### 2. Install Python dependencies with `uv` + +If you don't already have `uv`, install it (one-line; full instructions in the +[uv docs](https://docs.astral.sh/uv/getting-started/installation/)): + +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +``` + +Then create a virtual environment and install the proxy + benchmark dependencies +(`litellm[proxy]`, `aws-bedrock-token-generator`, `datasets`): + +```bash +uv sync # creates .venv/ and installs everything +source .venv/bin/activate +``` + +`uv sync` reads [pyproject.toml](pyproject.toml) and produces a reproducible +install. Activating the venv puts `litellm` on your `PATH` so the proxy script +can find it. + +### 3. Use Anthropic models (no proxy needed) ```bash -./scripts/claude-model.sh --model claude-opus -./scripts/claude-model.sh --model claude-sonnet -./scripts/claude-model.sh --model claude-haiku +./scripts/claude-model.sh --model us.anthropic.claude-opus-4-8 +./scripts/claude-model.sh --model us.anthropic.claude-sonnet-4-6 +./scripts/claude-model.sh --model us.anthropic.claude-haiku-4-5-20251001-v1:0 ``` -### 3. Use third-party models (proxy required) +### 4. Use third-party models (proxy required) ```bash # Step 1: Start the LiteLLM proxy (generates Bedrock token, installs deps) ./scripts/setup-proxy.sh # Step 2: Run Claude Code with any model -./scripts/claude-model.sh --model qwen-coder-next -./scripts/claude-model.sh --model deepseek-v3 -./scripts/claude-model.sh --model kimi-k2.5 -./scripts/claude-model.sh --model devstral-123b +./scripts/claude-model.sh --model qwen.qwen3-coder-next +./scripts/claude-model.sh --model deepseek.v3.2 +./scripts/claude-model.sh --model moonshotai.kimi-k2.5 +./scripts/claude-model.sh --model mistral.devstral-2-123b # With a prompt -./scripts/claude-model.sh --model qwen-coder-next -p "write a Python REST API" +./scripts/claude-model.sh --model qwen.qwen3-coder-next -p "write a Python REST API" ``` -### 4. Interactive model picker +### 5. Interactive model picker ```bash ./scripts/claude-model.sh -# Shows numbered list of all 43 models — pick one +# Shows numbered list of all 45 models — pick one ``` -### 5. List all available models +### 6. List all available models ```bash ./scripts/claude-model.sh --list @@ -195,7 +220,7 @@ chmod +x scripts/*.sh # Start proxy (installs litellm + token generator if needed) ./scripts/setup-proxy.sh -# Custom port +# Custom port (default: 4000) ./scripts/setup-proxy.sh --port 8080 # Check status @@ -211,6 +236,33 @@ chmod +x scripts/*.sh tail -f .litellm.log ``` +### Bind address (`--host`) + +By default the proxy binds to **`127.0.0.1`** — Claude Code on the same machine +can reach it, nothing else can. The proxy does not authenticate its own clients +(the only auth gate is the Bedrock bearer token it uses upstream), so leaving +the default in place is the safe choice. + +If you need clients on other hosts to reach the proxy (e.g. Claude Code running +in a different VM in the same VPC, or a containerized client using a separate +network namespace), override the bind: + +```bash +# Bind to a specific private IP — only that interface accepts connections +./scripts/setup-proxy.sh --host 10.0.1.42 + +# Bind to all interfaces — careful, see warning below +./scripts/setup-proxy.sh --host 0.0.0.0 +``` + +When `--host` is anything other than `127.0.0.1` / `localhost` the script prints +a warning. Treat the security-group / firewall layer as the only thing standing +between the proxy and the outside world, and: + +- restrict ingress to port 4000 (or whatever `--port` you used) to known source CIDRs +- prefer a private IP over `0.0.0.0` so the proxy only listens on the interface that needs it +- run on a private subnet rather than something with a public IP attached + ## Manual Configuration (No Scripts) ### Anthropic models (native Bedrock) @@ -234,7 +286,7 @@ LITELLM_USE_CHAT_COMPLETIONS_URL_FOR_ANTHROPIC_MESSAGES=true \ ANTHROPIC_BASE_URL=http://localhost:4000 \ ANTHROPIC_API_KEY=bedrock-proxy \ claude --settings config/claude-proxy-settings.json \ - --model qwen-coder-next + --model qwen.qwen3-coder-next ``` > **Important:** The `--settings config/claude-proxy-settings.json` flag disables Bedrock native mode (`CLAUDE_CODE_USE_BEDROCK=0`) so Claude Code routes through the proxy instead. Without it, Claude Code may try to connect directly to Bedrock and fail for non-Anthropic model IDs. @@ -244,16 +296,16 @@ claude --settings config/claude-proxy-settings.json \ Add to `~/.zshrc` or `~/.bashrc`: ```bash -# Native Bedrock models -alias cc-opus='CLAUDE_CODE_USE_BEDROCK=1 AWS_REGION=us-east-1 claude' -alias cc-sonnet='CLAUDE_CODE_USE_BEDROCK=1 AWS_REGION=us-east-1 claude' +# Native Bedrock models — pin a specific Anthropic Bedrock model ID +alias cc-opus='CLAUDE_CODE_USE_BEDROCK=1 AWS_REGION=us-east-1 ANTHROPIC_MODEL=us.anthropic.claude-opus-4-6-v1 claude' +alias cc-sonnet='CLAUDE_CODE_USE_BEDROCK=1 AWS_REGION=us-east-1 ANTHROPIC_MODEL=us.anthropic.claude-sonnet-4-6 claude' # Proxy models (requires LiteLLM running on :4000) CC_PROXY="ANTHROPIC_BASE_URL=http://localhost:4000 ANTHROPIC_API_KEY=bedrock-proxy" -alias cc-qwen="$CC_PROXY claude --settings ~/claude-code-multi-model-bedrock/config/claude-proxy-settings.json --model qwen-coder-next" -alias cc-deepseek="$CC_PROXY claude --settings ~/claude-code-multi-model-bedrock/config/claude-proxy-settings.json --model deepseek-v3" -alias cc-devstral="$CC_PROXY claude --settings ~/claude-code-multi-model-bedrock/config/claude-proxy-settings.json --model devstral-123b" -alias cc-kimi="$CC_PROXY claude --settings ~/claude-code-multi-model-bedrock/config/claude-proxy-settings.json --model kimi-k2.5" +alias cc-qwen="$CC_PROXY claude --settings ~/sample-claude-code-multi-model/bedrock/config/claude-proxy-settings.json --model qwen.qwen3-coder-next" +alias cc-deepseek="$CC_PROXY claude --settings ~/sample-claude-code-multi-model/bedrock/config/claude-proxy-settings.json --model deepseek.v3.2" +alias cc-devstral="$CC_PROXY claude --settings ~/sample-claude-code-multi-model/bedrock/config/claude-proxy-settings.json --model mistral.devstral-2-123b" +alias cc-kimi="$CC_PROXY claude --settings ~/sample-claude-code-multi-model/bedrock/config/claude-proxy-settings.json --model moonshotai.kimi-k2.5" ``` ## What's Inside @@ -261,7 +313,7 @@ alias cc-kimi="$CC_PROXY claude --settings ~/claude-code-multi-model-bedrock/con | File | What it does | | --- | --- | | [scripts/setup-proxy.sh](scripts/setup-proxy.sh) | One-command proxy setup: generates Bedrock token, installs LiteLLM, starts proxy | -| [scripts/claude-model.sh](scripts/claude-model.sh) | Interactive model picker / launcher for all 43 models | +| [scripts/claude-model.sh](scripts/claude-model.sh) | Interactive model picker / launcher for all 45 models | | [scripts/mantle-token.sh](scripts/mantle-token.sh) | Standalone Bedrock bearer token generator (12h validity) | | [config/litellm-config.yaml](config/litellm-config.yaml) | LiteLLM proxy config with all 38 models | | [config/claude-proxy-settings.json](config/claude-proxy-settings.json) | Claude Code settings override (disables native Bedrock mode) | @@ -297,8 +349,8 @@ alias cc-kimi="$CC_PROXY claude --settings ~/claude-code-multi-model-bedrock/con ## See Also -- **[Claude Code on Amazon EC2](https://github.com/shekharprateek/claude-code-on-amazon-ec2)** — Run Claude Code backed by a self-hosted open-source model (Ollama + Qwen 3.5) on an EC2 GPU instance. Fixed hourly cost, data stays in your VPC. +- **[Claude Code on Amazon EC2](../self-hosted/README.md)** — Run Claude Code backed by a self-hosted open-source model (Ollama + Qwen 3.5) on an EC2 GPU instance. Fixed hourly cost, data stays in your VPC. ## License -This library is licensed under the MIT-0 License. See the [LICENSE](LICENSE) file. +This library is licensed under the MIT-0 License. See the [LICENSE](../LICENSE) file. diff --git a/bedrock/benchmark/humaneval_runner.py b/bedrock/benchmark/humaneval_runner.py index be73136..6ae112a 100644 --- a/bedrock/benchmark/humaneval_runner.py +++ b/bedrock/benchmark/humaneval_runner.py @@ -12,8 +12,8 @@ and deterministic. Usage: - python3 humaneval_runner.py --models claude-sonnet,qwen-coder-30b --tasks 20 - python3 humaneval_runner.py --models claude-sonnet --all + python3 humaneval_runner.py --models us.anthropic.claude-sonnet-4-6,qwen.qwen3-coder-30b-a3b-instruct --tasks 20 + python3 humaneval_runner.py --models us.anthropic.claude-sonnet-4-6 --all """ import argparse @@ -35,7 +35,13 @@ sys.exit(1) -MODELS = ["claude-sonnet", "qwen-coder-next", "deepseek-v3", "kimi-k2.5", "qwen-coder-30b"] +MODELS = [ + "us.anthropic.claude-sonnet-4-6", + "qwen.qwen3-coder-next", + "deepseek.v3.2", + "moonshotai.kimi-k2.5", + "qwen.qwen3-coder-30b-a3b-instruct", +] PROXY_PORT = 4000 TIMEOUT_PER_TASK = 180 # 3 min: an agent loop on one small function TEST_TIMEOUT = 30 # seconds to run the generated code + unit tests @@ -47,10 +53,9 @@ # Pin the HumanEval dataset to a known revision so the benchmark is reproducible. HUMANEVAL_DATASET = "openai_humaneval" HUMANEVAL_REVISION = "7dce6050a7d6d172f3cc5c32aa97f52fa1a2e544" -# Explicit native Bedrock model/inference-profile IDs for pinned Sonnet versions. -NATIVE_MODEL_IDS = { - "claude-sonnet-46": "us.anthropic.claude-sonnet-4-6", -} +# Native Bedrock IDs always start with `anthropic.` (or `us.anthropic.` for +# cross-region inference profiles). Anything else routes through the proxy. +NATIVE_PREFIXES = ("anthropic.", "us.anthropic.") def _get_clean_config_dir(): @@ -128,7 +133,7 @@ def run_claude_code(task, model): """Run Claude Code (backed by `model`) on one HumanEval task in an isolated temp dir. Returns (completion_text, elapsed, status).""" prompt = build_prompt(task) - is_native = model.startswith("claude-") + is_native = model.startswith(NATIVE_PREFIXES) env = os.environ.copy() # Use a clean Claude config dir so any user-level settings.json (which may @@ -146,11 +151,7 @@ def run_claude_code(task, model): env["AWS_REGION"] = "us-east-1" env.pop("ANTHROPIC_BASE_URL", None) env.pop("ANTHROPIC_API_KEY", None) - # Pin an explicit Bedrock model/inference-profile when requested. - # `claude-sonnet` -> Claude Code's default alias (resolves to Sonnet 4.5) - # `claude-sonnet-46` -> Sonnet 4.6 via its cross-region inference profile - if model in NATIVE_MODEL_IDS: - env["ANTHROPIC_MODEL"] = NATIVE_MODEL_IDS[model] + env["ANTHROPIC_MODEL"] = model else: env["ANTHROPIC_BASE_URL"] = f"http://localhost:{PROXY_PORT}" env["ANTHROPIC_API_KEY"] = "bedrock-proxy" diff --git a/bedrock/config/litellm-config.yaml b/bedrock/config/litellm-config.yaml index 3564f8e..6207078 100644 --- a/bedrock/config/litellm-config.yaml +++ b/bedrock/config/litellm-config.yaml @@ -7,270 +7,257 @@ # Base URL: hardcoded to us-east-1 (only available region) # The setup-proxy.sh script handles token generation automatically. # +# Naming: model_name is the raw Bedrock model ID (no aliases). Pass it +# directly via --model so what you type is what hits Bedrock. +# # Architecture: # Claude Code (Anthropic Messages API) # -> LiteLLM Proxy (localhost:4000, translates Anthropic -> OpenAI format) # -> Amazon Bedrock (Chat Completions API, bearer token auth) -# -> Any of 39 models from 12+ providers +# -> Any of 38 third-party models from 11 providers # # Usage: litellm --config config/litellm-config.yaml --port 4000 model_list: # ── Qwen — Coding ────────────────────────────────────────────── - - model_name: qwen-coder-next + - model_name: qwen.qwen3-coder-next litellm_params: model: openai/qwen.qwen3-coder-next api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: qwen-coder-480b + - model_name: qwen.qwen3-coder-480b-a35b-instruct litellm_params: model: openai/qwen.qwen3-coder-480b-a35b-instruct api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: qwen-coder-30b + - model_name: qwen.qwen3-coder-30b-a3b-instruct litellm_params: model: openai/qwen.qwen3-coder-30b-a3b-instruct api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY # ── Qwen — General / Vision ──────────────────────────────────── - - model_name: qwen-235b + - model_name: qwen.qwen3-235b-a22b-2507 litellm_params: model: openai/qwen.qwen3-235b-a22b-2507 api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: qwen-32b + - model_name: qwen.qwen3-32b litellm_params: model: openai/qwen.qwen3-32b api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: qwen-vl-235b + - model_name: qwen.qwen3-vl-235b-a22b-instruct litellm_params: model: openai/qwen.qwen3-vl-235b-a22b-instruct api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: qwen-next-80b + - model_name: qwen.qwen3-next-80b-a3b-instruct litellm_params: model: openai/qwen.qwen3-next-80b-a3b-instruct api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY # ── DeepSeek ──────────────────────────────────────────────────── - - model_name: deepseek-v3 + - model_name: deepseek.v3.2 litellm_params: model: openai/deepseek.v3.2 api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: deepseek-v3.1 + - model_name: deepseek.v3.1 litellm_params: model: openai/deepseek.v3.1 api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY # ── Mistral AI ────────────────────────────────────────────────── - - model_name: devstral-123b + - model_name: mistral.devstral-2-123b litellm_params: model: openai/mistral.devstral-2-123b api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: mistral-large-3 + - model_name: mistral.mistral-large-3-675b-instruct litellm_params: model: openai/mistral.mistral-large-3-675b-instruct api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: magistral-small + - model_name: mistral.magistral-small-2509 litellm_params: model: openai/mistral.magistral-small-2509 api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: ministral-14b + - model_name: mistral.ministral-3-14b-instruct litellm_params: model: openai/mistral.ministral-3-14b-instruct api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: ministral-8b + - model_name: mistral.ministral-3-8b-instruct litellm_params: model: openai/mistral.ministral-3-8b-instruct api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: ministral-3b + - model_name: mistral.ministral-3-3b-instruct litellm_params: model: openai/mistral.ministral-3-3b-instruct api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: voxtral-small-24b + - model_name: mistral.voxtral-small-24b-2507 litellm_params: model: openai/mistral.voxtral-small-24b-2507 api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: voxtral-mini-3b + - model_name: mistral.voxtral-mini-3b-2507 litellm_params: model: openai/mistral.voxtral-mini-3b-2507 api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY # ── Moonshot AI (Kimi) ────────────────────────────────────────── - - model_name: kimi-k2.5 + - model_name: moonshotai.kimi-k2.5 litellm_params: model: openai/moonshotai.kimi-k2.5 api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: kimi-k2-thinking + - model_name: moonshotai.kimi-k2-thinking litellm_params: model: openai/moonshotai.kimi-k2-thinking api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY # ── MiniMax ───────────────────────────────────────────────────── - - model_name: minimax-m2 + - model_name: minimax.minimax-m2 litellm_params: model: openai/minimax.minimax-m2 api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: minimax-m2.1 + - model_name: minimax.minimax-m2.1 litellm_params: model: openai/minimax.minimax-m2.1 api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: minimax-m2.5 + - model_name: minimax.minimax-m2.5 litellm_params: model: openai/minimax.minimax-m2.5 api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY # ── NVIDIA Nemotron ───────────────────────────────────────────── - - model_name: nemotron-super-120b + - model_name: nvidia.nemotron-super-3-120b litellm_params: model: openai/nvidia.nemotron-super-3-120b api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: nemotron-nano-30b + - model_name: nvidia.nemotron-nano-3-30b litellm_params: model: openai/nvidia.nemotron-nano-3-30b api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: nemotron-nano-12b + - model_name: nvidia.nemotron-nano-12b-v2 litellm_params: model: openai/nvidia.nemotron-nano-12b-v2 api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: nemotron-nano-9b + - model_name: nvidia.nemotron-nano-9b-v2 litellm_params: model: openai/nvidia.nemotron-nano-9b-v2 api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY # ── OpenAI (GPT OSS on Bedrock) ──────────────────────────────── - - model_name: gpt-oss-120b + - model_name: openai.gpt-oss-120b litellm_params: model: openai/openai.gpt-oss-120b api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: gpt-oss-20b + - model_name: openai.gpt-oss-20b litellm_params: model: openai/openai.gpt-oss-20b api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: gpt-oss-safeguard-120b + - model_name: openai.gpt-oss-safeguard-120b litellm_params: model: openai/openai.gpt-oss-safeguard-120b api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: gpt-oss-safeguard-20b + - model_name: openai.gpt-oss-safeguard-20b litellm_params: model: openai/openai.gpt-oss-safeguard-20b api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY # ── Z.AI (GLM) ───────────────────────────────────────────────── - - model_name: glm-5 + - model_name: zai.glm-5 litellm_params: model: openai/zai.glm-5 api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: glm-4.7 + - model_name: zai.glm-4.7 litellm_params: model: openai/zai.glm-4.7 api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: glm-4.7-flash + - model_name: zai.glm-4.7-flash litellm_params: model: openai/zai.glm-4.7-flash api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: glm-4.6 + - model_name: zai.glm-4.6 litellm_params: model: openai/zai.glm-4.6 api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY # ── Google (Gemma) ────────────────────────────────────────────── - - model_name: gemma-3-27b + - model_name: google.gemma-3-27b-it litellm_params: model: openai/google.gemma-3-27b-it api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: gemma-3-12b + - model_name: google.gemma-3-12b-it litellm_params: model: openai/google.gemma-3-12b-it api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - - model_name: gemma-3-4b + - model_name: google.gemma-3-4b-it litellm_params: model: openai/google.gemma-3-4b-it api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY # ── Writer (Palmyra) ─────────────────────────────────────────── - - model_name: palmyra-vision-7b + - model_name: writer.palmyra-vision-7b litellm_params: model: openai/writer.palmyra-vision-7b api_base: https://bedrock-mantle.us-east-1.api.aws/v1 api_key: os.environ/MANTLE_API_KEY - # ── Self-Hosted (Ollama via SSH tunnel) ───────────────────────── - # Uncomment these if you have an Ollama server running via SSH tunnel. - # Start tunnel first: SSH_KEY=~/.ssh/key GPU_SERVER_IP=x.x.x.x ./scripts/tunnel.sh start - # - # - model_name: qwen-local - # litellm_params: - # model: openai/qwen3.5:35b - # api_base: http://localhost:11434/v1 - # api_key: local - # - # - model_name: deepseek-local - # litellm_params: - # model: openai/deepseek-r1:32b - # api_base: http://localhost:11434/v1 - # api_key: local - litellm_settings: drop_params: true # drop unsupported params instead of erroring num_retries: 2 diff --git a/bedrock/pyproject.toml b/bedrock/pyproject.toml new file mode 100644 index 0000000..3a69238 --- /dev/null +++ b/bedrock/pyproject.toml @@ -0,0 +1,22 @@ +[project] +name = "nyc-data-analytics" +version = "0.1.0" +description = "Simple NYC dataset analytics using Polars" +requires-python = ">=3.11" +dependencies = [ + "polars>=0.20.0", + "pyarrow>=14.0.0", # For Parquet/CSV I/O + "requests>=2.31.0", # For downloading datasets + "typer>=0.9.0", # For CLI +] + +[tool.uv] +dev-dependencies = [ + "ruff>=0.4.0", # Linting/formatting + "mypy>=1.9.0", # Type checking + "pytest>=7.4.0", # Testing +] + +[build-system] +requires = ["uv>=0.1.0"] +build-backend = "uv.internals.build_backend" diff --git a/bedrock/scripts/claude-model.sh b/bedrock/scripts/claude-model.sh index 9d46027..d2b6f67 100755 --- a/bedrock/scripts/claude-model.sh +++ b/bedrock/scripts/claude-model.sh @@ -2,19 +2,19 @@ set -euo pipefail # --------------------------------------------------------------------------- -# claude-model.sh — Run Claude Code with any Bedrock model +# claude-model.sh — Run Claude Code with any Bedrock model (raw model IDs) # # For Anthropic models: connects directly to Bedrock (no proxy needed) # For third-party models: routes through LiteLLM proxy -> Amazon Bedrock # -# All 38 models support tools + streaming natively. +# All 38 third-party models support tools + streaming natively. # # Usage: -# ./scripts/claude-model.sh # interactive: pick a model -# ./scripts/claude-model.sh --model qwen-coder-next -# ./scripts/claude-model.sh --model claude-opus # native Bedrock -# ./scripts/claude-model.sh --model claude-sonnet -p "explain this code" -# ./scripts/claude-model.sh --list # list available models +# ./scripts/claude-model.sh # interactive picker +# ./scripts/claude-model.sh --model qwen.qwen3-coder-next +# ./scripts/claude-model.sh --model us.anthropic.claude-sonnet-4-6 # native Bedrock +# ./scripts/claude-model.sh --model us.anthropic.claude-sonnet-4-6 -p "..." +# ./scripts/claude-model.sh --list # # Environment: # PROXY_PORT LiteLLM proxy port (default: 4000) @@ -27,79 +27,79 @@ PROXY_PORT="${PROXY_PORT:-4000}" AWS_REGION="${AWS_REGION:-us-east-1}" # ── Model Registry ──────────────────────────────────────────────── -# Format: alias|type|model_id|description -# type: "native" = direct Bedrock, "proxy" = via LiteLLM -> Bedrock +# Format: model_id|type|description +# model_id: raw Bedrock model id (what is sent on the wire) +# type : "native" = direct Bedrock (Anthropic only) +# "proxy" = via LiteLLM -> Amazon Bedrock MODELS=( # ── Anthropic (native — no proxy needed) ────────────────────── - "claude-opus|native|us.anthropic.claude-opus-4-6-v1|Claude Opus 4.6 — flagship, best reasoning" - "claude-sonnet|native|us.anthropic.claude-sonnet-4-6|Claude Sonnet 4.6 — balanced speed/quality" - "claude-haiku|native|us.anthropic.claude-haiku-4-5-20251001-v1:0|Claude Haiku 4.5 — fast, lightweight" - "claude-opus-4.5|native|us.anthropic.claude-opus-4-5-20251101-v1:0|Claude Opus 4.5 — previous gen flagship" - "claude-sonnet-4.5|native|us.anthropic.claude-sonnet-4-5-20250929-v1:0|Claude Sonnet 4.5 — previous gen balanced" + "us.anthropic.claude-opus-4-8|native|Claude Opus 4.8 — latest flagship as of June 5 2026" + "us.anthropic.claude-opus-4-7|native|Claude Opus 4.7 — flagship" + "us.anthropic.claude-opus-4-6-v1|native|Claude Opus 4.6 — flagship, strong reasoning" + "us.anthropic.claude-sonnet-4-6|native|Claude Sonnet 4.6 — balanced speed/quality" + "us.anthropic.claude-haiku-4-5-20251001-v1:0|native|Claude Haiku 4.5 — fast, lightweight" + "us.anthropic.claude-opus-4-5-20251101-v1:0|native|Claude Opus 4.5 — previous gen flagship" + "us.anthropic.claude-sonnet-4-5-20250929-v1:0|native|Claude Sonnet 4.5 — previous gen balanced" # ── Qwen — Coding (via Bedrock) ──────────────────────────────── - "qwen-coder-next|proxy|qwen-coder-next|Qwen3 Coder Next — latest coding model" - "qwen-coder-480b|proxy|qwen-coder-480b|Qwen3 Coder 480B — largest coding MoE" - "qwen-coder-30b|proxy|qwen-coder-30b|Qwen3 Coder 30B — compact coding MoE" + "qwen.qwen3-coder-next|proxy|Qwen3 Coder Next — latest coding model as of June 5 2026" + "qwen.qwen3-coder-480b-a35b-instruct|proxy|Qwen3 Coder 480B — largest coding MoE" + "qwen.qwen3-coder-30b-a3b-instruct|proxy|Qwen3 Coder 30B — compact coding MoE" # ── Qwen — General / Vision (via Bedrock) ────────────────────── - "qwen-235b|proxy|qwen-235b|Qwen3 235B — general purpose MoE" - "qwen-32b|proxy|qwen-32b|Qwen3 32B — dense, hybrid thinking" - "qwen-vl-235b|proxy|qwen-vl-235b|Qwen3 VL 235B — vision + language" - "qwen-next-80b|proxy|qwen-next-80b|Qwen3 Next 80B — efficient MoE" + "qwen.qwen3-235b-a22b-2507|proxy|Qwen3 235B — general purpose MoE" + "qwen.qwen3-32b|proxy|Qwen3 32B — dense, hybrid thinking" + "qwen.qwen3-vl-235b-a22b-instruct|proxy|Qwen3 VL 235B — vision + language" + "qwen.qwen3-next-80b-a3b-instruct|proxy|Qwen3 Next 80B — efficient MoE" # ── DeepSeek (via Bedrock) ───────────────────────────────────── - "deepseek-v3|proxy|deepseek-v3|DeepSeek V3.2 — coding + reasoning MoE" - "deepseek-v3.1|proxy|deepseek-v3.1|DeepSeek V3.1 — previous gen" + "deepseek.v3.2|proxy|DeepSeek V3.2 — coding + reasoning MoE" + "deepseek.v3.1|proxy|DeepSeek V3.1 — previous gen" # ── Mistral AI (via Bedrock) ─────────────────────────────────── - "devstral-123b|proxy|devstral-123b|Devstral 2 123B — coding specialist" - "mistral-large-3|proxy|mistral-large-3|Mistral Large 3 675B — flagship MoE" - "magistral-small|proxy|magistral-small|Magistral Small — reasoning model" - "ministral-14b|proxy|ministral-14b|Ministral 14B — mid-size efficient" - "ministral-8b|proxy|ministral-8b|Ministral 8B — fast, lightweight" - "ministral-3b|proxy|ministral-3b|Ministral 3B — tiny, fastest" - "voxtral-small-24b|proxy|voxtral-small-24b|Voxtral Small 24B — multimodal" - "voxtral-mini-3b|proxy|voxtral-mini-3b|Voxtral Mini 3B — tiny multimodal" + "mistral.devstral-2-123b|proxy|Devstral 2 123B — coding specialist" + "mistral.mistral-large-3-675b-instruct|proxy|Mistral Large 3 675B — flagship MoE" + "mistral.magistral-small-2509|proxy|Magistral Small — reasoning model" + "mistral.ministral-3-14b-instruct|proxy|Ministral 14B — mid-size efficient" + "mistral.ministral-3-8b-instruct|proxy|Ministral 8B — fast, lightweight" + "mistral.ministral-3-3b-instruct|proxy|Ministral 3B — tiny, fastest" + "mistral.voxtral-small-24b-2507|proxy|Voxtral Small 24B — multimodal" + "mistral.voxtral-mini-3b-2507|proxy|Voxtral Mini 3B — tiny multimodal" # ── Moonshot AI / Kimi (via Bedrock) ─────────────────────────── - "kimi-k2.5|proxy|kimi-k2.5|Kimi K2.5 — coding + reasoning" - "kimi-k2-thinking|proxy|kimi-k2-thinking|Kimi K2 Thinking — chain-of-thought" + "moonshotai.kimi-k2.5|proxy|Kimi K2.5 — coding + reasoning" + "moonshotai.kimi-k2-thinking|proxy|Kimi K2 Thinking — chain-of-thought" # ── MiniMax (via Bedrock) ────────────────────────────────────── - "minimax-m2|proxy|minimax-m2|MiniMax M2 — general purpose" - "minimax-m2.1|proxy|minimax-m2.1|MiniMax M2.1 — improved general" - "minimax-m2.5|proxy|minimax-m2.5|MiniMax M2.5 — latest, 80.2% SWE-bench" + "minimax.minimax-m2|proxy|MiniMax M2 — general purpose" + "minimax.minimax-m2.1|proxy|MiniMax M2.1 — improved general" + "minimax.minimax-m2.5|proxy|MiniMax M2.5 — latest as of June 5 2026, 80.2% SWE-bench (vendor claimed)" # ── NVIDIA Nemotron (via Bedrock) ────────────────────────────── - "nemotron-super-120b|proxy|nemotron-super-120b|Nemotron Super 120B — large reasoning" - "nemotron-nano-30b|proxy|nemotron-nano-30b|Nemotron Nano 30B — mid-size" - "nemotron-nano-12b|proxy|nemotron-nano-12b|Nemotron Nano 12B — compact" - "nemotron-nano-9b|proxy|nemotron-nano-9b|Nemotron Nano 9B — smallest" + "nvidia.nemotron-super-3-120b|proxy|Nemotron Super 120B — large reasoning" + "nvidia.nemotron-nano-3-30b|proxy|Nemotron Nano 30B — mid-size" + "nvidia.nemotron-nano-12b-v2|proxy|Nemotron Nano 12B — compact" + "nvidia.nemotron-nano-9b-v2|proxy|Nemotron Nano 9B — smallest" # ── OpenAI GPT OSS (via Bedrock) ────────────────────────────── - "gpt-oss-120b|proxy|gpt-oss-120b|GPT OSS 120B — open-source GPT" - "gpt-oss-20b|proxy|gpt-oss-20b|GPT OSS 20B — compact open-source GPT" - "gpt-oss-safeguard-120b|proxy|gpt-oss-safeguard-120b|GPT OSS Safeguard 120B" - "gpt-oss-safeguard-20b|proxy|gpt-oss-safeguard-20b|GPT OSS Safeguard 20B" + "openai.gpt-oss-120b|proxy|GPT OSS 120B — open-source GPT" + "openai.gpt-oss-20b|proxy|GPT OSS 20B — compact open-source GPT" + "openai.gpt-oss-safeguard-120b|proxy|GPT OSS Safeguard 120B" + "openai.gpt-oss-safeguard-20b|proxy|GPT OSS Safeguard 20B" # ── Z.AI / GLM (via Bedrock) ────────────────────────────────── - "glm-5|proxy|glm-5|GLM 5 — latest general model" - "glm-4.7|proxy|glm-4.7|GLM 4.7 — strong reasoning" - "glm-4.7-flash|proxy|glm-4.7-flash|GLM 4.7 Flash — fast inference" - "glm-4.6|proxy|glm-4.6|GLM 4.6 — previous gen" + "zai.glm-5|proxy|GLM 5 — latest general model as of June 5 2026" + "zai.glm-4.7|proxy|GLM 4.7 — strong reasoning" + "zai.glm-4.7-flash|proxy|GLM 4.7 Flash — fast inference" + "zai.glm-4.6|proxy|GLM 4.6 — previous gen" # ── Google Gemma (via Bedrock) ───────────────────────────────── - "gemma-3-27b|proxy|gemma-3-27b|Gemma 3 27B — open model, largest" - "gemma-3-12b|proxy|gemma-3-12b|Gemma 3 12B — open model, mid-size" - "gemma-3-4b|proxy|gemma-3-4b|Gemma 3 4B — open model, compact" + "google.gemma-3-27b-it|proxy|Gemma 3 27B — open model, largest" + "google.gemma-3-12b-it|proxy|Gemma 3 12B — open model, mid-size" + "google.gemma-3-4b-it|proxy|Gemma 3 4B — open model, compact" # ── Writer / Palmyra (via Bedrock) ───────────────────────────── - "palmyra-vision-7b|proxy|palmyra-vision-7b|Palmyra Vision 7B — vision model" - - # ── Self-hosted via Ollama (proxy required, SSH tunnel must be active) ── - # Uncomment after starting tunnel: ./scripts/tunnel.sh start - # "qwen-local|proxy|qwen-local|Qwen 3.5 35B — self-hosted on GPU server" + "writer.palmyra-vision-7b|proxy|Palmyra Vision 7B — vision model" ) # ── Functions ───────────────────────────────────────────────────── @@ -109,29 +109,28 @@ list_models() { echo "Available Models for Claude Code + Bedrock" echo "===========================================" echo "" - echo "Backend: Amazon Bedrock (Chat Completions API) — all proxy models support tools + streaming" + echo "Backend: Amazon Bedrock (Chat Completions API for proxy models, Messages API for native)" echo "" - printf " %-24s %-8s %s\n" "ALIAS" "TYPE" "DESCRIPTION" - printf " %-24s %-8s %s\n" "-----" "----" "-----------" + printf " %-46s %-8s %s\n" "MODEL ID" "TYPE" "DESCRIPTION" + printf " %-46s %-8s %s\n" "--------" "----" "-----------" - local current_section="" for entry in "${MODELS[@]}"; do - IFS='|' read -r alias type model_id desc <<< "$entry" - printf " %-24s %-8s %s\n" "$alias" "$type" "$desc" + IFS='|' read -r model_id type desc <<< "$entry" + printf " %-46s %-8s %s\n" "$model_id" "$type" "$desc" done echo "" echo "native = direct Bedrock (no proxy needed, Anthropic models only)" echo "proxy = via LiteLLM proxy -> Amazon Bedrock (start with: ./scripts/setup-proxy.sh)" echo "" - echo "Total: ${#MODELS[@]} models (5 native + $((${#MODELS[@]} - 5)) via Bedrock)" + echo "Total: ${#MODELS[@]} models (7 native + $((${#MODELS[@]} - 7)) via Bedrock)" } lookup_model() { local search="$1" for entry in "${MODELS[@]}"; do - IFS='|' read -r alias type model_id desc <<< "$entry" - if [[ "$alias" == "$search" ]]; then - echo "$alias|$type|$model_id|$desc" + IFS='|' read -r model_id type desc <<< "$entry" + if [[ "$model_id" == "$search" ]]; then + echo "$model_id|$type|$desc" return 0 fi done @@ -144,8 +143,8 @@ pick_model_interactive() { echo "" >&2 local i=1 for entry in "${MODELS[@]}"; do - IFS='|' read -r alias type model_id desc <<< "$entry" - printf " %2d) %-24s [%s] %s\n" "$i" "$alias" "$type" "$desc" >&2 + IFS='|' read -r model_id type desc <<< "$entry" + printf " %2d) %-46s [%s] %s\n" "$i" "$model_id" "$type" "$desc" >&2 ((i++)) done echo "" >&2 @@ -169,16 +168,16 @@ check_proxy() { # ── Parse args ──────────────────────────────────────────────────── -MODEL_ALIAS="" +MODEL_ID_ARG="" CLAUDE_ARGS=() while [[ $# -gt 0 ]]; do case $1 in - --model|-m) MODEL_ALIAS="$2"; shift 2 ;; + --model|-m) MODEL_ID_ARG="$2"; shift 2 ;; --list|-l) list_models; exit 0 ;; -h|--help) - echo "Usage: $0 [--model ALIAS] [--list] [claude args...]" - echo " $0 --model qwen-coder-next -p 'write a function'" + echo "Usage: $0 [--model MODEL_ID] [--list] [claude args...]" + echo " $0 --model qwen.qwen3-coder-next -p 'write a function'" echo " $0 --list" exit 0 ;; @@ -187,19 +186,19 @@ while [[ $# -gt 0 ]]; do done # Interactive selection if no model specified -if [[ -z "$MODEL_ALIAS" ]]; then +if [[ -z "$MODEL_ID_ARG" ]]; then SELECTED=$(pick_model_interactive) else - SELECTED=$(lookup_model "$MODEL_ALIAS") || { - echo "[error] Unknown model: $MODEL_ALIAS" + SELECTED=$(lookup_model "$MODEL_ID_ARG") || { + echo "[error] Unknown model: $MODEL_ID_ARG" echo " Run: $0 --list" exit 1 } fi -IFS='|' read -r ALIAS TYPE MODEL_ID DESC <<< "$SELECTED" +IFS='|' read -r MODEL_ID TYPE DESC <<< "$SELECTED" echo "" -echo "[model] $ALIAS — $DESC" +echo "[model] $MODEL_ID — $DESC" # ── Launch Claude Code ──────────────────────────────────────────── diff --git a/bedrock/scripts/setup-proxy.sh b/bedrock/scripts/setup-proxy.sh index 2a0f30e..81279cb 100755 --- a/bedrock/scripts/setup-proxy.sh +++ b/bedrock/scripts/setup-proxy.sh @@ -27,15 +27,19 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" CONFIG_FILE="$PROJECT_DIR/config/litellm-config.yaml" +DEFAULT_HOST="127.0.0.1" DEFAULT_PORT=4000 PID_FILE="$PROJECT_DIR/.litellm.pid" TOKEN_FILE="$PROJECT_DIR/.mantle-token" MANTLE_REGION="us-east-1" usage() { - echo "Usage: $0 [--port PORT] [--stop] [--status] [--refresh]" + echo "Usage: $0 [--host HOST] [--port PORT] [--stop] [--status] [--refresh]" echo "" echo "Options:" + echo " --host HOST Address to bind on (default: $DEFAULT_HOST). Use a" + echo " non-loopback address (e.g. a VPC private IP) only if" + echo " you need clients on other hosts to reach the proxy." echo " --port PORT Port to run proxy on (default: $DEFAULT_PORT)" echo " --stop Stop running proxy" echo " --status Check if proxy is running" @@ -43,11 +47,13 @@ usage() { exit 1 } +HOST=$DEFAULT_HOST PORT=$DEFAULT_PORT ACTION="start" while [[ $# -gt 0 ]]; do case $1 in + --host) HOST="$2"; shift 2 ;; --port) PORT="$2"; shift 2 ;; --stop) ACTION="stop"; shift ;; --status) ACTION="status"; shift ;; @@ -158,7 +164,14 @@ start_proxy() { # Start in background with Bedrock token + routing fix exported export MANTLE_API_KEY export LITELLM_USE_CHAT_COMPLETIONS_URL_FOR_ANTHROPIC_MESSAGES=true - nohup litellm --config "$CONFIG_FILE" --port "$PORT" > "$PROJECT_DIR/.litellm.log" 2>&1 & + # Default bind is 127.0.0.1; only override via --host if you have a real + # need to expose the proxy beyond the local machine. + if [[ "$HOST" != "127.0.0.1" && "$HOST" != "localhost" ]]; then + echo "[warn] Binding to $HOST — the proxy will be reachable from any host that" + echo "[warn] can reach $HOST:$PORT at the network layer. The proxy itself does" + echo "[warn] NOT authenticate clients; rely on your security group / firewall." + fi + nohup litellm --config "$CONFIG_FILE" --host "$HOST" --port "$PORT" > "$PROJECT_DIR/.litellm.log" 2>&1 & echo $! > "$PID_FILE" # Wait for proxy to be ready @@ -166,7 +179,7 @@ start_proxy() { for i in $(seq 1 15); do if curl -sf "http://localhost:${PORT}/health" &>/dev/null; then echo "" - echo "[ready] Proxy running on http://localhost:${PORT}" + echo "[ready] Proxy running on http://${HOST}:${PORT}" echo "[pid] $(cat "$PID_FILE")" echo "[log] $PROJECT_DIR/.litellm.log" echo "" diff --git a/bedrock/uv.lock b/bedrock/uv.lock new file mode 100644 index 0000000..9596322 --- /dev/null +++ b/bedrock/uv.lock @@ -0,0 +1,605 @@ +version = 1 +revision = 3 +requires-python = ">=3.11" +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version < '3.15'", +] + +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + +[[package]] +name = "ast-serialize" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/9d/09e27731bd5864a9ce04e3244074e674bb8936bf62b45e0357248717adac/ast_serialize-0.5.0.tar.gz", hash = "sha256:5880091bfe6f4f986f22866375c2e884843e7a0b6343ae41aeea659613d879b6", size = 61157, upload-time = "2026-05-17T17:48:29.429Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/9a/13dde51ba9e15f8b97957ab7cb0120d0e381524d651c6bd630b9c359227f/ast_serialize-0.5.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8f5c14f169eb0972c0c21bada5358b23d6047c76583b005234f865b11f1fa00a", size = 1183520, upload-time = "2026-05-17T17:47:30.831Z" }, + { url = "https://files.pythonhosted.org/packages/37/de/5a7f0a9fe68944f536632a5af84676739c7d2582be42deb082634bf3a754/ast_serialize-0.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7d1a2de9de5be04652f0ed60738356ef94f66db37924a9499fffe98dc491aa0b", size = 1175779, upload-time = "2026-05-17T17:47:32.551Z" }, + { url = "https://files.pythonhosted.org/packages/9c/81/0bb853e76e4f6e9a1855d569003c59e19ffac45f7079d91505d1bb212f92/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be5173fb66f9b49026d9d5a2ff0fc7c7009077107c0eb285b2d60fdf1fe10bd1", size = 1233750, upload-time = "2026-05-17T17:47:34.731Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d3/4cf705beeccc08754d0bbda99aefff26110e209b9a07ac8a6b60eec48531/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8015cd071ac1339924ee2b8098c93e00e155f30a16f40ec9816fcf84f4753f6", size = 1235942, upload-time = "2026-05-17T17:47:36.287Z" }, + { url = "https://files.pythonhosted.org/packages/26/c8/ee097e437ea27dd2b8b227865c875492b585650a5802a22d82b304c8201b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5499e8797edff2a9186aa313ed382c6b422e798e9332d9953badcee6e69a88f2", size = 1442517, upload-time = "2026-05-17T17:47:38.17Z" }, + { url = "https://files.pythonhosted.org/packages/ff/bd/68063442838f1ba68ec72b5436430bc75b3bb17a1a3c3063f09b0c05ae2b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6848f2a093fb5548751a9a09bff8fcd229e2bbeb0e3331f391b6ae6d26cd9903", size = 1254081, upload-time = "2026-05-17T17:47:39.826Z" }, + { url = "https://files.pythonhosted.org/packages/50/e2/1e520793bc6a4e4524a6ab022391e827825eaa0c3811828bfdc6852eca26/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:832d4c998e0b091fd60a6d6bceee535483c4d490de9ba85003af835225719261", size = 1259910, upload-time = "2026-05-17T17:47:41.369Z" }, + { url = "https://files.pythonhosted.org/packages/4e/e1/49b60f467979979cfe6913b43948ff25bca971ad0591d181812f163a988e/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:16db7c62ec0b8efe1d7afd283a388d8f74f2605d56032e5a37747d2de8dba027", size = 1250678, upload-time = "2026-05-17T17:47:43.702Z" }, + { url = "https://files.pythonhosted.org/packages/74/ba/66ab9555de6275677566f6574e5ef6c29cb185ea866f643bc06f8280a8ee/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:baf5eb061eb5bccade4128ad42da33787d72f6013809cd1b590376ece8b3c937", size = 1301603, upload-time = "2026-05-17T17:47:46.256Z" }, + { url = "https://files.pythonhosted.org/packages/66/42/6aca9b9abc710014b2be9059689e5dd1679339e78f567ffb4d255a9e2050/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:104e4a35bd7c124173c41760ef9aaea17ddb3f86c65cb643671d59afbe3ee94c", size = 1410332, upload-time = "2026-05-17T17:47:47.899Z" }, + { url = "https://files.pythonhosted.org/packages/47/68/2f76594432a22581ecf878b5e75a9b8601c24b2241cf0bbeb1e21fcf370c/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:36be371028fc1675acb38a331bde160dbab7ff907fdf00b67eb6911aa106951b", size = 1509979, upload-time = "2026-05-17T17:47:50.942Z" }, + { url = "https://files.pythonhosted.org/packages/40/ac/a93c9b58292653f6c595752f677a08e608f903b710594909e9231a389b3b/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:061ee58bdb52341c8201a6df41182a977736bae3b7ded87ca7176ca25a8a47ab", size = 1505002, upload-time = "2026-05-17T17:47:54.093Z" }, + { url = "https://files.pythonhosted.org/packages/14/2e/b278f68c497ee2f1d1576cbbef8db5281cd4a5f2db040537592ac9c8862e/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b15219e9cdc9f53f6f4cb51c009203507228226148c05c5e8fe451c28b435eb3", size = 1456231, upload-time = "2026-05-17T17:47:56.311Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/419be1c566a4c504cd8fd60ce2f84e790f295495c0f327cfaeadf3d51012/ast_serialize-0.5.0-cp314-cp314t-win32.whl", hash = "sha256:842d1c004bb466c7df036f95fabef789570541922b10976b12f5592a69cf0b38", size = 1058668, upload-time = "2026-05-17T17:47:58.305Z" }, + { url = "https://files.pythonhosted.org/packages/03/6f/c9d4d549295ed05111aeb8853232d1afd9d0a179fddb01eeffbb3a4a6842/ast_serialize-0.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b0c06d760909b095cc466356dfccd05a1c7233a6ca191c020dca2c6a6f16c24c", size = 1101075, upload-time = "2026-05-17T17:48:00.35Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8e/d00c5ab30c58222e07d62956fca86c59d91b9ad32997e633c38b526623a3/ast_serialize-0.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:787baedb0262cc49e8ce37cc15c00ae818e46a165a3b36f5e21ed174998104cb", size = 1075347, upload-time = "2026-05-17T17:48:01.753Z" }, + { url = "https://files.pythonhosted.org/packages/e0/9e/dc2530acb3a60dc6e46d65abf27d1d9f86721694757906a148d90a6860de/ast_serialize-0.5.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:0668aa9459cfa8c9c49ddd2163ebcf43088ba045ef7492af6fe22e0098303101", size = 1191380, upload-time = "2026-05-17T17:48:03.738Z" }, + { url = "https://files.pythonhosted.org/packages/26/0a/bd3d18a582f273d6c843d16bb9e22e9e16365ff7991e92f18f798e9f1224/ast_serialize-0.5.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:bf683d6363edf2b39eed6b6d4fe22d34b6203867a67e27134d9e2a2680c4bc4a", size = 1183879, upload-time = "2026-05-17T17:48:05.463Z" }, + { url = "https://files.pythonhosted.org/packages/40/ae/1f919100f8620887af58fcc381c61a1f218cdf89c6e155f87b213e61010a/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc22cf0c9be65e71cf88fda130af60d61eb4a79370ad4cfe7900d48a4aa2211", size = 1244529, upload-time = "2026-05-17T17:48:07.008Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ca/6376559dcce707cdbc1d0d9a13c8d3baaaa501e949ce0ebdc4230cd881aa/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f66173891548c9f2726bf27957b41cabce12fa679dc6da505ddbde4d4b3b31cf", size = 1240560, upload-time = "2026-05-17T17:48:08.46Z" }, + { url = "https://files.pythonhosted.org/packages/35/b2/a620e206b5aeb7efbf2710336df57d457cffbb3991076bbcc1147ef9abd4/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e42d729ef2be96a14efbad355093284739e3670ece3e534f82cc8832790911d9", size = 1451172, upload-time = "2026-05-17T17:48:09.922Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e0/4ad5c04c24a40481b2935ce9a0ccdb6023dc8b667167d06ae530cc3512f2/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b725026bafa801dbd7310eb13a75f0a2e370e7e51b2cb225f9d21fcfadf919ee", size = 1265072, upload-time = "2026-05-17T17:48:11.469Z" }, + { url = "https://files.pythonhosted.org/packages/b2/71/4d1d479aa56d0101c40e17720c3d6ac2af7269ea0487a80b18e7bfd1a5b7/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b54f60c1d78767a53b67eaa663f0dfac3afe606aa07f1301572f588b73d64809", size = 1270488, upload-time = "2026-05-17T17:48:13.575Z" }, + { url = "https://files.pythonhosted.org/packages/6d/4f/0de1bbe06f6edef9fde4ed12ca8e7b3ec7e6e2bd4e672c5af487f7957665/ast_serialize-0.5.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:27d51654fc240a1e87e742d353d98eb45b75f62f129086b3596ab53df2ac2a43", size = 1260702, upload-time = "2026-05-17T17:48:15.141Z" }, + { url = "https://files.pythonhosted.org/packages/75/61/e00872439cfdddcc3c1b6cdaa6e5d904ba8e26a18807c67c4e14409d0ca8/ast_serialize-0.5.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c36237c46dd1674542f2109740ea5ea485a169bf1431939ada0434e17934", size = 1311182, upload-time = "2026-05-17T17:48:16.779Z" }, + { url = "https://files.pythonhosted.org/packages/76/8e/699a5b955f7926956c95e9e1d74132acad73c2fe7a426f94da89123c20aa/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1943db345233cc7194a470f13afa9c59772c0b123dea0c9414c4d4ca54369759", size = 1421410, upload-time = "2026-05-17T17:48:18.527Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ae/d5b7626874478997adc7a29ab28accf21e596fb590c944290401dfd0b29e/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df1c00022cbbcb064bfaa505aa9c9295362443ce5dacb459d1331d3da353f887", size = 1516587, upload-time = "2026-05-17T17:48:20.133Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ce/b59e02a82d9c4244d64cde502e0b00e83e38816abe19155ceb5437402c7f/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:cae65289fc456fde04af979a2be09302ef5d8ab92ef23e596d6746dc267ada27", size = 1515171, upload-time = "2026-05-17T17:48:21.921Z" }, + { url = "https://files.pythonhosted.org/packages/8b/38/d8d90042747d05aa08d4efcf1c99035a5f670a6bf4c214d31644392afbca/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:239a4c354e8d676e9d94631d1d4a64edc6b266f86ff3a5a80aedd344f342c01d", size = 1464668, upload-time = "2026-05-17T17:48:23.544Z" }, + { url = "https://files.pythonhosted.org/packages/dd/51/5b840c4df7334104cecffa28f23904fe81ca89ca223d2450e288de39fd3c/ast_serialize-0.5.0-cp39-abi3-win32.whl", hash = "sha256:143a4ef63285a075871908fda3672dc21864b83a8ec3ee12304aa3e4c5387b9a", size = 1068311, upload-time = "2026-05-17T17:48:25.027Z" }, + { url = "https://files.pythonhosted.org/packages/41/11/ca5672c7d491825bc4cd6702dea106a6b60d928707712ec257c7833ae476/ast_serialize-0.5.0-cp39-abi3-win_amd64.whl", hash = "sha256:cf25572c526add400f26a4750dc6ce0c3bb93fc1f75e7ae0cad4ce4f2cd5c590", size = 1108931, upload-time = "2026-05-17T17:48:26.591Z" }, + { url = "https://files.pythonhosted.org/packages/45/19/cc8bd127d28a43da249aa955cfd164cf8fd534e79e42cea96c4854d72fd0/ast_serialize-0.5.0-cp39-abi3-win_arm64.whl", hash = "sha256:92a31c9c20d25a076edaeec76b128a3535d74a24f340b9a8a7e96c9b86dc9642", size = 1081181, upload-time = "2026-05-17T17:48:28.122Z" }, +] + +[[package]] +name = "certifi" +version = "2026.5.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705, upload-time = "2026-04-02T09:26:02.191Z" }, + { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419, upload-time = "2026-04-02T09:26:03.583Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901, upload-time = "2026-04-02T09:26:04.738Z" }, + { url = "https://files.pythonhosted.org/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742, upload-time = "2026-04-02T09:26:06.36Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061, upload-time = "2026-04-02T09:26:08.347Z" }, + { url = "https://files.pythonhosted.org/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239, upload-time = "2026-04-02T09:26:09.823Z" }, + { url = "https://files.pythonhosted.org/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173, upload-time = "2026-04-02T09:26:10.953Z" }, + { url = "https://files.pythonhosted.org/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841, upload-time = "2026-04-02T09:26:12.142Z" }, + { url = "https://files.pythonhosted.org/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304, upload-time = "2026-04-02T09:26:13.711Z" }, + { url = "https://files.pythonhosted.org/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455, upload-time = "2026-04-02T09:26:14.941Z" }, + { url = "https://files.pythonhosted.org/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036, upload-time = "2026-04-02T09:26:16.478Z" }, + { url = "https://files.pythonhosted.org/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739, upload-time = "2026-04-02T09:26:17.751Z" }, + { url = "https://files.pythonhosted.org/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277, upload-time = "2026-04-02T09:26:18.981Z" }, + { url = "https://files.pythonhosted.org/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819, upload-time = "2026-04-02T09:26:20.295Z" }, + { url = "https://files.pythonhosted.org/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281, upload-time = "2026-04-02T09:26:21.74Z" }, + { url = "https://files.pythonhosted.org/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843, upload-time = "2026-04-02T09:26:22.901Z" }, + { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, + { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, + { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, + { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, +] + +[[package]] +name = "click" +version = "8.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96", size = 353007, upload-time = "2026-05-22T04:08:37.769Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", size = 116639, upload-time = "2026-05-22T04:08:35.26Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "idna" +version = "3.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "librt" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/40/08/9e7f6b5d2b5bed6ad055cdd5925f192bb403a51280f86b56554d9d0699a2/librt-0.11.0.tar.gz", hash = "sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1", size = 200139, upload-time = "2026-05-10T18:17:25.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/87/2bf31fe17587b29e3f93ec31421e2b1e1c3e349b8bf6c7c313dbad1d5340/librt-0.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:93d95bd45b7d58343d8b90d904450a545144eec19a002511163426f8ab1fae29", size = 141092, upload-time = "2026-05-10T18:15:34.795Z" }, + { url = "https://files.pythonhosted.org/packages/cf/08/5c5bf772920b7ebac6e32bc91a643e0ab3870199c0b542356d3baa83970a/librt-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ee278c769a713638cdacd4c0436d72156e75df3ebc0166ab2b9dc43acc386c9", size = 142035, upload-time = "2026-05-10T18:15:36.242Z" }, + { url = "https://files.pythonhosted.org/packages/06/20/662a03d254e5b000d838e8b345d83303ddb768c080fd488e40634c0fa66b/librt-0.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f230cb1cbc9faaa616f9a678f530ebcf186e414b6bcbd88b960e4ba1b92428d5", size = 475022, upload-time = "2026-05-10T18:15:37.56Z" }, + { url = "https://files.pythonhosted.org/packages/de/f3/aa81523e45184c6ec23dc7f63263362ec55f80a09d424c012359ecbe7e35/librt-0.11.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5d63c855d86938d9de93e265c9bd8c705b51ec494de5738340ee93767a686e4b", size = 467273, upload-time = "2026-05-10T18:15:39.182Z" }, + { url = "https://files.pythonhosted.org/packages/6b/6f/59c74b560ca8853834d5501d589c8a2519f4184f273a085ffd0f37a1cc47/librt-0.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993f028be9e96a08d31df3479ac80d99be374d17f3b78e4796b3fd3c913d4e89", size = 497083, upload-time = "2026-05-10T18:15:40.634Z" }, + { url = "https://files.pythonhosted.org/packages/fe/7b/5aa4d2c9600a719401160bf7055417df0b2a47439b9d88286ce45e56b65f/librt-0.11.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:258d73a0aa66a055e65b2e4d1b8cdb23b9d132c5bb915d9547d804fcaed116cc", size = 489139, upload-time = "2026-05-10T18:15:41.934Z" }, + { url = "https://files.pythonhosted.org/packages/d6/31/9143803d7da6856a69153785768c4936864430eec0fd9461c3ea527d9922/librt-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0827efe7854718f04aaddf6496e96960a956e676fe1d0f04eb41511fd8ad06d5", size = 508442, upload-time = "2026-05-10T18:15:43.206Z" }, + { url = "https://files.pythonhosted.org/packages/2f/5a/bce08184488426bda4ccc2c4964ac048c8f68ae89bd7120082eef4233cfd/librt-0.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7753e57d6e12d019c0d8786f1c09c709f4c3fcc57c3887b24e36e6c06ec938b7", size = 514230, upload-time = "2026-05-10T18:15:44.761Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/bb5e213d254b7505a0e658da199d8ab719086632ce09eef311ab27976523/librt-0.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:11bd19822431cc21af9f27374e7ae2e58103c7d98bda823536a6c47f6bb2bb3d", size = 494231, upload-time = "2026-05-10T18:15:46.308Z" }, + { url = "https://files.pythonhosted.org/packages/9d/fb/541cdad5b1ab1300398c74c4c9a497b88e5074c21b1244c8f49731d3a284/librt-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:22bdf239b219d3993761a148ffa134b19e52e9989c84f845d5d7b71d70a17412", size = 537585, upload-time = "2026-05-10T18:15:47.629Z" }, + { url = "https://files.pythonhosted.org/packages/8f/f2/464bb69295c320cb06bddb4f14a4ec67934ee14b2bffb12b19fb7ab287ba/librt-0.11.0-cp311-cp311-win32.whl", hash = "sha256:46c60b61e308eb535fbd6fa622b1ee1bb2815691c1ad9c98bf7b84952ec3bc8d", size = 100509, upload-time = "2026-05-10T18:15:49.157Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e7/a17ee1788f9e4fbf548c19f4afa07c92089b9e24fef6cb2410863781ef4c/librt-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:902e546ff044f579ff1c953ff5fce97b636fe9e3943996b2177710c6ef076f73", size = 118628, upload-time = "2026-05-10T18:15:50.345Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c7/6c766214f9f9903bcfcfbef97d807af8d8f5aa3502d247858ab17582d212/librt-0.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:65ac3bc20f78aa0ee5ae84baa68917f89fef4af63e941084dd019a0d0e749f0c", size = 103122, upload-time = "2026-05-10T18:15:52.068Z" }, + { url = "https://files.pythonhosted.org/packages/8b/d0/07c77e067f0838949b43bd89232c29d72efebb9d2801a9750184eb706b71/librt-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b87504f1690a23b9a2cca841191a04f83895d4fc2dd04df91d82b1a04ca2ad46", size = 144147, upload-time = "2026-05-10T18:15:53.227Z" }, + { url = "https://files.pythonhosted.org/packages/7a/24/8493538fa4f62f982686398a5b8f68008138a75086abdea19ade64bf4255/librt-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40071fc5fe0ce8daa6de616702314a01e1250711682b0523d6ab8d4525910cb3", size = 143614, upload-time = "2026-05-10T18:15:54.657Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1e/f8bad050810d9171f34a1648ed910e56814c2ba61639f2bd53c6377ae24b/librt-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:137e79445c896a0ea7b265f52d23954e05b64222ee1af69e2cb34219067cbb67", size = 485538, upload-time = "2026-05-10T18:15:56.117Z" }, + { url = "https://files.pythonhosted.org/packages/c0/fe/3594ebfbaf03084ba4b120c9ba5c3183fd938a48725e9bbe6ff0a5159ad8/librt-0.11.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:cca6644054e78746d8d4ef238681f9c34ff8b584fe6b988ecebb8db3b15e622a", size = 479623, upload-time = "2026-05-10T18:15:57.544Z" }, + { url = "https://files.pythonhosted.org/packages/b0/da/5d1876984b3746c85dbd219dbfcb73c85f54ee263fd32e5b2a632ec14571/librt-0.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5b0eea49f5562861ee8d757a32ef7d559c1d35be2aaaa1ec28941d74c9ffc8a", size = 513082, upload-time = "2026-05-10T18:15:58.805Z" }, + { url = "https://files.pythonhosted.org/packages/19/6e/55bdf5d5ca00c3e18430690bf2c953d8d3ffd3c337418173d33dec985dc9/librt-0.11.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0d1029d7e1ae1a7e647ed6fb5df8c4ce2dffefb7a9f5fd1376a4554d96dac09f", size = 508105, upload-time = "2026-05-10T18:16:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/f1f23a7c595ee90ece4d35c851e5d104b1311a887ed1b4ac4c35bbd13da8/librt-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bc3ce6b33c5828d9e80592011a5c584cb2ce86edbc4088405f70da47dc1d1b3b", size = 522268, upload-time = "2026-05-10T18:16:01.708Z" }, + { url = "https://files.pythonhosted.org/packages/b6/02/5720f5697a7f54b78b3aefbe20df3a48cedcff1276618c4aa481177942ed/librt-0.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:936c5995f3514a42111f20099397d8177c79b4d7e70961e396c6f5a0a3566766", size = 527348, upload-time = "2026-05-10T18:16:03.496Z" }, + { url = "https://files.pythonhosted.org/packages/50/db/b4a47c6f91db4ff76348a0b3dd0cc65e090a078b765a810a62ff9434c3d3/librt-0.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9bc0ca6ad9381cbe8e4aa6e5726e4c80c78115a6e9723c599ed1d73e092bc49d", size = 516294, upload-time = "2026-05-10T18:16:05.173Z" }, + { url = "https://files.pythonhosted.org/packages/9e/58/9384b2f4eb1ed1d273d40948a7c5c4b2360213b402ef3be4641c06299f9c/librt-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:070aa8c26c0a74774317a72df8851facc7f0f012a5b406557ac56992d92e1ec8", size = 553608, upload-time = "2026-05-10T18:16:06.839Z" }, + { url = "https://files.pythonhosted.org/packages/21/7b/5aa8848a7c6a9278c79375146da1812e695754ceec5f005e6043461a7315/librt-0.11.0-cp312-cp312-win32.whl", hash = "sha256:6bf14feb84b05ae945277395451998c89c54d0def4070eb5c08de544930b245a", size = 101879, upload-time = "2026-05-10T18:16:08.103Z" }, + { url = "https://files.pythonhosted.org/packages/37/33/8a745436944947575b584231750a41417de1a38cf6a2e9251d1065651c09/librt-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:75672f0bc524ede266287d532d7923dbce94c7514ad07627bac3d0c6d92cc4d9", size = 119831, upload-time = "2026-05-10T18:16:09.174Z" }, + { url = "https://files.pythonhosted.org/packages/59/67/a6739ac96e28b7855808bdb0370e250606104a859750d209e5a0716fe7ab/librt-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f10cf143e4a9bb0f4f5af568a00df94a2d69ef41c2579584454bb0fe5cc642c", size = 103470, upload-time = "2026-05-10T18:16:10.369Z" }, + { url = "https://files.pythonhosted.org/packages/82/61/e59168d4d0bf2bf90f4f0caf7a001bfc60254c3af4586013b04dc3ef517b/librt-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:78dc31f7fdfe9c9d0eb0e8f42d139db230e826415bbcabd9f0e9faaaee909894", size = 144119, upload-time = "2026-05-10T18:16:11.771Z" }, + { url = "https://files.pythonhosted.org/packages/61/fd/caa1d60b12f7dd79ccea23054e06eeaebe266a5f52c40a6b651069200ce5/librt-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fa475675db22290c3158e1d42326d0f5a65f04f44a0e68c3630a25b53560fb9c", size = 143565, upload-time = "2026-05-10T18:16:13.334Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a9/dc744f5c2b4978d48db970be29f22716d3413d28b14ad99740817315cf2c/librt-0.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:621db29691044bdeda22e789e482e1b0f3a985d90e3426c9c6d17606416205ea", size = 485395, upload-time = "2026-05-10T18:16:14.729Z" }, + { url = "https://files.pythonhosted.org/packages/8f/21/7f8e97a1e4dae952a5a95948f6f8507a173bc1e669f54340bba6ca1ca31b/librt-0.11.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:a9010e2ed5b3a9e158c5fd966b3ab7e834bb3d3aacc8f66c91dd4b57a3799230", size = 479383, upload-time = "2026-05-10T18:16:16.321Z" }, + { url = "https://files.pythonhosted.org/packages/a6/6d/d8ee9c114bebf2c50e29ec2aa940826fccb62a645c3e4c18760987d0e16d/librt-0.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c39513d8b7477a2e1ed8c43fc21c524e8d5a0f8d4e8b7b074dbdbe7820a08e2", size = 513010, upload-time = "2026-05-10T18:16:17.647Z" }, + { url = "https://files.pythonhosted.org/packages/f0/43/0b5708af2bd30a46400e72ba6bdaa8f066f15fb9a688527e34220e8d6c06/librt-0.11.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7aef3cf1d5af86e770ab04bfd993dfc4ae8b8c17f66fb77dd4a7d50de7bbb1a3", size = 508433, upload-time = "2026-05-10T18:16:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/4a/50/356187247d09013490481033183b3532b58acf8028bcb34b2b56a375c9b2/librt-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:557183ddc36babe46b27dd60facbd5adb4492181a5be887587d57cda6e092f21", size = 522595, upload-time = "2026-05-10T18:16:20.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/e7/c6ac4240899c7f3248079d5a9900debe0dadb3fdeaf856684c987105ba47/librt-0.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83d3e1f72bd42f6c5c0b7daec530c3f829bd02db42c70b8ddf0c2d90a2459930", size = 527255, upload-time = "2026-05-10T18:16:22.352Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b5/a81322dbeedeeaf9c1ee6f001734d28a09d8383ac9e6779bc24bbd0743c6/librt-0.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4ce1f21fbe589bc1afd7872dece84fb0e1144f794a288e58a10d2c54a55c43be", size = 516847, upload-time = "2026-05-10T18:16:23.627Z" }, + { url = "https://files.pythonhosted.org/packages/ae/66/6e6323787d592b55204a42595ff1102da5115601b53a7e9ddebc889a6da5/librt-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b09f7044ea2b64c9da42fd3d335666518cfd1c6e8a182c95da73d0214b41e", size = 553920, upload-time = "2026-05-10T18:16:25.025Z" }, + { url = "https://files.pythonhosted.org/packages/9c/21/623f8ca230857102066d9ca8c6c1734995908c4d0d1bee7bb2ef0021cb33/librt-0.11.0-cp313-cp313-win32.whl", hash = "sha256:78fddc31cd4d3caa897ad5d31f856b1faadc9474021ad6cb182b9018793e254e", size = 101898, upload-time = "2026-05-10T18:16:26.649Z" }, + { url = "https://files.pythonhosted.org/packages/b3/1d/b4ebd44dd723f768469007515cb92251e0ae286c94c140f374801140fa74/librt-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ca8aa88751a775870b764e93bad5135385f563cb8dcee399abf034ea4d3cb47", size = 119812, upload-time = "2026-05-10T18:16:27.859Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e4/b2f4ca7965ca373b491cdb4bc25cdb30c1649ca81a8782056a83850292a9/librt-0.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:96f044bb325fd9cf1a723015638c219e9143f0dfbc0ca54c565df2b7fc748b44", size = 103448, upload-time = "2026-05-10T18:16:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/29/eb/dbce197da4e227779e56b5735f2decc3eb36e55a1cdbf1bd65d6639d76c1/librt-0.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4a017a95e5837dc15a8c5661d60e05daa96b90908b1aa6b7acdf443cd25c8ebd", size = 143345, upload-time = "2026-05-10T18:16:30.674Z" }, + { url = "https://files.pythonhosted.org/packages/76/a3/254bebd0c11c8ba684018efb8006ff22e466abce445215cca6c778e7d9de/librt-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ecbd9819deccc39b7542bf4d2a740d8a620694d39989e58661d3763458f8d4", size = 143131, upload-time = "2026-05-10T18:16:32.037Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3f/f77d6122d21ac7bf6ae8a7dfced1bd2a7ac545d3273ebdcaf8042f6d619f/librt-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7da327dacd7be8f8ec36547373550744a3cc0e536d54665cd83f8bcd961200e8", size = 477024, upload-time = "2026-05-10T18:16:33.493Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0a/2c996dadebaa7d9bbbd43ef2d4f3e66b6da545f838a41694ef6172cebec8/librt-0.11.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0dc56b1f8d06e60db362cc3fdae206681817f86ce4725d34511473487f12a34b", size = 474221, upload-time = "2026-05-10T18:16:34.864Z" }, + { url = "https://files.pythonhosted.org/packages/0a/7e/f5d92af8486b8272c23b3e686b46ff72d89c8169585eb61eef01a2ac7147/librt-0.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05fb8fb2ab90e21c8d12ea240d744ad514da9baf381ebfa70d91d20d21713175", size = 505174, upload-time = "2026-05-10T18:16:36.705Z" }, + { url = "https://files.pythonhosted.org/packages/af/1a/cb0734fe86398eb33193ab753b7326255c74cac5eb09e76b9b16536e7adb/librt-0.11.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cae74872be221df4374d10fec61f93ed1513b9546ea84f2c0bf73ab3e9bd0b03", size = 497216, upload-time = "2026-05-10T18:16:38.418Z" }, + { url = "https://files.pythonhosted.org/packages/18/06/094820f91558b66e29943c0ec41c9914f460f48dd51fc503c3101e10842d/librt-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32bcc918c0148eb7e3d57385125bac7e5f9e4359d05f07448b09f6f778c2f31c", size = 513921, upload-time = "2026-05-10T18:16:39.848Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c2/00de9018871a282f530cacb457d5ec0428f6ac7e6fedde9aff7468d9fb04/librt-0.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f9743fc99135d5f78d2454435615f6dec0473ca507c26ce9d92b10b562a280d3", size = 520850, upload-time = "2026-05-10T18:16:41.471Z" }, + { url = "https://files.pythonhosted.org/packages/51/9d/64631832348fd1834fb3a61b996434edddaaf25a31d03b0a76273159d2cf/librt-0.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5ba067f4aadae8fda802d91d2124c90c42195ff32d9161d3549e6d05cfe26f96", size = 504237, upload-time = "2026-05-10T18:16:43.15Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ec/ae5525eb16edc827a044e7bb8777a455ff95d4bca9379e7e6bddd7383647/librt-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:de3bf945454d032f9e390b85c4072e0a0570bf825421c8be0e71209fa65e1abe", size = 546261, upload-time = "2026-05-10T18:16:44.408Z" }, + { url = "https://files.pythonhosted.org/packages/5a/09/adce371f27ca039411da9659f7430fcc2ba6cd0c7b3e4467a0f091be7fa9/librt-0.11.0-cp314-cp314-win32.whl", hash = "sha256:d2277a05f6dcb9fd13db9566aac4fabd68c3ea1ea46ee5567d4eef8efa495a2f", size = 96965, upload-time = "2026-05-10T18:16:46.039Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ee/8ac720d98548f173c7ce2e632a7ca94673f74cacd5c8162a84af5b35958a/librt-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:ab73e8db5e3f564d812c1f5c3a175930a5f9bc96ccb5e3b22a34d7858b401cf7", size = 115151, upload-time = "2026-05-10T18:16:47.133Z" }, + { url = "https://files.pythonhosted.org/packages/94/20/c900cf14efeb09b6bef2b2dff20779f73464b97fd58d1c6bccc379588ae3/librt-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aea3caa317752e3a466fa8af45d91ee0ea8c7fdd96e42b0a8dd9b76a7931eba1", size = 98850, upload-time = "2026-05-10T18:16:48.597Z" }, + { url = "https://files.pythonhosted.org/packages/0c/71/944bfe4b64e12abffcd3c15e1cce07f72f3d55655083786285f4dedeb532/librt-0.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d1b36540d7aaf9b9101b3a6f376c8d8e9f7a9aec93ed05918f2c69d493ffef72", size = 151138, upload-time = "2026-05-10T18:16:49.839Z" }, + { url = "https://files.pythonhosted.org/packages/b6/10/99e64a5c86989357fda078c8143c533389585f6473b7439172dd8f3b3b2d/librt-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:efbb343ab2ce3540f4ecbe6315d677ed70f37cd9a72b1e58066c918ca83acbaa", size = 151976, upload-time = "2026-05-10T18:16:51.062Z" }, + { url = "https://files.pythonhosted.org/packages/21/31/5072ad880946d83e5ea4147d6d018c78eefce85b77819b19bdd0ee229435/librt-0.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0dd688aab3f7914d3e6e5e3554978e0383312fb8e771d84be008a35b9ee548", size = 557927, upload-time = "2026-05-10T18:16:52.632Z" }, + { url = "https://files.pythonhosted.org/packages/5e/8d/70b5fb7cfbab60edbe7381614ab985da58e144fbf465c86d44c95f43cdca/librt-0.11.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f5fb36b8c6c63fdcbb1d526d94c0d1331610d43f4118cc1beb4efef4f3faacb2", size = 539698, upload-time = "2026-05-10T18:16:53.934Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a3/ba3495a0b3edbd24a4cae0d1d3c64f39a9fc45d06e812101289b50c1a619/librt-0.11.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a9a237d13addb93715b6fee74023d5ee3469b53fce527626c0e088aa585805f", size = 577162, upload-time = "2026-05-10T18:16:55.589Z" }, + { url = "https://files.pythonhosted.org/packages/f7/db/36e25fb81f99937ff1b96612a1dc9fd66f039cb9cc3aee12c01fac31aab9/librt-0.11.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5ddd17bd87b2c56ddd60e546a7984a2e64c4e8eab92fb4cf3830a48ad5469d51", size = 566494, upload-time = "2026-05-10T18:16:56.975Z" }, + { url = "https://files.pythonhosted.org/packages/33/0d/3f622b47f0b013eeb9cf4cc07ae9bfe378d832a4eec998b2b209fe84244d/librt-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd43992b4473d42f12ff9e68326079f0696d9d4e6000e8f39a0238d482ba6ee2", size = 596858, upload-time = "2026-05-10T18:16:58.374Z" }, + { url = "https://files.pythonhosted.org/packages/a9/02/71b90bc93039c46a2000651f6ad60122b114c8f54c4ad306e0e96f5b75ad/librt-0.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f8e3e8056dd674e279741485e2e512d6e9a751c7455809d0114e6ebf8d781085", size = 590318, upload-time = "2026-05-10T18:16:59.676Z" }, + { url = "https://files.pythonhosted.org/packages/04/04/418cb3f75621e2b761fb1ab0f017f4d70a1a72a6e7c74ee4f7e8d198c2f3/librt-0.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c1f708d8ae9c56cf38a903c44297243d2ec83fd82b396b977e0144a3e76217e3", size = 575115, upload-time = "2026-05-10T18:17:01.007Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2c/5a2183ac58dd911f26b5d7e7d7d8f1d87fcecdddd99d6c12169a258ff62c/librt-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0add982e0e7b9fc14cf4b33789d5f13f66581889b88c2f58099f6ce8f92617bd", size = 617918, upload-time = "2026-05-10T18:17:02.682Z" }, + { url = "https://files.pythonhosted.org/packages/15/1f/dc6771a52592a4451be6effa200cbfc9cec61e4393d3033d81a9d307961d/librt-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:2b481d846ac894c4e8403c5fd0e87c5d11d6499e404b474602508a224ff531c8", size = 103562, upload-time = "2026-05-10T18:17:03.99Z" }, + { url = "https://files.pythonhosted.org/packages/62/4a/7d1415567027286a75ba1093ec4aca11f073e0f559c530cf3e0a757ad55c/librt-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:28edb433edde181112a908c78907af28f964eabc15f4dd16c9d66c834302677c", size = 124327, upload-time = "2026-05-10T18:17:05.465Z" }, + { url = "https://files.pythonhosted.org/packages/ce/62/b40b382fa0c66fee1478073eb8db352a4a6beda4a1adccf1df911d8c289c/librt-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253", size = 102572, upload-time = "2026-05-10T18:17:06.809Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mypy" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ast-serialize" }, + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/15/cca9d88503549ed6fedeaa1d448cdddd542ee8a490232d732e278036fbf2/mypy-2.1.0.tar.gz", hash = "sha256:81e76ad12c2d804512e9b13240d1588316531bfba07558286078bfbce9613633", size = 3898359, upload-time = "2026-05-11T18:37:36.237Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/a1/639f3024794a2a15899cb90707fe02e044c4412794c39c5769fd3df2e2ef/mypy-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a683016b16fe2f572dc04c72be7ee0504ac1605a265d0200f5cea695fb788f41", size = 14691685, upload-time = "2026-05-11T18:33:27.973Z" }, + { url = "https://files.pythonhosted.org/packages/3b/08/9a585dea4325f20d8b80dc78623fa50d1fd2173b710f6237afd6ba6ab39b/mypy-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a293c534adb55271fef24a26da04b855540a8c13cc07bc5917b9fd2c394f2ca", size = 13555165, upload-time = "2026-05-11T18:32:16.107Z" }, + { url = "https://files.pythonhosted.org/packages/81/dc/7c42cc9c6cb01e8eb09961f1f738741d3e9c7e9d5c5b30ec69222625cd5f/mypy-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7406f4d048e71e576f5356d317e5b0a9e666dfd966bd99f9d14ca06e1a341538", size = 13994376, upload-time = "2026-05-11T18:32:39.256Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/285946c33bce716e082c11dfeee9ee196eaf1f5042efb3581a31f9f205e4/mypy-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0210d626fc8b31ccc90233754c7bc90e1f43205e85d96387f7db1285b55c398", size = 14864618, upload-time = "2026-05-11T18:34:49.765Z" }, + { url = "https://files.pythonhosted.org/packages/2b/83/82397f48af6c27e295d57979ded8490c9829040152cf7571b2f026aeb9a0/mypy-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3712c20deed54e814eaaa825603bada8ea1c390670a397c95b98405347acc563", size = 15102063, upload-time = "2026-05-11T18:34:05.855Z" }, + { url = "https://files.pythonhosted.org/packages/40/68/b02dec39057b88eb03dc0aa854732e26e8361f34f9d0e20c7614967d1eba/mypy-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fcaa0e479066e31f7cceb6a3bea39cb22b2ff51a6b2f24f193d19179ba17c389", size = 11060564, upload-time = "2026-05-11T18:35:36.494Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a8/ea3dcbef31f99b634f2ee23bb0321cbc8c1b388b76a861eb849f13c347dc/mypy-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:0b1a5260c95aa443083f9ed3592662941951bca3d4ca224a5dc517c38b7cf666", size = 9966983, upload-time = "2026-05-11T18:37:14.139Z" }, + { url = "https://files.pythonhosted.org/packages/95/b1/55861beb5c339b44f9a2ba92df9e2cb1eeb4ae1eee674cdf7772c797778b/mypy-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:244358bf1c0da7722230bce60683d52e8e9fd030554926f15b747a84efb5b3af", size = 14874381, upload-time = "2026-05-11T18:37:31.784Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b3/b7f770114b7d0ac92d0f76e8d93c2780844a70488a90e91821927850da86/mypy-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ec7c57657493c7a75534df2751c8ae2cda383c16ecc55d2106c54476b1b16f6", size = 13665501, upload-time = "2026-05-11T18:34:23.063Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f3/8ae2037967e2126689a0c11d99e2b707134a565191e92c60ca2572aec60a/mypy-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8161b6ff4392410023224f0969d17db93e1e154bc3e4ba62598e720723ae211", size = 14045750, upload-time = "2026-05-11T18:31:48.151Z" }, + { url = "https://files.pythonhosted.org/packages/a0/32/615eb5911859e43d054941b0d0a7d06cfa2870eba86529cf385b052b111c/mypy-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf03e12003084a67395184d3eb8cbd6a489dc3655b5664b28c210a9e2403ab0b", size = 15061630, upload-time = "2026-05-11T18:37:06.898Z" }, + { url = "https://files.pythonhosted.org/packages/d4/03/4eafbfff8bfab1b87082741eae6e6a624028c984e6708b73bce2a8570c9d/mypy-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:20509760fd791c51579d573153407d226385ec1f8bcce55d730b354f3336bc22", size = 15288831, upload-time = "2026-05-11T18:31:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/919661478e5891a3c96e549c036e467e64563ab85995b10c53c8358e16a3/mypy-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:6753d0c1fdd6b1a23b9e4f283ce80b2153b724adcb2653b20b85a8a28ac6436b", size = 11135228, upload-time = "2026-05-11T18:34:31.23Z" }, + { url = "https://files.pythonhosted.org/packages/24/0a/6a12b9782ca0831a553192f351679f4548abc9d19a7cc93bb7feb02084c7/mypy-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:98ebb6589bb3b6d0c6f0c459d53ca55b8091fbc13d277c4041c885392e8195e8", size = 10040684, upload-time = "2026-05-11T18:36:48.199Z" }, + { url = "https://files.pythonhosted.org/packages/6e/dd/c7191469c777f07689c032a8f7326e393ea34c92d6d76eb7ce5ba57ea66d/mypy-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35aac3bb114e03888f535d5eb51b8bafbb3266586b599da1940f9b1be3ec5bd5", size = 14852174, upload-time = "2026-05-11T18:31:38.929Z" }, + { url = "https://files.pythonhosted.org/packages/55/8c/aed55408879043d72bb9135f4d0d19a02b886dd569631e113e3d2706cb8d/mypy-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8de55a8c861f2a49331f807be98d90caeceeef520bde13d43a160207f8af613e", size = 13651542, upload-time = "2026-05-11T18:36:04.636Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8e/f371a824b1f1fa8ea6e3dbb8703d232977d572be2329554a3bc4d960302f/mypy-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fdf2941a07434af755837d9880f7d7d25f1dacb1af9dcd4b9b66f2220a3024e", size = 14033929, upload-time = "2026-05-11T18:35:55.742Z" }, + { url = "https://files.pythonhosted.org/packages/94/21/f54be870d6dd53a82c674407e0f8eed7174b05ec78d42e5abd7b42e84fd5/mypy-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e195b817c13f02352a9c124301f9f30f078405444679b6753c1b96b6eed37285", size = 15039200, upload-time = "2026-05-11T18:33:10.281Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/bf21748626a40ce59fd29a39386ab46afec88b7bd2f0fa6c3a97c995523f/mypy-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5431d42af987ebd92ba2f71d45c85ed41d8e6ca9f5fd209a69f68f707d2469e5", size = 15272690, upload-time = "2026-05-11T18:32:07.205Z" }, + { url = "https://files.pythonhosted.org/packages/d6/d7/9e90d2cf47100bea550ed2bc7b0d4de3a62181d84d5e37da0003e8462637/mypy-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:767fe8c66dc3e01e19e1737d4c38ebefead16125e1b8e58ad421903b376f5c65", size = 11147435, upload-time = "2026-05-11T18:33:56.477Z" }, + { url = "https://files.pythonhosted.org/packages/ec/46/e5c449e858798e35ffc90946282a27c62a77be743fe17480e4977374eb91/mypy-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:ecfe70d43775ab99562ab128ce49854a362044c9f894961f68f898c23cb7429d", size = 10035052, upload-time = "2026-05-11T18:32:30.049Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ca/b279a672e874aedd5498ae25f722dacc8aa86bbffb939b3f97cbb1cf6686/mypy-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7354c5a7f69d9345c3d6e69921d57088eea3ddeeb6b20d34c1b3855b02c36ec2", size = 14848422, upload-time = "2026-05-11T18:35:45.984Z" }, + { url = "https://files.pythonhosted.org/packages/27/e6/3efe56c631d959b9b4454e208b0ac4b7f4f58b404c89f8bec7b49efdfc21/mypy-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:49890d4f76ac9e06ec117f9e09f3174da70a620a0c300953d8595c926e80947f", size = 13677374, upload-time = "2026-05-11T18:36:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/84/7f/8107ea87a44fd1f1b59882442f033c9c3488c127201b1d1d15f1cbd6022e/mypy-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:761be68e023ef5d94678772396a8af1220030f80837a3afd8d0aef3b419666f4", size = 14055743, upload-time = "2026-05-11T18:35:18.361Z" }, + { url = "https://files.pythonhosted.org/packages/51/4d/b6d34db183133b83761b9199a82d31557cdbb70a380d8c3b3438e11882a3/mypy-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c90345fc182dc363b891350457ec69c35140858538f38b4540845afcc32b1aef", size = 15020937, upload-time = "2026-05-11T18:34:59.618Z" }, + { url = "https://files.pythonhosted.org/packages/ff/d7/f08360c691d758acb02f45022c34d98b92892f4ea756644e1000d4b9f3d8/mypy-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b84802e7b5a6daf1f5e15bc9fcd7ddae77be13981ffab037f1c67bb84d67d135", size = 15253371, upload-time = "2026-05-11T18:36:41.081Z" }, + { url = "https://files.pythonhosted.org/packages/67/1b/09460a13719530a19bce27bd3bc8449e83569dd2ba7faf51c9c3c30c0b61/mypy-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:022c771234936ceac541ebaf836fe9e2abeb3f5e09aff21588fe543ff006fe21", size = 11326429, upload-time = "2026-05-11T18:34:13.526Z" }, + { url = "https://files.pythonhosted.org/packages/40/62/75dbf0f82f7b6680340efc614af29dd0b3c17b8a4f1cd09b8bd2fd6bc814/mypy-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:498207db725cec88829a6a5c2fc771205fd043719ef98bc49aba8fb9fc4e6d57", size = 10218799, upload-time = "2026-05-11T18:32:23.491Z" }, + { url = "https://files.pythonhosted.org/packages/b2/66/caca04ed7d972fb6eb6dd1ccd6df1de5c38fae8c5b3dc1c4e8e0d85ee6b9/mypy-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d5e5cad0efeba72b93cd17490cc0d69c5ac9ca132994fe3fb0314808aeeb83e", size = 15923458, upload-time = "2026-05-11T18:35:28.64Z" }, + { url = "https://files.pythonhosted.org/packages/ed/52/2d90cbe49d014b13ed7ff337930c30bad35893fe38a1e4641e756bb62191/mypy-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ff715050c127d724fd260a2e666e7747fdd83511c0c47d449d98238970aef780", size = 14757697, upload-time = "2026-05-11T18:36:14.208Z" }, + { url = "https://files.pythonhosted.org/packages/ac/37/d98f4a14e081b238992d0ed96b6d39c7cc0148c9699eb71eaa68629665ea/mypy-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82208da9e09414d520e912d3e462d454854bed0810b71540bb016dcbca7308fd", size = 15405638, upload-time = "2026-05-11T18:33:48.249Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c2/15c46613b24a84fad2aea1248bf9619b99c2767ae9071fe224c179a0b7d4/mypy-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e79ebc1b904b84f0310dff7469655a9c36c7a68bddb37bdd42b67a332df61d08", size = 16215852, upload-time = "2026-05-11T18:32:50.296Z" }, + { url = "https://files.pythonhosted.org/packages/5c/90/9c16a57f482c76d25f6379762b56bbf65c711d8158cf271fb2802cfb0640/mypy-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e583edc957cfb0deb142079162ae826f58449b116c1d442f2d91c69d9fced081", size = 16452695, upload-time = "2026-05-11T18:33:38.182Z" }, + { url = "https://files.pythonhosted.org/packages/0f/4c/215a4eeb63cacc5f17f516691ea7285d11e249802b942476bff15922a314/mypy-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b33b6cd332695bba180d55e717a79d3038e479a2c49cc5eb3d53603409b9a5d7", size = 12866622, upload-time = "2026-05-11T18:34:39.945Z" }, + { url = "https://files.pythonhosted.org/packages/4b/50/1043e1db5f455ffe4c9ab22747cd8ca2bc492b1e4f4e21b130a44ee2b217/mypy-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4f910fe825376a7b66ef7ca8c98e5a149e8cd64c19ae71d84047a74ee060d4e6", size = 10610798, upload-time = "2026-05-11T18:36:31.444Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2a/13ca1f292f6db1b98ff495ef3467736b331621c5917cad984b7043e7348d/mypy-2.1.0-py3-none-any.whl", hash = "sha256:a663814603a5c563fb87a4f96fb473eeb30d1f5a4885afcf44f9db000a366289", size = 2693302, upload-time = "2026-05-11T18:31:29.246Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "nyc-data-analytics" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "polars" }, + { name = "pyarrow" }, + { name = "requests" }, + { name = "typer" }, +] + +[package.dev-dependencies] +dev = [ + { name = "mypy" }, + { name = "pytest" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "polars", specifier = ">=0.20.0" }, + { name = "pyarrow", specifier = ">=14.0.0" }, + { name = "requests", specifier = ">=2.31.0" }, + { name = "typer", specifier = ">=0.9.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "mypy", specifier = ">=1.9.0" }, + { name = "pytest", specifier = ">=7.4.0" }, + { name = "ruff", specifier = ">=0.4.0" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "polars" +version = "1.38.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "polars-runtime-32" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/5e/208a24471a433bcd0e9a6889ac49025fd4daad2815c8220c5bd2576e5f1b/polars-1.38.1.tar.gz", hash = "sha256:803a2be5344ef880ad625addfb8f641995cfd777413b08a10de0897345778239", size = 717667, upload-time = "2026-02-06T18:13:23.013Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/49/737c1a6273c585719858261753da0b688454d1b634438ccba8a9c4eb5aab/polars-1.38.1-py3-none-any.whl", hash = "sha256:a29479c48fed4984d88b656486d221f638cba45d3e961631a50ee5fdde38cb2c", size = 810368, upload-time = "2026-02-06T18:11:55.819Z" }, +] + +[[package]] +name = "polars-runtime-32" +version = "1.38.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/07/4b/04d6b3fb7cf336fbe12fbc4b43f36d1783e11bb0f2b1e3980ec44878df06/polars_runtime_32-1.38.1.tar.gz", hash = "sha256:04f20ed1f5c58771f34296a27029dc755a9e4b1390caeaef8f317e06fdfce2ec", size = 2812631, upload-time = "2026-02-06T18:13:25.206Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/a2/a00defbddadd8cf1042f52380dcba6b6592b03bac8e3b34c436b62d12d3b/polars_runtime_32-1.38.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:18154e96044724a0ac38ce155cf63aa03c02dd70500efbbf1a61b08cadd269ef", size = 44108001, upload-time = "2026-02-06T18:11:58.127Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fb/599ff3709e6a303024efd7edfd08cf8de55c6ac39527d8f41cbc4399385f/polars_runtime_32-1.38.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c49acac34cc4049ed188f1eb67d6ff3971a39b4af7f7b734b367119970f313ac", size = 40230140, upload-time = "2026-02-06T18:12:01.181Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8c/3ac18d6f89dc05fe2c7c0ee1dc5b81f77a5c85ad59898232c2500fe2ebbf/polars_runtime_32-1.38.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fef2ef2626a954e010e006cc8e4de467ecf32d08008f130cea1c78911f545323", size = 41994039, upload-time = "2026-02-06T18:12:04.332Z" }, + { url = "https://files.pythonhosted.org/packages/f2/5a/61d60ec5cc0ab37cbd5a699edb2f9af2875b7fdfdfb2a4608ca3cc5f0448/polars_runtime_32-1.38.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8a5f7a8125e2d50e2e060296551c929aec09be23a9edcb2b12ca923f555a5ba", size = 45755804, upload-time = "2026-02-06T18:12:07.846Z" }, + { url = "https://files.pythonhosted.org/packages/91/54/02cd4074c98c361ccd3fec3bcb0bd68dbc639c0550c42a4436b0ff0f3ccf/polars_runtime_32-1.38.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:10d19cd9863e129273b18b7fcaab625b5c8143c2d22b3e549067b78efa32e4fa", size = 42159605, upload-time = "2026-02-06T18:12:10.919Z" }, + { url = "https://files.pythonhosted.org/packages/8e/f3/b2a5e720cc56eaa38b4518e63aa577b4bbd60e8b05a00fe43ca051be5879/polars_runtime_32-1.38.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:61e8d73c614b46a00d2f853625a7569a2e4a0999333e876354ac81d1bf1bb5e2", size = 45336615, upload-time = "2026-02-06T18:12:14.074Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8d/ee2e4b7de948090cfb3df37d401c521233daf97bfc54ddec5d61d1d31618/polars_runtime_32-1.38.1-cp310-abi3-win_amd64.whl", hash = "sha256:08c2b3b93509c1141ac97891294ff5c5b0c548a373f583eaaea873a4bf506437", size = 45680732, upload-time = "2026-02-06T18:12:19.097Z" }, + { url = "https://files.pythonhosted.org/packages/bf/18/72c216f4ab0c82b907009668f79183ae029116ff0dd245d56ef58aac48e7/polars_runtime_32-1.38.1-cp310-abi3-win_arm64.whl", hash = "sha256:6d07d0cc832bfe4fb54b6e04218c2c27afcfa6b9498f9f6bbf262a00d58cc7c4", size = 41639413, upload-time = "2026-02-06T18:12:22.044Z" }, +] + +[[package]] +name = "pyarrow" +version = "24.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/13/13e1069b351bdc3881266e11147ffccf687505dbb0ea74036237f5d454a5/pyarrow-24.0.0.tar.gz", hash = "sha256:85fe721a14dd823aca09127acbb06c3ca723efbd436c004f16bca601b04dcc83", size = 1180261, upload-time = "2026-04-21T10:51:25.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/c9/a47ab7ece0d86cbe6678418a0fbd1ac4bb493b9184a3891dfa0e7f287ae0/pyarrow-24.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b0e131f880cda8d04e076cee175a46fc0e8bc8b65c99c6c09dff6669335fde74", size = 35068898, upload-time = "2026-04-21T10:46:36.599Z" }, + { url = "https://files.pythonhosted.org/packages/d1/bc/8db86617a9a58008acf8913d6fed68ea2a46acb6de928db28d724c891a68/pyarrow-24.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:1b2fe7f9a5566401a0ef2571f197eb92358925c1f0c8dba305d6e43ea0871bb3", size = 36679915, upload-time = "2026-04-21T10:46:42.602Z" }, + { url = "https://files.pythonhosted.org/packages/eb/8e/fb178720400ef69db251eb4a9c3ccf4af269bc1feb5055529b8fc87170d1/pyarrow-24.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:0b3537c00fb8d384f15ac1e79b6eb6db04a16514c8c1d22e59a9b95c8ba42868", size = 45697931, upload-time = "2026-04-21T10:46:48.403Z" }, + { url = "https://files.pythonhosted.org/packages/f3/27/99c42abe8e21b44f4917f62631f3aa31404882a2c41d8a4cd5c110e13d52/pyarrow-24.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:14e31a3c9e35f1ab6356c6378f6f72830e6d2d5f1791df3774a7b097d18a6a1e", size = 48837449, upload-time = "2026-04-21T10:46:55.329Z" }, + { url = "https://files.pythonhosted.org/packages/36/b6/333749e2666e9032891125bf9c691146e92901bece62030ac1430e2e7c88/pyarrow-24.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b7d9a514e73bc42711e6a35aaccf3587c520024fe0a25d830a1a8a27c15f4f57", size = 49395949, upload-time = "2026-04-21T10:47:01.869Z" }, + { url = "https://files.pythonhosted.org/packages/17/25/c5201706a2dd374e8ba6ee3fd7a8c89fb7ffc16eed5217a91fd2bd7f7626/pyarrow-24.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b196eb3f931862af3fa84c2a253514d859c08e0d8fe020e07be12e75a5a9780c", size = 51912986, upload-time = "2026-04-21T10:47:09.872Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d2/4d1bbba65320b21a49678d6fbdc6ff7c649251359fdcfc03568c4136231d/pyarrow-24.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:35405aecb474e683fb36af650618fd5340ee5471fc65a21b36076a18bbc6c981", size = 27255371, upload-time = "2026-04-21T10:47:15.943Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a9/9686d9f07837f91f775e8932659192e02c74f9d8920524b480b85212cc68/pyarrow-24.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:6233c9ed9ab9d1db47de57d9753256d9dcffbf42db341576099f0fd9f6bf4810", size = 34981559, upload-time = "2026-04-21T10:47:22.17Z" }, + { url = "https://files.pythonhosted.org/packages/80/b6/0ddf0e9b6ead3474ab087ae598c76b031fc45532bf6a63f3a553440fb258/pyarrow-24.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:f7616236ec1bc2b15bfdec22a71ab38851c86f8f05ff64f379e1278cf20c634a", size = 36663654, upload-time = "2026-04-21T10:47:28.315Z" }, + { url = "https://files.pythonhosted.org/packages/7c/3b/926382efe8ce27ba729071d3566ade6dfb86bdf112f366000196b2f5780a/pyarrow-24.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:1617043b99bd33e5318ae18eb2919af09c71322ef1ca46566cdafc6e6712fb66", size = 45679394, upload-time = "2026-04-21T10:47:34.821Z" }, + { url = "https://files.pythonhosted.org/packages/b3/7a/829f7d9dfd37c207206081d6dad474d81dde29952401f07f2ba507814818/pyarrow-24.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6165461f55ef6314f026de6638d661188e3455d3ec49834556a0ebbdbace18bb", size = 48863122, upload-time = "2026-04-21T10:47:42.056Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e8/f88ce625fe8babaae64e8db2d417c7653adb3019b08aae85c5ed787dc816/pyarrow-24.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3b13dedfe76a0ad2d1d859b0811b53827a4e9d93a0bcb05cf59333ab4980cc7e", size = 49376032, upload-time = "2026-04-21T10:47:48.967Z" }, + { url = "https://files.pythonhosted.org/packages/36/7a/82c363caa145fff88fb475da50d3bf52bb024f61917be5424c3392eaf878/pyarrow-24.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:25ea65d868eb04015cd18e6df2fbe98f07e5bda2abefabcb88fce39a947716f6", size = 51929490, upload-time = "2026-04-21T10:47:55.981Z" }, + { url = "https://files.pythonhosted.org/packages/66/1c/e3e72c8014ad2743ca64a701652c733cc5cbcee15c0463a32a8c55518d9e/pyarrow-24.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:295f0a7f2e242dabd513737cf076007dc5b2d59237e3eca37b05c0c6446f3826", size = 27355660, upload-time = "2026-04-21T10:48:01.718Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d3/a1abf004482026ddc17f4503db227787fa3cfe41ec5091ff20e4fea55e57/pyarrow-24.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:02b001b3ed4723caa44f6cd1af2d5c86aa2cf9971dacc2ffa55b21237713dfba", size = 34976759, upload-time = "2026-04-21T10:48:07.258Z" }, + { url = "https://files.pythonhosted.org/packages/4f/4a/34f0a36d28a2dd32225301b79daad44e243dc1a2bb77d43b60749be255c4/pyarrow-24.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:04920d6a71aabd08a0417709efce97d45ea8e6fb733d9ca9ecffb13c67839f68", size = 36658471, upload-time = "2026-04-21T10:48:13.347Z" }, + { url = "https://files.pythonhosted.org/packages/1f/78/543b94712ae8bb1a6023bcc1acf1a740fbff8286747c289cd9468fced2a5/pyarrow-24.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a964266397740257f16f7bb2e4f08a0c81454004beab8ff59dd531b73610e9f2", size = 45675981, upload-time = "2026-04-21T10:48:20.201Z" }, + { url = "https://files.pythonhosted.org/packages/84/9f/8fb7c222b100d314137fa40ec050de56cd8c6d957d1cfff685ce72f15b17/pyarrow-24.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6f066b179d68c413374294bc1735f68475457c933258df594443bb9d88ddc2a0", size = 48859172, upload-time = "2026-04-21T10:48:27.541Z" }, + { url = "https://files.pythonhosted.org/packages/a7/d3/1ea72538e6c8b3b475ed78d1049a2c518e655761ea50fe1171fc855fcab7/pyarrow-24.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1183baeb14c5f587b1ec52831e665718ce632caab84b7cd6b85fd44f96114495", size = 49385733, upload-time = "2026-04-21T10:48:34.7Z" }, + { url = "https://files.pythonhosted.org/packages/c3/be/c3d8b06a1ba35f2260f8e1f771abbee7d5e345c0937aab90675706b1690a/pyarrow-24.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:806f24b4085453c197a5078218d1ee08783ebbba271badd153d1ae22a3ee804f", size = 51934335, upload-time = "2026-04-21T10:48:42.099Z" }, + { url = "https://files.pythonhosted.org/packages/9c/62/89e07a1e7329d2cde3e3c6994ba0839a24977a2beda8be6005ea3d860b99/pyarrow-24.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:e4505fc6583f7b05ab854934896bcac8253b04ac1171a77dfb73efef92076d91", size = 27271748, upload-time = "2026-04-21T10:49:42.532Z" }, + { url = "https://files.pythonhosted.org/packages/17/1a/cff3a59f80b5b1658549d46611b67163f65e0664431c076ad728bf9d5af4/pyarrow-24.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:1a4e45017efbf115032e4475ee876d525e0e36c742214fbe405332480ecd6275", size = 35238554, upload-time = "2026-04-21T10:48:48.526Z" }, + { url = "https://files.pythonhosted.org/packages/a8/99/cce0f42a327bfef2c420fb6078a3eb834826e5d6697bf3009fe11d2ad051/pyarrow-24.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:7986f1fa71cee060ad00758bcc79d3a93bab8559bf978fab9e53472a2e25a17b", size = 36782301, upload-time = "2026-04-21T10:48:55.181Z" }, + { url = "https://files.pythonhosted.org/packages/2a/66/8e560d5ff6793ca29aca213c53eec0dd482dd46cb93b2819e5aab52e4252/pyarrow-24.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:d3e0b61e8efb24ed38898e5cdc5fffa9124be480008d401a1f8071500494ae42", size = 45721929, upload-time = "2026-04-21T10:49:03.676Z" }, + { url = "https://files.pythonhosted.org/packages/27/0c/a26e25505d030716e078d9f16eb74973cbf0b33b672884e9f9da1c83b871/pyarrow-24.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:55a3bc1e3df3b5567b7d27ef551b2283f0c68a5e86f1cd56abc569da4f31335b", size = 48825365, upload-time = "2026-04-21T10:49:11.714Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/771f9ecb0c65e73fe9dccdd1717901b9594f08c4515d000c7c62df573811/pyarrow-24.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:641f795b361874ac9da5294f8f443dfdbee355cf2bd9e3b8d97aaac2306b9b37", size = 49451819, upload-time = "2026-04-21T10:49:21.474Z" }, + { url = "https://files.pythonhosted.org/packages/48/da/61ae89a88732f5a785646f3ec6125dbb640fa98a540eb2b9889caa561403/pyarrow-24.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8adc8e6ce5fccf5dc707046ae4914fd537def529709cc0d285d37a7f9cd442ca", size = 51909252, upload-time = "2026-04-21T10:49:31.164Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1a/8dd5cafab7b66573fa91c03d06d213356ad4edd71813aa75e08ce2b3a844/pyarrow-24.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:9b18371ad2f44044b81a8d23bc2d8a9b6a6226dca775e8e16cfee640473d6c5d", size = 27388127, upload-time = "2026-04-21T10:49:37.334Z" }, + { url = "https://files.pythonhosted.org/packages/ad/80/d022a34ff05d2cbedd8ccf841fc1f532ecfa9eb5ed1711b56d0e0ea71fc9/pyarrow-24.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:1cc9057f0319e26333b357e17f3c2c022f1a83739b48a88b25bfd5fa2dc18838", size = 35007997, upload-time = "2026-04-21T10:49:48.796Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ff/f01485fda6f4e5d441afb8dd5e7681e4db18826c1e271852f5d3957d6a80/pyarrow-24.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:e6f1278ee4785b6db21229374a1c9e54ec7c549de5d1efc9630b6207de7e170b", size = 36678720, upload-time = "2026-04-21T10:49:55.858Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c2/2d2d5fea814237923f71b36495211f20b43a1576f9a4d6da7e751a64ec6f/pyarrow-24.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:adbbedc55506cbdabb830890444fb856bfb0060c46c6f8026c6c2f2cf86ae795", size = 45741852, upload-time = "2026-04-21T10:50:04.624Z" }, + { url = "https://files.pythonhosted.org/packages/8e/3a/28ba9c1c1ebdbb5f1b94dfebb46f207e52e6a554b7fe4132540fde29a3a0/pyarrow-24.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:ae8a1145af31d903fa9bb166824d7abe9b4681a000b0159c9fb99c11bc11ad26", size = 48889852, upload-time = "2026-04-21T10:50:12.293Z" }, + { url = "https://files.pythonhosted.org/packages/df/51/4a389acfd31dca009f8fb82d7f510bb4130f2b3a8e18cf00194d0687d8ac/pyarrow-24.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d7027eba1df3b2069e2e8d80f644fa0918b68c46432af3d088ddd390d063ecde", size = 49445207, upload-time = "2026-04-21T10:50:20.677Z" }, + { url = "https://files.pythonhosted.org/packages/19/4b/0bab2b23d2ae901b1b9a03c0efd4b2d070256f8ce3fc43f6e58c167b2081/pyarrow-24.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e56a1ffe9bf7b727432b89104cc0849c21582949dd7bdcb34f17b2001a351a76", size = 51954117, upload-time = "2026-04-21T10:50:29.14Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/f4e9145da0417b3d2c12035a8492b35ff4a3dbc653e614fcfb51d9dedb38/pyarrow-24.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:38be1808cdd068605b787e6ca9119b27eb275a0234e50212c3492331680c3b1e", size = 28001155, upload-time = "2026-04-21T10:51:22.337Z" }, + { url = "https://files.pythonhosted.org/packages/79/4f/46a49a63f43526da895b1a45bbb51d5baf8e4d77159f8528fc3e5490007f/pyarrow-24.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:418e48ce50a45a6a6c73c454677203a9c75c966cb1e92ca3370959185f197a05", size = 35250387, upload-time = "2026-04-21T10:50:35.552Z" }, + { url = "https://files.pythonhosted.org/packages/a0/da/d5e0cd5ef00796922404806d5f00325cdadc3441ce2c13fe7115f2df9a64/pyarrow-24.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:2f16197705a230a78270cdd4ea8a1d57e86b2fdcbc34a1f6aebc72e65c986f9a", size = 36797102, upload-time = "2026-04-21T10:50:42.417Z" }, + { url = "https://files.pythonhosted.org/packages/34/c7/5904145b0a593a05236c882933d439b5720f0a145381179063722fbfc123/pyarrow-24.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:fb24ac194bfc5e86839d7dcd52092ee31e5fe6733fe11f5e3b06ef0812b20072", size = 45745118, upload-time = "2026-04-21T10:50:49.324Z" }, + { url = "https://files.pythonhosted.org/packages/13/d3/cca42fe166d1c6e4d5b80e530b7949104d10e17508a90ae202dac205ce2a/pyarrow-24.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:9700ebd9a51f5895ce75ff4ac4b3c47a7d4b42bc618be8e713e5d56bacf5f931", size = 48844765, upload-time = "2026-04-21T10:50:55.579Z" }, + { url = "https://files.pythonhosted.org/packages/b0/49/942c3b79878ba928324d1e17c274ed84581db8c0a749b24bcf4cbdf15bd3/pyarrow-24.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d8ddd2768da81d3ee08cfea9b597f4abb4e8e1dc8ae7e204b608d23a0d3ab699", size = 49471890, upload-time = "2026-04-21T10:51:02.439Z" }, + { url = "https://files.pythonhosted.org/packages/76/97/ff71431000a75d84135a1ace5ca4ba11726a231a8007bbb320a4c54075d5/pyarrow-24.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:61a3d7eaa97a14768b542f3d284dc6400dd2470d9f080708b13cd46b6ae18136", size = 51932250, upload-time = "2026-04-21T10:51:10.576Z" }, + { url = "https://files.pythonhosted.org/packages/51/be/6f79d55816d5c22557cf27533543d5d70dfe692adfbee4b99f2760674f38/pyarrow-24.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:c91d00057f23b8d353039520dc3a6c09d8608164c692e9f59a175a42b2ae0c19", size = 28131282, upload-time = "2026-04-21T10:51:16.815Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "rich" +version = "13.9.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149, upload-time = "2024-11-01T16:43:57.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424, upload-time = "2024-11-01T16:43:55.817Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/5f7ec371001337d8fa61701c186ff8b613ecac1651848c5950f4c4d5f2e9/ruff-0.15.16.tar.gz", hash = "sha256:d05e78d38c78caf020b03789e25106c93017db5a0cb6e2819885018c61343b78", size = 4714267, upload-time = "2026-06-04T16:33:09.974Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/42/53ef1c3953f157956db9bf7861e3bc50b9b887ce93300aa48cdba8336fe6/ruff-0.15.16-py3-none-linux_armv6l.whl", hash = "sha256:6ac3c0b3969cc6cf6b158c4e2f8f682acb58e7d700d8a44b65ecdc72d66ab0b2", size = 10709025, upload-time = "2026-06-04T16:32:51.935Z" }, + { url = "https://files.pythonhosted.org/packages/93/9a/a79159346f19134a956607754e57d8d128f7a4c00f4ad2f7514d224c172c/ruff-0.15.16-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:197c207ed75ffba54a0dec23db4aa939a27a3053073e085e0042433cbdc58e4a", size = 11063550, upload-time = "2026-06-04T16:32:42.24Z" }, + { url = "https://files.pythonhosted.org/packages/bc/72/3ce2ac000a5299ec238e01f51397b3b653c93b077d9b1bfe8715bb895f20/ruff-0.15.16-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3a39fec45ab316cc23e7558f23fea4a70403ddb5648ea9a4a3854a16973d0071", size = 10421345, upload-time = "2026-06-04T16:32:37.251Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c2/cc7fad3ec9169373f5b6a18f1917b91080feec40c3f9658334a1d28e2f03/ruff-0.15.16-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba93191d79003116b95128c9d306e045200fdbd0bccb782b110f3cd1d4abc5cf", size = 10757217, upload-time = "2026-06-04T16:32:54.722Z" }, + { url = "https://files.pythonhosted.org/packages/69/d2/3474009eaa0a65b31fa7152a2fad5e2f050c640ceb1e6b02ee6922e94c82/ruff-0.15.16-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6ee4b90520630120ef032aa5cc10db483852dff950e78b1d717e2993a61ac8d", size = 10507035, upload-time = "2026-06-04T16:33:05.343Z" }, + { url = "https://files.pythonhosted.org/packages/ca/81/b7ae6ccbd11f0c8dc3d5d67fc4be9b57ff57ca86ba56152021378e1277f2/ruff-0.15.16-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e4215bc938bc3c8215c1472c1aa437e310fee20cd427335fec9d7e609563628", size = 11255291, upload-time = "2026-06-04T16:32:49.49Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e1/46e526f1a7cc90857ce6ddf25fbb77eb6568651ac38d71b033af07076dd5/ruff-0.15.16-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c8d26be963b090f10e29abc8b3e74a2a321f6fa34e02424e30b5af89350ecbb", size = 12124922, upload-time = "2026-06-04T16:33:07.821Z" }, + { url = "https://files.pythonhosted.org/packages/1a/da/5c791b088b596b24d0deb967fa28ae02ad751a140c0b9ea81c5ab915d6c0/ruff-0.15.16-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f198cf4123602a2280ed46c307bcbafe41758d6fee5b456b6b6058ca1514b3b4", size = 11332186, upload-time = "2026-06-04T16:33:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/72/11/5da87abe20047c8962361473923ebb2f62b595250126aadfad8c20649c1e/ruff-0.15.16-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb27515fa6240fb586ae82b901a59e67d24acff86f2190b433dc542fe0435aeb", size = 11373541, upload-time = "2026-06-04T16:32:47.007Z" }, + { url = "https://files.pythonhosted.org/packages/fe/2a/8554754c23a854ae3fd6b507e36ad61ddb121e298c6d5d617dec94ed0f14/ruff-0.15.16-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a267c46ba1593fc26b8eecbea050b39d40c0b6bb7781ee11c90a02cd10032951", size = 11353014, upload-time = "2026-06-04T16:32:34.795Z" }, + { url = "https://files.pythonhosted.org/packages/62/25/62ea41529ec89f742ea3fed9cb1059c72877ec7cf9b9e99ac9cf3294d1d9/ruff-0.15.16-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:528c68f39a91498a8d50e91ff5985df3d105782bab49cc378e73ac26bff083e8", size = 10737467, upload-time = "2026-06-04T16:32:26.348Z" }, + { url = "https://files.pythonhosted.org/packages/90/17/334d3ad9de4d40f9dd58fdd09e35ce64553bb501e2f19a839e2fb6be14fc/ruff-0.15.16-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7ed55c58950df60589a9a7a5d2f8fa5f54ebd287163be805adfe6ee95a9de123", size = 10521910, upload-time = "2026-06-04T16:32:32.54Z" }, + { url = "https://files.pythonhosted.org/packages/4d/bd/3ac7c6ae77a885c1004b3dda2446ea401768d24f851c14b4ad4b24f6639c/ruff-0.15.16-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d482feaf51512b50f9790ceb417a56a61dd1e9d9bf967662b9ed27c01b34f53a", size = 10979190, upload-time = "2026-06-04T16:32:57.492Z" }, + { url = "https://files.pythonhosted.org/packages/33/d7/609546e6a413c3f216fbf2a50c928f97c80939154f6a0503114094a86191/ruff-0.15.16-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1e15bc8c94513dae2a40cc9ef07c94fdd4ecc9e29dabebeebe170f952322c9e3", size = 11477014, upload-time = "2026-06-04T16:32:44.687Z" }, + { url = "https://files.pythonhosted.org/packages/74/0d/f2cd247ad32633a5c36e97141a2c21b11c6279f7957bc2ff360b1e08fddd/ruff-0.15.16-py3-none-win32.whl", hash = "sha256:580378f7bd4aa25f72e74aa54948a9622f142b1e509521dd10902e886681cc1e", size = 10735541, upload-time = "2026-06-04T16:32:30.145Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9e/02e845ef151b1dee585e55c4739f8e1734ae1d9f1221dff65761c162208b/ruff-0.15.16-py3-none-win_amd64.whl", hash = "sha256:408256017284eddf98fff77b29aa4fb30f586042d535b2d9befc6512f400aaec", size = 11843403, upload-time = "2026-06-04T16:32:39.76Z" }, + { url = "https://files.pythonhosted.org/packages/15/19/016553f86f207450aebebc2b2b5088d086b901cc8186c02ac4284db3bd88/ruff-0.15.16-py3-none-win_arm64.whl", hash = "sha256:8cd61783afb39638a7133ef0d2dfb1e91277593962f81b5a8423eb0b888a6121", size = 11134555, upload-time = "2026-06-04T16:33:00.136Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "typer" +version = "0.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/51/9aed62104cea109b820bbd6c14245af756112017d309da813ef107d42e7e/typer-0.25.1.tar.gz", hash = "sha256:9616eb8853a09ffeabab1698952f33c6f29ffdbceb4eaeecf571880e8d7664cc", size = 122276, upload-time = "2026-04-30T19:32:16.964Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl", hash = "sha256:75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89", size = 58409, upload-time = "2026-04-30T19:32:18.271Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] diff --git a/benchmarks/swe-benchmark-data/README.md b/benchmarks/swe-benchmark-data/README.md new file mode 100644 index 0000000..89ed8ad --- /dev/null +++ b/benchmarks/swe-benchmark-data/README.md @@ -0,0 +1,83 @@ +# SWE Benchmark Data + +This directory holds the inputs and outputs of an LLM software-engineering benchmark. Each run takes a real-world problem inside a real repository and asks a specific model (driven by the `/swe` skill) to produce a GitHub issue spec, a low-level design, an expert review, and a testing plan. Multiple models can attempt the same problem so their artifacts can be compared side-by-side. **The skill stops at design and review - it does not implement the change.** + +## Directory Layout + +``` +benchmarks/swe-benchmark-data/ +├── README.md # This file +└── {repo-name}/ + ├── repo/ # Cloned source (gitignored - cloned by contributor, never committed) + ├── {problem-name}/ + │ ├── {model-name-A}/ # Artifacts produced by model A on this problem + │ │ ├── github-issue.md + │ │ ├── lld.md + │ │ ├── review.md + │ │ └── testing.md + │ └── {model-name-B}/ # Artifacts produced by model B on the same problem + │ └── ... + └── {next-problem-name}/ + └── ... +``` + +The `repo/` checkout under each `{repo-name}/` is **not** stored in this repository. It is added to `.gitignore` so contributors clone their own copy at the right tag before invoking `/swe`. This avoids carrying large third-party trees and keeps the per-tag history pinned by the contributor, not by this repo. + +## How to Set Up a Benchmark Repository Locally + +Clone each target repository at the documented tag inside its `{repo-name}/` folder. Clone into a `repo/` subdirectory so artifacts and source never collide: + +```bash +cd benchmarks/swe-benchmark-data/{repo-name} +git clone --branch --depth 1 https://github.com//.git repo +``` + +Use `--depth 1` to keep the checkout small. If you later need full history, run `git fetch --unshallow` from inside `repo/`. + +--- + +## Benchmark Repositories + +Each section below documents one target repository. To benchmark a model on one of its tasks, clone the repo at the listed tag and run `/swe` against the task description. + +### 1. mcp-gateway-registry + +| Field | Value | +|-------|-------| +| Source | https://github.com/agentic-community/mcp-gateway-registry | +| Tag | `1.24.4` | +| Local path | `benchmarks/swe-benchmark-data/mcp-gateway-registry/repo/` | +| Artifact path | `benchmarks/swe-benchmark-data/mcp-gateway-registry/{problem-name}/{model-name}/` | + +#### Setup + +```bash +cd benchmarks/swe-benchmark-data/mcp-gateway-registry +git clone --branch 1.24.4 --depth 1 https://github.com/agentic-community/mcp-gateway-registry.git repo +``` + +#### Tasks + +The tasks below are run with multiple models via the `/swe` skill. For each `{model-name}`, the resulting artifacts land at `benchmarks/swe-benchmark-data/mcp-gateway-registry/{problem-name}/{model-name}/`. + +| # | Problem name (folder) | Description | +|---|-----------------------|-------------| +| 1 | `remove-faiss` | Remove FAISS from the codebase and documentation. FAISS is obsolete in this repo. Delete all FAISS imports, dependencies, configuration, and references in docs. Replace any remaining vector-search needs with the maintained alternative already used elsewhere in the repo. | +| 2 | `remove-efs-from-terraform-aws-ecs` | Remove EFS from `terraform/aws-ecs/`. EFS is obsolete in this deployment. Delete the EFS file system, mount targets, security groups, and any task-definition volume mounts that reference it. Update `variables.tf`, `terraform.tfvars.example`, and module wiring. Verify `terraform validate` and `terraform plan` still succeed. | + +##### Future Enhancements + +Additional tasks will be added one by one as enhancements are scoped. Each new task gets its own `{problem-name}` folder (kebab-case) and a row in the table above. + +#### How to Run a Task with `/swe` + +``` +/swe + +# When prompted by the skill: +# - repo-name : mcp-gateway-registry +# - problem-name: remove-faiss (use the kebab-case name from the table) +# - model-name : claude-opus-4-7 (or whichever model is being benchmarked) +``` + +The skill will create `benchmarks/swe-benchmark-data/mcp-gateway-registry/remove-faiss/claude-opus-4-7/` and populate it with `github-issue.md`, `lld.md`, `review.md`, and `testing.md`. Re-run with a different `model-name` to add a sibling folder for direct comparison. The skill does not implement the change - that is a separate step the user can take with the design package as input. diff --git a/benchmarks/swe-benchmark-data/mcp-gateway-registry/remove-faiss/qwen-qwen3-coder-next/github-issue.md b/benchmarks/swe-benchmark-data/mcp-gateway-registry/remove-faiss/qwen-qwen3-coder-next/github-issue.md new file mode 100644 index 0000000..22108b3 --- /dev/null +++ b/benchmarks/swe-benchmark-data/mcp-gateway-registry/remove-faiss/qwen-qwen3-coder-next/github-issue.md @@ -0,0 +1,56 @@ +# GitHub Issue: Remove FAISS from the codebase and documentation + +## Title +Remove FAISS and replace with DocumentDB hybrid search + +## Labels +- enhancement +- refactor +- infra +- documentation + +## Description + +### Problem Statement +FAISS (Facebook AI Similarity Search) is an obsolete vector search library in this repository. The codebase now uses a maintained DocumentDB-based hybrid search implementation that provides equivalent functionality with better operational characteristics (serverless, scalable, no local vector index management). Keeping FAISS in the codebase creates unnecessary dependencies, potential maintenance burden, and confusion for future developers. + +### Proposed Solution +1. Remove `faiss-cpu` dependency from `pyproject.toml` +2. Delete the FAISS-specific search service (`registry/search/service.py`) +3. Update repository factory to remove FAISS path +4. Update configuration to remove FAISS file paths +5. Delete `FaissSearchRepository` implementation +6. Clean up all mock FAISS files and test files related to FAISS +7. Update documentation to remove FAISS references +8. Update Terraform and Docker configurations to remove FAISS references + +### User Stories +- As a developer, I want to remove obsolete FAISS code so the codebase is easier to maintain +- As a new team member, I want clear documentation about the search implementation without references to deprecated technologies +- As an operator, I want fewer dependencies to reduce potential security vulnerabilities + +### Acceptance Criteria +- [ ] `faiss-cpu` dependency removed from `pyproject.toml` +- [ ] `registry/search/` directory deleted +- [ ] `FaissSearchRepository` removed from `registry/repositories/file/search_repository.py` +- [ ] `registry/repositories/factory.py` updated to remove FAISS code paths +- [ ] `Settings` class updated to remove `faiss_index_path` and `faiss_metadata_path` properties +- [ ] All FAISS-related imports removed from codebase +- [ ] All FAISS-related documentation removed or updated +- [ ] `uv run python -m py_compile` passes on all remaining Python files +- [ ] Tests pass with new configuration (using DocumentDB/file backend search) +- [ ] Docker builds still work +- [ ] No FAISS references remain in `grep -r faiss` against the codebase + +### Out of Scope +- Changing the search API contract (search endpoints remain the same) +- Changing the embedding model configuration +- Modifying the DocumentDB search implementation +- Updating the metrics-service (which also has FAISS references) +- Changing from MongoDB to another database backend + +### Dependencies +- None - this is a self-contained refactor + +### Related Issues +- Issue #955 (search optimization) diff --git a/benchmarks/swe-benchmark-data/mcp-gateway-registry/remove-faiss/qwen-qwen3-coder-next/lld.md b/benchmarks/swe-benchmark-data/mcp-gateway-registry/remove-faiss/qwen-qwen3-coder-next/lld.md new file mode 100644 index 0000000..8766f47 --- /dev/null +++ b/benchmarks/swe-benchmark-data/mcp-gateway-registry/remove-faiss/qwen-qwen3-coder-next/lld.md @@ -0,0 +1,448 @@ +# Low-Level Design: Remove FAISS from the codebase + +*Created: 2026-06-05* +*Author: Claude* +*Status: Draft* + +## Table of Contents +1. [Overview](#overview) +2. [Codebase Analysis](#codebase-analysis) +3. [Architecture](#architecture) +4. [Data Models](#data-models) +5. [Implementation Details](#implementation-details) +6. [File Changes](#file-changes) +7. [Testing Strategy](#testing-strategy) +8. [Rollout Plan](#rollout-plan) + +## Overview + +### Problem Statement +FAISS (Facebook AI Similarity Search) is an obsolete vector search library that has been replaced by a DocumentDB-based hybrid search implementation. The current codebase maintains both implementations, which creates: +- Unnecessary dependency (`faiss-cpu>=1.7.4`) +- Maintenance burden (two search implementations) +- Confusion for new developers + +### Goals +- Remove FAISS as a dependency +- Delete FAISS-specific code paths +- Ensure DocumentDB/file backend search continues to work +- Update documentation and configurations +- Maintain backward compatibility for search API + +### Non-Goals +- Changing the search API or behavior +- Updating the metrics-service (separate service) +- Modifying the DocumentDB search implementation + +## Codebase Analysis + +### Key Files Reviewed + +| File/Directory | Purpose | Relevance to FAISS Removal | +|----------------|---------|--------------------------| +| `pyproject.toml` | Python dependencies | Contains `faiss-cpu>=1.7.4` dependency | +| `registry/search/service.py` | FAISS vector search service | Main FAISS implementation (998 lines) | +| `registry/repositories/file/search_repository.py` | File-based search with FAISS | Uses FaissService internally | +| `registry/repositories/documentdb/search_repository.py` | DocumentDB hybrid search | **This is the maintained alternative** | +| `registry/repositories/factory.py` | Repository factory | Routes to FaissSearchRepository for file backend | +| `registry/core/config.py` | Settings class | Contains `faiss_index_path` and `faiss_metadata_path` properties | +| `registry/core/schemas.py` | Pydantic models | Contains `FaissMetadata` model | +| `docker-compose*.yml` | Docker configuration | Comments mention FAISS | +| `build-config.yaml` | Build configuration | Comments mention FAISS | +| `tests/fixtures/mocks/mock_faiss.py` | Mock for testing | FAISS-specific test fixtures | +| `tests/unit/search/test_faiss_service.py` | Unit tests | Tests for FAISS service | + +### Existing Patterns Identified +1. **Repository Factory Pattern**: `registry/repositories/factory.py` chooses between implementations based on `storage_backend` + - File backend => `FaissSearchRepository` + - DocumentDB backend => `DocumentDBSearchRepository` + +2. **Search Interface**: All search repositories implement `SearchRepositoryBase` - this abstraction enables easy swapping + +3. **Embedding Abstraction**: `embeddings/create_embeddings_client()` creates embeddings via a factory pattern, supporting multiple providers + +4. **Hybrid Search in DocumentDB**: The DocumentDB implementation already has hybrid search (vector + keyword) with: + - HNSW vector index + - Keyword text boosting + - Reciprocal Rank Fusion (RRF) for score combination + +5. **Fallback Mechanisms**: DocumentDB search has fallback paths for: + - MongoDB CE (no vector search) => client-side cosine similarity + - Embedding model unavailable => lexical-only search + +### Integration Points + +| Component | Integration Type | Details | +|-----------|------------------|---------| +| `factory.py` | Factory pattern | Routes based on `settings.storage_backend` | +| `agent_routes.py` | API layer | Calls `get_search_repository().search()` | +| `search_routes.py` | API layer | Calls `get_search_repository().search()` | +| `cli/agent_mgmt.py` | CLI | Uses search repository for `search` command | + +### Constraints and Limitations Discovered +- **File backend**: Currently depends on FAISS; after removal, file backend needs an alternative +- **MongoDB CE compatibility**: DocumentDB search already has client-side fallback for environments without vector search +- **Test mocks**: `tests/fixtures/mocks/mock_faiss.py` exists to avoid loading native FAISS library during tests +- **Metrics-service**: Also has FAISS references but is a separate service (out of scope per spec) + +## Architecture + +### Current Architecture (with FAISS) +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Search Service Layer │ +├─────────────────────────────────────────────────────────────────┤ +│ ┌─────────────────┐ ┌──────────────────────────────┐ │ +│ │ DocumentDB │ │ FAISS-based File │ │ +│ │ Hybrid Search │ │ Search Repository │ │ +│ │ │ │ │ │ +│ │ - HNSW Index │ │ Uses FaissService: │ │ +│ │ - Keyword Boost │ │ - faiss.IndexFlatIP │ │ +│ │ - RRF Fusion │ │ - Embedding model │ │ +│ └─────────────────┘ │ - Metadata store │ │ +│ └──────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + ^ + │ + ┌───────────────┴───────────────┐ + │ registry/repositories/factory│ + └───────────────┬───────────────┘ + │ + storage_backend = "documentdb" / "file" +``` + +### Target Architecture (without FAISS) +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Search Service Layer │ +├─────────────────────────────────────────────────────────────────┤ +│ ┌─────────────────┐ ┌──────────────────────────────┐ │ +│ │ DocumentDB │ │ File-based Search with │ │ +│ │ Hybrid Search │ │ In-Memory Vector Search │ │ +│ │ │ │ (Lightweight Implementation)│ │ +│ │ - HNSW Index │ │ │ │ +│ │ - Keyword Boost │ │ Alternative approaches: │ │ +│ │ - RRF Fusion │ │ - In-memory FAISS (if │ │ +│ └─────────────────┘ │ needed temporarily) │ │ +│ └──────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + ^ + │ + ┌───────────────┴───────────────┐ + │ registry/repositories/factory│ + └───────────────┬───────────────┘ + │ + storage_backend = "documentdb" / "file" +``` + +### Key Changes +1. **Remove `FaissSearchRepository`**: File backend will use DocumentDB search with in-memory fallback, or implement a lightweight vector search +2. **Update factory.py**: Remove FAISS-specific routing - file backend should use DocumentDB search for consistency + +## Data Models + +### FAISS-Specific Models to Remove + +#### `FaissMetadata` in `registry/core/schemas.py` +```python +class FaissMetadata(BaseModel): + """FAISS metadata model.""" + id: int + text_for_embedding: str + full_server_info: ServerInfo +``` +**Action**: Delete this model entirely + +### New Models +None - no new models needed. Search behavior is preserved through DocumentDB implementation. + +## Implementation Details + +### Step-by-Step Plan (for a future implementer) + +#### Step 1: Remove FAISS dependency from pyproject.toml +**File:** `pyproject.toml` +**Lines:** ~23 + +Remove the faiss-cpu dependency: +```diff +- "faiss-cpu>=1.7.4", + "sentence-transformers>=3.0.0", +``` + +#### Step 2: Delete FaissSearchRepository +**File:** `registry/repositories/file/search_repository.py` +**Lines:** 1-137 (entire file) + +**Action:** Delete the entire `FaissSearchRepository` class + +#### Step 3: Update repository factory +**File:** `registry/repositories/factory.py` +**Lines:** 132-151 + +Current code: +```python +def get_search_repository() -> SearchRepositoryBase: + global _search_repo + + if _search_repo is not None: + return _search_repo + + backend = settings.storage_backend + logger.info(f"Creating search repository with backend: {backend}") + + if backend in MONGODB_BACKENDS: + from .documentdb.search_repository import DocumentDBSearchRepository + _search_repo = DocumentDBSearchRepository() + else: + from .file.search_repository import FaissSearchRepository + _search_repo = FaissSearchRepository() + + return _search_repo +``` + +**Action:** For file backend, use DocumentDB search repository (the maintained implementation). The DocumentDB implementation can work with any storage backend - it just needs a local MongoDB instance or in-memory mode. + +#### Step 4: Update config.py to remove FAISS paths +**File:** `registry/core/config.py` +**Lines:** 995-1001 + +Remove these properties: +```python +@property +def faiss_index_path(self) -> Path: + return self.servers_dir / "service_index.faiss" + +@property +def faiss_metadata_path(self) -> Path: + return self.servers_dir / "service_index_metadata.json" +``` + +#### Step 5: Delete FAISS service module +**File:** `registry/search/service.py` +**Lines:** 1-1202 (entire file) + +**Action:** Delete the entire `FaissService` class and the global `faiss_service` instance + +**Note:** The DocumentDB search implementation has its own embedding logic via `create_embeddings_client()` so no replacement is needed for embedding functionality + +#### Step 6: Delete mock FAISS files +**Files to delete:** +- `tests/fixtures/mocks/mock_faiss.py` +- `tests/unit/search/test_faiss_service.py` +- `tests/conftest.py` - remove FAISS-related imports and fixtures +- `tests/unit/conftest.py` - remove FAISS-related fixtures +- `tests/unit/search/__init__.py` - remove FAISS comment + +#### Step 7: Update test imports and references +**Files to modify:** +- `tests/README.md` - update documentation +- `tests/test_infrastructure.py` - update tests +- `tests/unit/api/test_agent_routes.py` - update mocks +- `tests/conftest.py` - remove mock_faiss module installation +- `tests/unit/test_safe_eval_arithmetic.py` - remove FAISS module patching + +#### Step 8: Update documentation +**Files to modify:** +- `docs/embeddings.md` - update search architecture explanation +- `docs/design/` - review for FAISS references +- `docs/testing/` - update test documentation +- `docs/faq/` - update FAQ if FAISS mentioned + +#### Step 9: Update configuration examples +**Files to modify:** +- `.env.example` - remove any FAISS-related comments +- `build-config.yaml` - remove FAISS mentions +- `docker-compose*.yml` - update comments + +#### Step 10: Update Terraform +**Files to modify:** +- `terraform/aws-ecs/OPERATIONS.md` - remove FAISS from service description +- `terraform/aws-ecs/scripts/service_mgmt.sh` - remove FAISS verification functions +- `terraform/telemetry-collector/lambda/collector/schemas.py` - update validation pattern + +#### Step 11: Update CLI +**File:** `cli/agent_mgmt.py` +**Lines:** ~1 + +The CLI references FAISS in documentation: +```python +"""The 'search' command performs natural language semantic search using FAISS vector index""" +``` + +**Action:** Update docstring to reference "vector search" or "hybrid search" instead of FAISS + +#### Step 12: Verify remaining references +```bash +# Check for any remaining FAISS references +grep -ri "faiss" registry/ --include="*.py" +grep -ri "faiss" docs/ --include="*.md" +grep -ri "faiss" terraform/ --include="*.py" --include="*.tf" --include="*.sh" +grep -ri "faiss" .env.example +``` + +## Configuration Parameters + +### Parameters to Remove +None - no new parameters are needed. The DocumentDB search repository configures itself based on existing settings. + +### Settings Class Changes +The following properties will be removed (no replacement needed): +- `faiss_index_path` - was `Path("/app/registry/servers/service_index.faiss")` +- `faiss_metadata_path` - was `Path("/app/registry/servers/service_index_metadata.json")` + +## New Dependencies +None - removing `faiss-cpu` reduces dependencies. + +## Implementation Details (continued) + +### Search Backend Strategy After Changes + +| `storage_backend` | Search Implementation | Notes | +|-------------------|----------------------|-------| +| `file` | `DocumentDBSearchRepository` with local fallback | Uses same code path as documentdb | +| `documentdb` | `DocumentDBSearchRepository` | Standard DocumentDB search | +| `mongodb-ce` | `DocumentDBSearchRepository` | Uses client-side fallback if vector search not supported | +| `mongodb` | `DocumentDBSearchRepository` | Standard DocumentDB search | +| `mongodb-atlas` | `DocumentDBSearchRepository` | Uses HNSW vector index | + +### Error Handling +- If `faiss` import fails during migration, it should be caught and the error message should guide users to update their dependencies +- If DocumentDB connection fails, fallback to lexical-only search (already implemented) + +### Logging +Add logging at startup to confirm search backend initialization: +```python +logger.info(f"Search repository initialized with backend: {settings.storage_backend}") +``` + +## File Changes Summary + +### Files to Delete +| File | Reason | +|------|--------| +| `registry/search/service.py` | FAISS service implementation | +| `tests/fixtures/mocks/mock_faiss.py` | Mock for FAISS testing | +| `tests/unit/search/test_faiss_service.py` | FAISS service unit tests | + +### Files to Modify +| File | Lines | Change | +|------|-------|--------| +| `pyproject.toml` | ~23 | Remove `faiss-cpu>=1.7.4` | +| `registry/repositories/factory.py` | 132-151 | Remove FAISS routing path | +| `registry/core/config.py` | 995-1001 | Remove faiss path properties | +| `registry/core/schemas.py` | 505-510 | Remove FaissMetadata model | +| `tests/conftest.py` | Multiple | Remove FAISS mocks | +| `tests/unit/conftest.py` | Multiple | Remove FAISS fixtures | +| `tests/unit/search/__init__.py` | 1 | Update docstring | +| `tests/unit/api/test_agent_routes.py` | Multiple | Remove FAISS mocks | +| `tests/unit/core/test_config.py` | Multiple | Remove FAISS tests | +| `cli/agent_mgmt.py` | 1 | Update docstring | + +### Files to Delete (Tests) +- `tests/unit/search/test_faiss_service.py` + +### Files with Comments Only +- `docs/embeddings.md` - update explanation +- `docker-compose*.yml` - update comments +- `build-config.yaml` - update comments +- `terraform/aws-ecs/OPERATIONS.md` - update table +- `terraform/aws-ecs/scripts/service_mgmt.sh` - remove functions +- `terraform/telemetry-collector/lambda/collector/schemas.py` - update pattern + +## Testing Strategy + +### Unit Tests to Add +- Test that `get_search_repository()` returns correct type for each backend +- Test that DocumentDB search works without FAISS +- Test search functionality end-to-end + +### Backwards Compatibility Tests +- Verify search API returns same structure without FAISS +- Verify `FaissSearchRepository` no longer exists +- Verify `faiss` import fails with proper error + +### Test Commands +```bash +# Verify no FAISS imports remain +uv run pytest tests/ -k "search" --tb=short + +# Test file backend +export STORAGE_BACKEND=file +uv run pytest tests/ -k "search" --tb=short + +# Test documentdb backend +export STORAGE_BACKEND=documentdb +uv run pytest tests/ -k "search" --tb=short + +# Full test suite +uv run pytest +``` + +## Alternatives Considered + +### Alternative 1: Keep FAISS but mark as deprecated +**Description:** Keep the code but add deprecation warnings +**Pros:** Gradual migration path +**Cons:** More complex codebase, maintenance burden +**Why Rejected:** Spec calls for complete removal, not deprecation + +### Alternative 2: Replace FaissSearchRepository with a new lightweight implementation +**Description:** Write a new in-memory vector search from scratch +**Pros:** Full control, no external dependencies +**Cons:** Significant development effort, potential bugs +**Why Rejected:** DocumentDB search is already maintained and works for both backends + +### Alternative 3: Keep DocumentDB search for DB backend, use something else for file backend +**Description:** Two different search implementations based on backend +**Pros:** Optimized for each backend +**Cons:** More code to maintain, test doubles +**Why Rejected:** Single search implementation is simpler and easier to maintain + +### Comparison Matrix + +| Criteria | Chosen Approach | Keep FAISS | New Implementation | +|----------|----------------|------------|-------------------| +| Code Complexity | Low (unified) | Medium (dual) | High (new code) | +| Maintenance | Low (one codebase) | Med (two backends) | High (new features) | +| Testing | Medium (Docker tests) | Low (FAISS tests) | High (new tests) | +| Performance | High (HNSW in DB) | Med (local FAISS) | Unknown | + +## Rollout Plan + +### Phase 1: Code Changes (out of scope for this skill) +1. Remove FAISS dependency +2. Delete FAISS-specific code +3. Update repository factory +4. Update tests + +### Phase 2: Testing +1. Run unit tests +2. Run integration tests +3. Test file backend +4. Test documentdb backend + +### Phase 3: Documentation +1. Update README.md +2. Update docs/embeddings.md +3. Update terraform documentation + +### Phase 4: Deployment +1. Update `.env.example` +2. Update docker-compose files +3. Deploy to staging +4. Deploy to production + +## Open Questions + +1. **Should the file backend still work?** The current implementation uses DocumentDBSearchRepository for all backends, but file backend might need a different approach if DocumentDB is not available. + +2. **What about the metrics-service?** It also has FAISS references but is a separate service. Should it be handled in a separate issue? + +3. **Should we keep any FAISS-related fixtures for backwards compatibility?** Tests that expect FAISS should be updated to expect DocumentDB search behavior. + +## References +- `registry/search/service.py` - Current FAISS implementation (to be deleted) +- `registry/repositories/documentdb/search_repository.py` - DocumentDB hybrid search (maintained) +- `registry/repositories/file/search_repository.py` - FAISS repository (to be deleted) +- Issue #955 - Search optimization diff --git a/benchmarks/swe-benchmark-data/mcp-gateway-registry/remove-faiss/qwen-qwen3-coder-next/review.md b/benchmarks/swe-benchmark-data/mcp-gateway-registry/remove-faiss/qwen-qwen3-coder-next/review.md new file mode 100644 index 0000000..164cb1e --- /dev/null +++ b/benchmarks/swe-benchmark-data/mcp-gateway-registry/remove-faiss/qwen-qwen3-coder-next/review.md @@ -0,0 +1,223 @@ +# Expert Review: Remove FAISS from the codebase + +*Created: 2026-06-05* +*Related LLD: `./lld.md`* +*Related Issue: `./github-issue.md`* + +--- + +## Review Summary + +| Reviewer | Verdict | Blockers | Key Recommendations | +|----------|---------|----------|---------------------| +| Frontend (Pixel) | APPROVED | 0 | Update CLI docs for search command | +| Backend (Byte) | APPROVED | 0 | Test file backend thoroughly | +| SRE (Circuit) | APPROVED WITH CHANGES | 1 | Verify Docker builds work | +| Security (Cipher) | APPROVED | 0 | Run security scan after changes | +| SMTS (Sage) | APPROVED | 0 | Consider metrics-service in follow-up | + +--- + +## Review Details + +### Frontend Engineer (Pixel) + +**Focus:** CLI, user-facing API, documentation + +#### Strengths +- Search API contract remains unchanged - no breaking changes for clients +- Documentation updates scope is well-defined +- Repository factory provides clean abstraction for search backend + +#### Concerns +1. **CLI documentation**: The `cli/agent_mgmt.py` docstring mentions "FAISS vector index" - this should be updated to "vector search" or "hybrid search" +2. **User-facing error messages**: If someone has `faiss-cpu` installed and tries to use file backend after removal, error messages should be helpful + +#### New Libraries / Dependencies Required +None - removing a dependency is a net positive + +#### Better Alternatives Considered +- Keeping FAISS deprecated:Rejected - adds complexity +- Rolling out deprecation warnings first: Rejected - spec calls for direct removal + +#### Recommendations +1. Update CLI command help text to reference "vector search" generically +2. Add a startup log message about which search backend is being used +3. Consider adding a migration note in release notes + +#### Questions for Author +1. Will the `搜索` command still work the same way after the change? *Yes - same API contract* + +#### Verdict: APPROVED + +--- + +### Backend Engineer (Byte) + +**Focus:** API design, data models, business logic, performance + +#### Strengths +- DocumentDB search implementation is well-structured with proper fallbacks +- Search repository interface provides clean separation of concerns +- Hybrid search (vector + keyword + RRF) is a robust implementation + +#### Concerns +1. **File backend behavior**: After removing `FaissSearchRepository`, file backend uses `DocumentDBSearchRepository`. Need to verify this works correctly for local development +2. **Embedded server test isolation**: If tests assume FAISS exists, they may fail + +#### New Libraries / Dependencies Required +None - removing `faiss-cpu` is beneficial + +#### Better Alternatives Considered +- Implementing lightweight vector search for file backend: Rejected - too much work +- Keeping FAISS: Rejected - maintenance burden + +#### Recommendations +1. Add integration test for file backend search functionality +2. Verify embeddings client works without FAISS loaded +3. Ensure test fixtures don't reference FAISS + +#### Questions for Author +1. How should local development mode work without FAISS? *DocumentDB search can work in file mode by connecting to local MongoDB or using client-side search* + +#### Verdict: APPROVED + +--- + +### SRE/DevOps Engineer (Circuit) + +**Focus:** Deployment, monitoring, scaling, infrastructure + +#### Strengths +- Docker image build should be faster without FAISS (smaller image) +- No more native FAISS library to install in containers +- Simpler dependency graph + +#### Concerns +1. **Docker build verification**: FAISS has native extensions that may affect Docker layers - need to verify builds work after removal +2. **MongoDB connection in file mode**: If file backend needs MongoDB, ensure `MONGO_URI` or similar is documented +3. **Terraform service descriptions**: The AWS ECS service management script has FAISS-related verification functions that should be removed + +#### New Libraries / Dependencies Required +None - removing FAISS is good + +#### Better Alternatives Considered +- None - this is a straightforward dependency removal + +#### Recommendations +1. **Verify Docker image builds** without FAISS +2. Update `docker-compose*.yml` comments to remove "FAISS" mention +3. Remove Faiss-related functions from `terraform/aws-ecs/scripts/service_mgmt.sh` +4. Update `.env.example` if FAISS is mentioned there + +#### Deployment Checklist +- [ ] Docker image builds successfully +- [ ] FAISS not present in image layers +- [ ] Search endpoints work after deployment +- [ ] File backend search works for local development + +#### Questions for Author +1. Will the Docker image be smaller after removing FAISS? *Yes - FAISS has native dependencies that add significant size* + +#### Verdict: APPROVED WITH CHANGES + +**Blocker:** Verify Docker builds work before deployment + +--- + +### Security Engineer (Cipher) + +**Focus:** AuthN/AuthZ, validation, OWASP, data protection + +#### Strengths +- Removing an external dependency reduces attack surface +- NoFAISS means fewer native library vulnerabilities to track +- DocumentDB search uses standard Python libraries + +#### Concerns +1. **Dependency reduction**: Removing `faiss-cpu` reduces the number of dependencies to audit +2. **Embeddings security**: Ensure embedding API calls still go through proper auth paths + +#### New Libraries / Dependencies Required +None + +#### Better Alternatives Considered +- None - this is a security-positive change + +#### Recommendations +1. Run `uv run bandit -r registry/` after changes +2. Verify no embeddings credentials are logged +3. Run `pip-audit` or similar to verify dependency tree + +#### Compliance Considerations +- None - this change does not affect data handling or PII + +#### Questions for Author +1. Does removing FAISS affect any data export/import functionality? *No - search is read-only* + +#### Verdict: APPROVED + +--- + +### SMTS (Sage) + +**Focus:** Architecture, code quality, maintainability + +#### Strengths +- Clean abstraction with `SearchRepositoryBase` interface +- DocumentDB search is production-ready with robust fallbacks +- Code removal follows principle of keeping only necessary code + +#### Concerns +1. **Test coverage**: Need to ensure all search-related tests work without FAISS +2. **Migration path**: Users with existing FAISS indices need guidance + +#### New Libraries / Dependencies Required +None + +#### Better Alternatives Considered +- Alternative 1: Deprecate then remove - Rejected +- Alternative 2: New lightweight vector search - Rejected +- Alternative 3: Dual implementation - Rejected + +#### Recommendations +1. Consider a follow-up issue to clean up metrics-service FAISS references +2. Add a migration section to release notes +3. Consider logging which search backend is active at startup + +#### Code Quality Observations +- Repository factory pattern is well-designed for backend switching +- Search interface is clean and minimal +- DocumentDB search implementation follows existing patterns + +#### Questions for Author +1. What happens to existing FAISS indices when upgrading? *They become unused - no data loss since DocumentDB maintains its own search index* + +#### Verdict: APPROVED + +--- + +## Cross-Cutting Concerns + +### Testing Strategy +All reviewers agree that testing is critical: +1. Run full test suite +2. Test file backend specifically +3. Test documentdb backend +4. Test search API endpoints + +### Deployment Checklist +- [ ] Remove `faiss-cpu` from `pyproject.toml` +- [ ] Delete FAISS-specific files +- [ ] Update factory.py +- [ ] Update tests +- [ ] Run `uv run bandit -r registry/` +- [ ] Build Docker image +- [ ] Test search endpoints + +### Documentation Checklist +- [ ] Update CLI help text +- [ ] Update docker-compose comments +- [ ] Update README.md if needed +- [ ] Update release notes +- [ ] Update terraform documentation diff --git a/benchmarks/swe-benchmark-data/mcp-gateway-registry/remove-faiss/qwen-qwen3-coder-next/summary.md b/benchmarks/swe-benchmark-data/mcp-gateway-registry/remove-faiss/qwen-qwen3-coder-next/summary.md new file mode 100644 index 0000000..c5aba38 --- /dev/null +++ b/benchmarks/swe-benchmark-data/mcp-gateway-registry/remove-faiss/qwen-qwen3-coder-next/summary.md @@ -0,0 +1,69 @@ +# Summary: mcp-gateway-registry / remove-faiss / qwen-qwen3-coder-next + +*Created: 2026-06-05T20:20:00Z* +*Artifacts: `benchmarks/swe-benchmark-data/mcp-gateway-registry/remove-faiss/qwen-qwen3-coder-next/`* + +## Run Status + +| Artifact | Exists | Bytes | Lines | Well-formed | Issues | +|----------|--------|-------|-------|-------------|--------| +| github-issue.md | yes | 2,669 | 56 | yes | 0 | +| lld.md | yes | 19,751 | 448 | yes | 0 | +| review.md | yes | 7,641 | 223 | yes | 0 | +| testing.md | yes | 17,067 | 624 | yes | 0 | + +**Overall:** GREEN - All four artifacts exist, all four well-formed, no errors + +### Issues Captured in Artifacts + +None + +## Session Token Usage + +| Model | Messages | Input | Output | Cache Create | Cache Read | Total | +|-------|----------|-------|--------|--------------|------------|-------| +| qwen.qwen3-coder-next | 135 | 14,797,176 | 39,563 | 0 | 0 | 14,836,739 | +| **All models** | 135 | 14,797,176 | 39,563 | 0 | 0 | 14,836,739 | + +*Cache hit ratio:* 0% (Qwen model does not use cache) + +## Tool Call Mix + +| Tool | Count | +|------|-------| +| Bash | 42 | +| Read | 15 | +| AskUserQuestion | 7 | +| Write | 5 | +| Grep | 1 | +| Agent | 1 | + +## Errors and Warnings + +None + +## Themes from User Prompts + +- /swe benchmark run request (135 mentions) +- remove FAISS from codebase (primary focus) +- agentic-community/mcp-gateway-registry repo as target +- qwen-qwen3-coder-next model (session model) + +## Sessions Included + +| Session | First event | Last event | Lines | +|---------|-------------|------------|-------| +| 278b31c8-e24f-443a-a02c-8b99cf058e40 | 2026-06-05T19:49:24Z | 2026-06-05T20:18:52Z | 3,590 | + +--- + +## Artifact Quality Notes + +All four artifacts passed the heuristics for "well-formed": + +1. **github-issue.md**: Contains `## Title`, `## Description`, and `### Acceptance Criteria` with checkboxes +2. **lld.md**: Contains `# Low-Level Design`, `## Table of Contents`, `## Architecture`, `## Data Models`, and `## File Changes` +3. **review.md**: Contains all five reviewers (Pixel, Byte, Circuit, Cipher, Sage) and `Verdict` lines +4. **testing.md**: Contains `# Testing Plan`, `## 1. Functional Tests`, and `## 6. Test Execution Checklist` + +The session used qwen.qwen3-coder-next for all 135 assistant messages. This session did not use token caching (0% cache hit ratio). diff --git a/benchmarks/swe-benchmark-data/mcp-gateway-registry/remove-faiss/qwen-qwen3-coder-next/testing.md b/benchmarks/swe-benchmark-data/mcp-gateway-registry/remove-faiss/qwen-qwen3-coder-next/testing.md new file mode 100644 index 0000000..f4ea211 --- /dev/null +++ b/benchmarks/swe-benchmark-data/mcp-gateway-registry/remove-faiss/qwen-qwen3-coder-next/testing.md @@ -0,0 +1,624 @@ +# Testing Plan: Remove FAISS from the codebase + +*Created: 2026-06-05* +*Related LLD: `./lld.md`* +*Related Issue: `./github-issue.md`* + +## Overview + +### Scope of Testing +This plan tests the removal of FAISS (Facebook AI Similarity Search) from the MCP Gateway Registry codebase. We verify that: +1. The `faiss-cpu` dependency is removed from `pyproject.toml` +2. FAISS-specific code is deleted +3. DocumentDB search repository takes over for all backends +4. Search functionality continues to work correctly +5. Docker builds succeed without FAISS +6. All tests pass with the new configuration + +### Prerequisites +- [ ] Access to the repository: `/home/ubuntu/repos/sample-claude-code-multi-model/benchmarks/swe-benchmark-data/mcp-gateway-registry/repo` +- [ ] Python 3.14+ available via `uv` +- [ ] Docker daemon running (for Docker tests) +- [ ] MongoDB/DocumentDB accessible (for documentdb backend tests) + +### Shared Variables +```bash +export REPO_ROOT="/home/ubuntu/repos/sample-claude-code-multi-model/benchmarks/swe-benchmark-data/mcp-gateway-registry/repo" +export UV_RUN="uv run" +export REGISTRY_PORT=8000 +``` + +--- + +## 1. Functional Tests + +### 1.1 Dependency Verification Tests + +#### Test: Verify faiss-cpu is removed from pyproject.toml +```bash +cd "$REPO_ROOT" + +# Should return exit code 1 (no matches) +if grep -q "faiss-cpu" pyproject.toml; then + echo "FAIL: faiss-cpu still present in pyproject.toml" + exit 1 +else + echo "PASS: faiss-cpu removed from pyproject.toml" +fi +``` + +#### Test: Verify faiss import fails in fresh Python +```bash +cd "$REPO_ROOT" + +# Install dependencies (without faiss-cpu) +uv sync + +# Should raise ModuleNotFoundError +if uv run python -c "import faiss" 2>/dev/null; then + echo "FAIL: faiss module still importable" + exit 1 +else + echo "PASS: faiss module not importable" +fi +``` + +### 1.2 Code Structure Tests + +#### Test: Verify FAISS service module is deleted +```bash +cd "$REPO_ROOT" + +if [ -f "registry/search/service.py" ]; then + echo "FAIL: registry/search/service.py still exists" + exit 1 +else + echo "PASS: registry/search/service.py deleted" +fi + +if [ -f "registry/repositories/file/search_repository.py" ]; then + echo "FAIL: registry/repositories/file/search_repository.py still exists" + exit 1 +else + echo "PASS: registry/repositories/file/search_repository.py deleted" +fi +``` + +#### Test: Verify FaissMetadata model is removed +```bash +cd "$REPO_ROOT" + +if grep -q "class FaissMetadata" registry/core/schemas.py; then + echo "FAIL: FaissMetadata model still exists" + exit 1 +else + echo "PASS: FaissMetadata model removed" +fi + +if grep -q "faiss_index_path" registry/core/config.py; then + echo "FAIL: faiss_index_path property still exists" + exit 1 +else + echo "PASS: faiss_index_path property removed" +fi +``` + +### 1.3 Search Execution Tests + +#### Test: DocumentDB search repository can be instantiated +```bash +cd "$REPO_ROOT" + +uv run python -c " +from registry.repositories.factory import get_search_repository +from registry.core.config import settings + +# Test documentdb backend +settings.storage_backend = 'documentdb' +repo = get_search_repository() +print(f'Search repository type: {type(repo).__name__}') +assert 'DocumentDB' in type(repo).__name__, 'Expected DocumentDB search repository' +print('PASS: DocumentDB search repository created successfully') +" + +# Test file backend (should use DocumentDB search) +settings.storage_backend = 'file' +repo = get_search_repository() +print(f'Search repository type for file backend: {type(repo).__name__}') +assert 'DocumentDB' in type(repo).__name__, 'Expected DocumentDB search repository for file backend' +echo "PASS: File backend uses DocumentDB search" +``` + +#### Test: Search API endpoint works +```bash +# Start registry (with mock database) +cd "$REPO_ROOT" + +# Test with mock - this tests the search code path without real database +uv run python -c " +import asyncio +from registry.repositories.factory import get_search_repository +from registry.core.config import settings + +async def test_search(): + settings.storage_backend = 'file' + repo = get_search_repository() + + # Test that search method exists and is callable + assert hasattr(repo, 'search'), 'Search repository missing search method' + assert callable(repo.search), 'search method is not callable' + + # Verify it returns expected structure + result = await repo.search('test query', entity_types=['mcp_server']) + assert isinstance(result, dict), 'Search should return dict' + assert 'servers' in result, 'Search result missing servers key' + assert 'tools' in result, 'Search result missing tools key' + assert 'agents' in result, 'Search result missing agents key' + + return True + +result = asyncio.run(test_search()) +print('PASS: Search API works correctly') +" +``` + +#### Test: Search returns same structure as before +```bash +cd "$REPO_ROOT" + +uv run python -c " +import asyncio +from registry.repositories.factory import get_search_repository +from registry.core.config import settings + +async def verify_search_structure(): + settings.storage_backend = 'file' + repo = get_search_repository() + + result = await repo.search( + 'test', + entity_types=['mcp_server', 'a2a_agent'], + max_results=5 + ) + + # Verify structure matches expected API contract + expected_keys = {'servers', 'tools', 'agents'} + actual_keys = set(result.keys()) + + assert actual_keys == expected_keys, f'Expected {expected_keys}, got {actual_keys}' + + # Verify each server has expected fields + for server in result['servers']: + assert 'path' in server, 'Server missing path' + assert 'server_name' in server, 'Server missing server_name' + assert 'description' in server, 'Server missing description' + assert 'relevance_score' in server, 'Server missing relevance_score' + + return result + +result = asyncio.run(verify_search_structure()) +print(f'PASS: Search structure verified, {len(result[\"servers\"])} servers returned') +" +``` + +### 1.4 CLI Tests + +#### Test: CLI search command help text (no FAISS references) +```bash +cd "$REPO_ROOT" + +# Check that --help text doesn't mention FAISS +if uv run python -m cli.agent_mgmt --help 2>&1 | grep -i "faiss"; then + echo "FAIL: CLI help still mentions FAISS" + exit 1 +else + echo "PASS: CLI help text updated" +fi +``` + +#### Test: CLI search command runs (with mock) +```bash +cd "$REPO_ROOT" + +# This verifies the CLI can import and use search +uv run python -c " +from registry.repositories.factory import get_search_repository +print('CLI search module imports successfully') +" +``` + +--- + +## 2. Backwards Compatibility Tests + +### 2.1 Search API Compatibility + +#### Test: Search request format unchanged +```bash +cd "$REPO_ROOT" + +uv run python -c " +import asyncio +from registry.repositories.factory import get_search_repository +from registry.core.config import settings + +async def test_backward_compat(): + settings.storage_backend = 'file' + repo = get_search_repository() + + # Test all parameter combinations that were supported before + tests = [ + # Basic search + {'query': 'test'}, + # With entity types + {'query': 'test', 'entity_types': ['mcp_server']}, + # With max results + {'query': 'test', 'max_results': 20}, + # With filters + {'query': 'test', 'max_results': 5, 'include_draft': True}, + ] + + for params in tests: + result = await repo.search(**params) + assert isinstance(result, dict) + + return True + +asyncio.run(test_backward_compat()) +print('PASS: Search API accepts same parameters as before') +" +``` + +#### Test: Search response format unchanged +```bash +cd "$REPO_ROOT" + +uv run python -c " +import asyncio +from registry.repositories.factory import get_search_repository +from registry.core.config import settings + +async def test_response_format(): + settings.storage_backend = 'file' + repo = get_search_repository() + + result = await repo.search('test', max_results=10) + + # Verify response structure + for entity_type in ['servers', 'tools', 'agents']: + assert entity_type in result, f'{entity_type} missing from response' + assert isinstance(result[entity_type], list), f'{entity_type} should be a list' + + # Verify server results have specific fields + for server in result['servers']: + assert 'entity_type' in server, 'Server missing entity_type' + assert 'path' in server, 'Server missing path' + assert 'relevance_score' in server, 'Server missing relevance_score' + + return result + +result = asyncio.run(test_response_format()) +print(f'PASS: Search response format unchanged') +" +``` + +--- + +## 3. UX Tests + +### 3.1 CLI Output Tests + +#### Test: CLI search output format +```bash +cd "$REPO_ROOT" + +# Verify the CLI handles empty search results +uv run python -c " +import asyncio +from registry.repositories.factory import get_search_repository +from registry.core.config import settings + +async def test_empty_search(): + settings.storage_backend = 'file' + repo = get_search_repository() + + result = await repo.search('nonexistent-query-xyz', max_results=5) + + assert result['servers'] == [], 'Empty search should return empty list' + assert result['tools'] == [], 'Empty search should return empty tools list' + assert result['agents'] == [], 'Empty search should return empty agents list' + + return True + +asyncio.run(test_empty_search()) +print('PASS: Empty search returns empty lists') +" +``` + +### 3.2 Error Message Tests + +#### Test: Error messages don't reference FAISS +```bash +cd "$REPO_ROOT" + +# Check source files for FAISS mentions +if grep -ri "faiss" registry/ --include="*.py" | grep -v "test" | grep -v "fixtures"; then + echo "FAIL: FAISS still referenced in source code" + exit 1 +else + echo "PASS: No FAISS references in source code" +fi +``` + +--- + +## 4. Deployment Surface Tests + +### 4.1 Docker Wiring + +#### Test: Docker image builds without FAISS +```bash +cd "$REPO_ROOT" + +# Build the Docker image +docker build -t mcp-registry-test . 2>&1 | tee /tmp/docker-build.log + +# Check build succeeded +if [ ${PIPESTATUS[0]} -ne 0 ]; then + echo "FAIL: Docker build failed" + cat /tmp/docker-build.log + exit 1 +fi + +echo "PASS: Docker image builds successfully" + +# Verify FAISS is not in the image +docker run --rm mcp-registry-test python -c "import faiss" 2>/dev/null +if [ $? -eq 0 ]; then + echo "FAIL: FAISS still present in Docker image" + exit 1 +else + echo "PASS: FAISS not present in Docker image" +fi +``` + +#### Test: Docker container starts +```bash +# Start container with minimal config +docker run -d --name test-registry -p 8000:8000 mcp-registry-test + +# Wait for health check or startup +sleep 10 + +# Check if container is running +if ! docker ps | grep -q test-registry; then + echo "FAIL: Container not running" + docker logs test-registry + docker rm test-registry + exit 1 +fi + +echo "PASS: Container starts successfully" + +# Cleanup +docker stop test-registry +docker rm test-registry +``` + +### 4.2 Docker Compose Files + +#### Test: Docker Compose comments updated +```bash +cd "$REPO_ROOT" + +# Check docker-compose files for FAISS mentions +for file in docker-compose.yml docker-compose.prebuilt.yml docker-compose.podman.yml; do + if grep -q "FAISS" "$file"; then + echo "INFO: $file still has FAISS comment (can be updated separately)" + else + echo "PASS: $file FAISS comment removed" + fi +done +``` + +### 4.3 Terraform Files + +#### Test: Terraform schemas updated +```bash +cd "$REPO_ROOT" + +# Check for FAISS in validation patterns +if grep -q "pattern=\"^(faiss|documentdb)\$" terraform/telemetry-collector/lambda/collector/schemas.py; then + echo "INFO: Schema pattern still includes 'faiss' (can be removed)" +else + echo "PASS: Schema pattern updated" +fi +``` + +#### Test: Terraform documentation updated +```bash +cd "$REPO_ROOT" + +if grep -q "FAISS" terraform/aws-ecs/OPERATIONS.md; then + echo "INFO: terraform/aws-ecs/OPERATIONS.md still mentions FAISS" +else + echo "PASS: Terraform docs updated" +fi +``` + +--- + +## 5. End-to-End API Tests + +### 5.1 Search Workflow Tests + +#### Test: Register server then search +```bash +cd "$REPO_ROOT" + +uv run python -c " +import asyncio +from registry.repositories.factory import get_search_repository, reset_repositories +from registry.core.config import settings + +async def test_full_workflow(): + settings.storage_backend = 'file' + reset_repositories() + + repo = get_search_repository() + + # Simulate indexing a server + server_data = { + 'server_name': 'Test Server', + 'description': 'A test server for search', + 'tags': ['test', 'search'], + 'tool_list': [ + { + 'name': 'test-tool', + 'description': 'A test tool', + 'parsed_description': {'main': 'Test tool description'} + } + ] + } + + # Index the server (this would normally be called by the registry service) + await repo.index_server('/servers/test-server', server_data, True) + + # Search for it + result = await repo.search('test server', max_results=10) + + # Verify search found the indexed server + assert len(result['servers']) > 0, 'Search should find indexed server' + + found = False + for server in result['servers']: + if server['server_name'] == 'Test Server': + found = True + break + + assert found, 'Indexed server should be in search results' + + return result + +result = asyncio.run(test_full_workflow()) +print(f'PASS: Server indexing and search works (found {len(result[\"servers\"])} results)') +" +``` + +#### Test: Search with keyword boost +```bash +cd "$REPO_ROOT" + +uv run python -c " +import asyncio +from registry.repositories.factory import get_search_repository, reset_repositories +from registry.core.config import settings + +async def test_keyword_boost(): + settings.storage_backend = 'file' + reset_repositories() + + repo = get_search_repository() + + # Search for a term that should match via keyword boost + result = await repo.search('test', max_results=5) + + # Should return results even without vector embeddings + # (uses lexical search as fallback) + assert 'servers' in result + assert 'tools' in result + assert 'agents' in result + + return result + +result = asyncio.run(test_keyword_boost()) +print(f'PASS: Keyword search fallback works') +" +``` + +### 5.2 Integration Tests + +#### Test: File backend search integration +```bash +cd "$REPO_ROOT" + +uv run python -c " +import asyncio +from registry.repositories.factory import get_search_repository, reset_repositories +from registry.core.config import settings + +async def test_file_backend_integration(): + settings.storage_backend = 'file' + reset_repositories() + + repo = get_search_repository() + + # Verify we get the right repository type + print(f'Repository type: {type(repo).__name__}') + + # Verify search method works + result = await repo.search('sample query', max_results=3) + print(f'Search result keys: {list(result.keys())}') + + # Verify structure + assert isinstance(result['servers'], list) + assert isinstance(result['tools'], list) + assert isinstance(result['agents'], list) + + return True + +asyncio.run(test_file_backend_integration()) +print('PASS: File backend integration test completed') +" +``` + +--- + +## 6. Test Execution Checklist + +- [ ] Section 1 (Functional) passes + - [ ] 1.1 Dependency verification tests pass + - [ ] 1.2 Code structure tests pass + - [ ] 1.3 Search execution tests pass + - [ ] 1.4 CLI tests pass +- [ ] Section 2 (Backwards Compatibility) verified + - [ ] 2.1 Search API compatibility tests pass + - [ ] 2.2 Search response format tests pass +- [ ] Section 3 (UX) verified + - [ ] 3.1 CLI output tests pass + - [ ] 3.2 Error message tests pass +- [ ] Section 4 (Deployment) verified + - [ ] 4.1 Docker image builds pass + - [ ] 4.2 Docker Compose comments verified + - [ ] 4.3 Terraform schemas updated +- [ ] Section 5 (E2E) verified + - [ ] 5.1 Search workflow tests pass + - [ ] 5.2 Integration tests pass +- [ ] Run full test suite with `uv run pytest tests/` +- [ ] Run security scan with `uv run bandit -r registry/` +- [ ] Run type checking with `uv run mypy registry/` + +--- + +## 7. Known Issues / Notes + +### Metrics-Service +The metrics-service also has FAISS references but is a separate service: +- `metrics-service/app/storage/database.py` +- `metrics-service/app/storage/migrations.py` + +These should be addressed in a separate issue. + +### TestFixtures +Some test fixtures in `tests/fixtures/mocks/mock_faiss.py` may be kept temporarily if other parts of the codebase reference them. If not needed, delete: +- `tests/fixtures/mocks/mock_faiss.py` +- `tests/unit/search/test_faiss_service.py` + +### Docker Build Notes +The Dockerfile may need adjustment if FAISS was installed via specific instructions: +```dockerfile +# Check if FAISS installation steps can be removed +# Look for lines like: +# - RUN pip install faiss-cpu +# - RUN apt-get install faiss +```