diff --git a/.agents/skills/cloud-run-basics b/.agents/skills/cloud-run-basics new file mode 120000 index 0000000..6306873 --- /dev/null +++ b/.agents/skills/cloud-run-basics @@ -0,0 +1 @@ +/Users/alp/.runwaize_skills_cookbook/chef/skills/cloud-run-basics \ No newline at end of file diff --git a/.agents/skills/cloudformation-to-pulumi b/.agents/skills/cloudformation-to-pulumi new file mode 120000 index 0000000..512357f --- /dev/null +++ b/.agents/skills/cloudformation-to-pulumi @@ -0,0 +1 @@ +/Users/alp/.runwaize_skills_cookbook/chef/skills/cloudformation-to-pulumi \ No newline at end of file diff --git a/.agents/skills/google-cloud-networking-observability b/.agents/skills/google-cloud-networking-observability new file mode 120000 index 0000000..6d578bb --- /dev/null +++ b/.agents/skills/google-cloud-networking-observability @@ -0,0 +1 @@ +/Users/alp/.runwaize_skills_cookbook/chef/skills/google-cloud-networking-observability \ No newline at end of file diff --git a/.agents/skills/google-cloud-recipe-auth b/.agents/skills/google-cloud-recipe-auth new file mode 120000 index 0000000..8286fad --- /dev/null +++ b/.agents/skills/google-cloud-recipe-auth @@ -0,0 +1 @@ +/Users/alp/.runwaize_skills_cookbook/chef/skills/google-cloud-recipe-auth \ No newline at end of file diff --git a/.agents/skills/google-cloud-waf-cost-optimization b/.agents/skills/google-cloud-waf-cost-optimization new file mode 120000 index 0000000..75a71f1 --- /dev/null +++ b/.agents/skills/google-cloud-waf-cost-optimization @@ -0,0 +1 @@ +/Users/alp/.runwaize_skills_cookbook/chef/skills/google-cloud-waf-cost-optimization \ No newline at end of file diff --git a/.agents/skills/google-cloud-waf-operational-excellence b/.agents/skills/google-cloud-waf-operational-excellence new file mode 120000 index 0000000..0721c57 --- /dev/null +++ b/.agents/skills/google-cloud-waf-operational-excellence @@ -0,0 +1 @@ +/Users/alp/.runwaize_skills_cookbook/chef/skills/google-cloud-waf-operational-excellence \ No newline at end of file diff --git a/.agents/skills/google-cloud-waf-performance-optimization b/.agents/skills/google-cloud-waf-performance-optimization new file mode 120000 index 0000000..1f0a6a3 --- /dev/null +++ b/.agents/skills/google-cloud-waf-performance-optimization @@ -0,0 +1 @@ +/Users/alp/.runwaize_skills_cookbook/chef/skills/google-cloud-waf-performance-optimization \ No newline at end of file diff --git a/.agents/skills/google-cloud-waf-reliability b/.agents/skills/google-cloud-waf-reliability new file mode 120000 index 0000000..27516e7 --- /dev/null +++ b/.agents/skills/google-cloud-waf-reliability @@ -0,0 +1 @@ +/Users/alp/.runwaize_skills_cookbook/chef/skills/google-cloud-waf-reliability \ No newline at end of file diff --git a/.agents/skills/google-cloud-waf-security b/.agents/skills/google-cloud-waf-security new file mode 120000 index 0000000..85c5302 --- /dev/null +++ b/.agents/skills/google-cloud-waf-security @@ -0,0 +1 @@ +/Users/alp/.runwaize_skills_cookbook/chef/skills/google-cloud-waf-security \ No newline at end of file diff --git a/.agents/skills/package-usage b/.agents/skills/package-usage new file mode 120000 index 0000000..d8ddf8d --- /dev/null +++ b/.agents/skills/package-usage @@ -0,0 +1 @@ +/Users/alp/.runwaize_skills_cookbook/chef/skills/package-usage \ No newline at end of file diff --git a/.agents/skills/provider-upgrade b/.agents/skills/provider-upgrade new file mode 120000 index 0000000..bd17d77 --- /dev/null +++ b/.agents/skills/provider-upgrade @@ -0,0 +1 @@ +/Users/alp/.runwaize_skills_cookbook/chef/skills/provider-upgrade \ No newline at end of file diff --git a/.agents/skills/pulumi-arm-to-pulumi b/.agents/skills/pulumi-arm-to-pulumi new file mode 120000 index 0000000..3042ca8 --- /dev/null +++ b/.agents/skills/pulumi-arm-to-pulumi @@ -0,0 +1 @@ +/Users/alp/.runwaize_skills_cookbook/chef/skills/pulumi-arm-to-pulumi \ No newline at end of file diff --git a/.agents/skills/pulumi-automation-api b/.agents/skills/pulumi-automation-api new file mode 120000 index 0000000..95989f6 --- /dev/null +++ b/.agents/skills/pulumi-automation-api @@ -0,0 +1 @@ +/Users/alp/.runwaize_skills_cookbook/chef/skills/pulumi-automation-api \ No newline at end of file diff --git a/.agents/skills/pulumi-best-practices b/.agents/skills/pulumi-best-practices new file mode 120000 index 0000000..d65a67d --- /dev/null +++ b/.agents/skills/pulumi-best-practices @@ -0,0 +1 @@ +/Users/alp/.runwaize_skills_cookbook/chef/skills/pulumi-best-practices \ No newline at end of file diff --git a/.agents/skills/pulumi-cdk-to-pulumi b/.agents/skills/pulumi-cdk-to-pulumi new file mode 120000 index 0000000..6e17075 --- /dev/null +++ b/.agents/skills/pulumi-cdk-to-pulumi @@ -0,0 +1 @@ +/Users/alp/.runwaize_skills_cookbook/chef/skills/pulumi-cdk-to-pulumi \ No newline at end of file diff --git a/.agents/skills/pulumi-component b/.agents/skills/pulumi-component new file mode 120000 index 0000000..80eba8e --- /dev/null +++ b/.agents/skills/pulumi-component @@ -0,0 +1 @@ +/Users/alp/.runwaize_skills_cookbook/chef/skills/pulumi-component \ No newline at end of file diff --git a/.agents/skills/pulumi-esc b/.agents/skills/pulumi-esc new file mode 120000 index 0000000..6542d7e --- /dev/null +++ b/.agents/skills/pulumi-esc @@ -0,0 +1 @@ +/Users/alp/.runwaize_skills_cookbook/chef/skills/pulumi-esc \ No newline at end of file diff --git a/.agents/skills/pulumi-neo-handoff b/.agents/skills/pulumi-neo-handoff new file mode 120000 index 0000000..a46bc21 --- /dev/null +++ b/.agents/skills/pulumi-neo-handoff @@ -0,0 +1 @@ +/Users/alp/.runwaize_skills_cookbook/chef/skills/pulumi-neo-handoff \ No newline at end of file diff --git a/.agents/skills/pulumi-terraform-to-pulumi b/.agents/skills/pulumi-terraform-to-pulumi new file mode 120000 index 0000000..809537e --- /dev/null +++ b/.agents/skills/pulumi-terraform-to-pulumi @@ -0,0 +1 @@ +/Users/alp/.runwaize_skills_cookbook/chef/skills/pulumi-terraform-to-pulumi \ No newline at end of file diff --git a/.agents/skills/pulumi-upgrade-provider b/.agents/skills/pulumi-upgrade-provider new file mode 120000 index 0000000..7333b27 --- /dev/null +++ b/.agents/skills/pulumi-upgrade-provider @@ -0,0 +1 @@ +/Users/alp/.runwaize_skills_cookbook/chef/skills/pulumi-upgrade-provider \ No newline at end of file diff --git a/.agents/skills/upstream-patches b/.agents/skills/upstream-patches new file mode 120000 index 0000000..96549d8 --- /dev/null +++ b/.agents/skills/upstream-patches @@ -0,0 +1 @@ +/Users/alp/.runwaize_skills_cookbook/chef/skills/upstream-patches \ No newline at end of file diff --git a/.claude/skills/gitnexus/gitnexus-cli/SKILL.md b/.claude/skills/gitnexus/gitnexus-cli/SKILL.md index cd9a83b..989c082 100644 --- a/.claude/skills/gitnexus/gitnexus-cli/SKILL.md +++ b/.claude/skills/gitnexus/gitnexus-cli/SKILL.md @@ -5,14 +5,16 @@ description: "Use when the user needs to run GitNexus CLI commands like analyze/ # GitNexus CLI Commands -All commands work via `npx` — no global install required. +Commands below use `node .gitnexus/run.cjs ` — the project-local runner `gitnexus analyze` drops next to the index. It auto-selects an available runner at call time (global `gitnexus`, else `pnpm dlx`, else `npx`), so no package-manager assumption and no global install is required. + +> **Not analyzed yet, or `node .gitnexus/run.cjs` reports `Cannot find module`** (the gitignored runner is absent — e.g. a fresh clone or `git clean`)? (Re)generate it with `npx gitnexus analyze` from the project root. On **npm 11.x**, if `npx` crashes during install (`node.target is null`), install once with `npm i -g gitnexus` (then `gitnexus analyze`) or use `pnpm --allow-build=@ladybugdb/core --allow-build=gitnexus --allow-build=tree-sitter dlx gitnexus@latest analyze`. See [#1939](https://github.com/abhigyanpatwari/GitNexus/issues/1939). ## Commands ### analyze — Build or refresh the index ```bash -npx gitnexus analyze +node .gitnexus/run.cjs analyze ``` Run from the project root. This parses all source files, builds the knowledge graph, writes it to `.gitnexus/`, and generates CLAUDE.md / AGENTS.md context files. @@ -28,7 +30,7 @@ Run from the project root. This parses all source files, builds the knowledge gr ### status — Check index freshness ```bash -npx gitnexus status +node .gitnexus/run.cjs status ``` Shows whether the current repo has a GitNexus index, when it was last updated, and symbol/relationship counts. Use this to check if re-indexing is needed. @@ -36,7 +38,7 @@ Shows whether the current repo has a GitNexus index, when it was last updated, a ### clean — Delete the index ```bash -npx gitnexus clean +node .gitnexus/run.cjs clean ``` Deletes the `.gitnexus/` directory and unregisters the repo from the global registry. Use before re-indexing if the index is corrupt or after removing GitNexus from a project. @@ -49,7 +51,7 @@ Deletes the `.gitnexus/` directory and unregisters the repo from the global regi ### wiki — Generate documentation from the graph ```bash -npx gitnexus wiki +node .gitnexus/run.cjs wiki ``` Generates repository documentation from the knowledge graph using an LLM. Requires an API key (saved to `~/.gitnexus/config.json` on first use). @@ -66,7 +68,7 @@ Generates repository documentation from the knowledge graph using an LLM. Requir ### list — Show all indexed repos ```bash -npx gitnexus list +node .gitnexus/run.cjs list ``` Lists all repositories registered in `~/.gitnexus/registry.json`. The MCP `list_repos` tool provides the same information. diff --git a/.claude/skills/gitnexus/gitnexus-debugging/SKILL.md b/.claude/skills/gitnexus/gitnexus-debugging/SKILL.md index 9510b97..9834f94 100644 --- a/.claude/skills/gitnexus/gitnexus-debugging/SKILL.md +++ b/.claude/skills/gitnexus/gitnexus-debugging/SKILL.md @@ -16,23 +16,23 @@ description: "Use when the user is debugging a bug, tracing an error, or asking ## Workflow ``` -1. gitnexus_query({query: ""}) → Find related execution flows -2. gitnexus_context({name: ""}) → See callers/callees/processes +1. query({query: ""}) → Find related execution flows +2. context({name: ""}) → See callers/callees/processes 3. READ gitnexus://repo/{name}/process/{name} → Trace execution flow -4. gitnexus_cypher({query: "MATCH path..."}) → Custom traces if needed +4. cypher({query: "MATCH path..."}) → Custom traces if needed ``` -> If "Index is stale" → run `npx gitnexus analyze` in terminal. +> If "Index is stale" → run `node .gitnexus/run.cjs analyze` in terminal. ## Checklist ``` - [ ] Understand the symptom (error message, unexpected behavior) -- [ ] gitnexus_query for error text or related code +- [ ] query for error text or related code - [ ] Identify the suspect function from returned processes -- [ ] gitnexus_context to see callers and callees +- [ ] context to see callers and callees - [ ] Trace execution flow via process resource if applicable -- [ ] gitnexus_cypher for custom call chain traces if needed +- [ ] cypher for custom call chain traces if needed - [ ] Read source files to confirm root cause ``` @@ -40,7 +40,7 @@ description: "Use when the user is debugging a bug, tracing an error, or asking | Symptom | GitNexus Approach | | -------------------- | ---------------------------------------------------------- | -| Error message | `gitnexus_query` for error text → `context` on throw sites | +| Error message | `query` for error text → `context` on throw sites | | Wrong return value | `context` on the function → trace callees for data flow | | Intermittent failure | `context` → look for external calls, async deps | | Performance issue | `context` → find symbols with many callers (hot paths) | @@ -48,24 +48,24 @@ description: "Use when the user is debugging a bug, tracing an error, or asking ## Tools -**gitnexus_query** — find code related to error: +**query** — find code related to error: ``` -gitnexus_query({query: "payment validation error"}) +query({query: "payment validation error"}) → Processes: CheckoutFlow, ErrorHandling → Symbols: validatePayment, handlePaymentError, PaymentException ``` -**gitnexus_context** — full context for a suspect: +**context** — full context for a suspect: ``` -gitnexus_context({name: "validatePayment"}) +context({name: "validatePayment"}) → Incoming calls: processCheckout, webhookHandler → Outgoing calls: verifyCard, fetchRates (external API!) → Processes: CheckoutFlow (step 3/7) ``` -**gitnexus_cypher** — custom call chain traces: +**cypher** — custom call chain traces: ```cypher MATCH path = (a)-[:CodeRelation {type: 'CALLS'}*1..2]->(b:Function {name: "validatePayment"}) @@ -75,11 +75,11 @@ RETURN [n IN nodes(path) | n.name] AS chain ## Example: "Payment endpoint returns 500 intermittently" ``` -1. gitnexus_query({query: "payment error handling"}) +1. query({query: "payment error handling"}) → Processes: CheckoutFlow, ErrorHandling → Symbols: validatePayment, handlePaymentError -2. gitnexus_context({name: "validatePayment"}) +2. context({name: "validatePayment"}) → Outgoing calls: verifyCard, fetchRates (external API!) 3. READ gitnexus://repo/my-app/process/CheckoutFlow diff --git a/.claude/skills/gitnexus/gitnexus-exploring/SKILL.md b/.claude/skills/gitnexus/gitnexus-exploring/SKILL.md index 927a4e4..ccf684c 100644 --- a/.claude/skills/gitnexus/gitnexus-exploring/SKILL.md +++ b/.claude/skills/gitnexus/gitnexus-exploring/SKILL.md @@ -18,20 +18,20 @@ description: "Use when the user asks how code works, wants to understand archite ``` 1. READ gitnexus://repos → Discover indexed repos 2. READ gitnexus://repo/{name}/context → Codebase overview, check staleness -3. gitnexus_query({query: ""}) → Find related execution flows -4. gitnexus_context({name: ""}) → Deep dive on specific symbol +3. query({query: ""}) → Find related execution flows +4. context({name: ""}) → Deep dive on specific symbol 5. READ gitnexus://repo/{name}/process/{name} → Trace full execution flow ``` -> If step 2 says "Index is stale" → run `npx gitnexus analyze` in terminal. +> If step 2 says "Index is stale" → run `node .gitnexus/run.cjs analyze` in terminal. ## Checklist ``` - [ ] READ gitnexus://repo/{name}/context -- [ ] gitnexus_query for the concept you want to understand +- [ ] query for the concept you want to understand - [ ] Review returned processes (execution flows) -- [ ] gitnexus_context on key symbols for callers/callees +- [ ] context on key symbols for callers/callees - [ ] READ process resource for full execution traces - [ ] Read source files for implementation details ``` @@ -47,18 +47,18 @@ description: "Use when the user asks how code works, wants to understand archite ## Tools -**gitnexus_query** — find execution flows related to a concept: +**query** — find execution flows related to a concept: ``` -gitnexus_query({query: "payment processing"}) +query({query: "payment processing"}) → Processes: CheckoutFlow, RefundFlow, WebhookHandler → Symbols grouped by flow with file locations ``` -**gitnexus_context** — 360-degree view of a symbol: +**context** — 360-degree view of a symbol: ``` -gitnexus_context({name: "validateUser"}) +context({name: "validateUser"}) → Incoming calls: loginHandler, apiMiddleware → Outgoing calls: checkToken, getUserById → Processes: LoginFlow (step 2/5), TokenRefresh (step 1/3) @@ -68,10 +68,10 @@ gitnexus_context({name: "validateUser"}) ``` 1. READ gitnexus://repo/my-app/context → 918 symbols, 45 processes -2. gitnexus_query({query: "payment processing"}) +2. query({query: "payment processing"}) → CheckoutFlow: processPayment → validateCard → chargeStripe → RefundFlow: initiateRefund → calculateRefund → processRefund -3. gitnexus_context({name: "processPayment"}) +3. context({name: "processPayment"}) → Incoming: checkoutHandler, webhookHandler → Outgoing: validateCard, chargeStripe, saveTransaction 4. Read src/payments/processor.ts for implementation details diff --git a/.claude/skills/gitnexus/gitnexus-guide/SKILL.md b/.claude/skills/gitnexus/gitnexus-guide/SKILL.md index 937ac73..a543378 100644 --- a/.claude/skills/gitnexus/gitnexus-guide/SKILL.md +++ b/.claude/skills/gitnexus/gitnexus-guide/SKILL.md @@ -15,7 +15,7 @@ For any task involving code understanding, debugging, impact analysis, or refact 2. **Match your task to a skill below** and **read that skill file** 3. **Follow the skill's workflow and checklist** -> If step 1 warns the index is stale, run `npx gitnexus analyze` in the terminal first. +> If step 1 warns the index is stale, run `node .gitnexus/run.cjs analyze` in the terminal first. ## Skills @@ -38,7 +38,38 @@ For any task involving code understanding, debugging, impact analysis, or refact | `detect_changes` | Git-diff impact — what do your current changes affect | | `rename` | Multi-file coordinated rename with confidence-tagged edits | | `cypher` | Raw graph queries (read `gitnexus://repo/{name}/schema` first) | -| `list_repos` | Discover indexed repos | +| `list_repos` | Discover indexed repos (paginated — `limit`/`offset`) | + +### Paginating `list_repos` + +`list_repos` is paginated so a large registry is not truncated by MCP/LLM token limits. It takes optional `limit` (default **50**, max **200**) and `offset`, and returns: + +```jsonc +{ + "repositories": [ + { "name": "...", "path": "...", "indexedAt": "...", "lastCommit": "...", "stats": { } } + ], + "pagination": { + "total": 437, + "limit": 50, + "offset": 0, + "returned": 50, + "hasMore": true, + "nextOffset": 50 + } +} +``` + +To enumerate **every** repository, keep calling with `offset` set to `pagination.nextOffset` until `hasMore` is `false`: + +```text +list_repos {} → repos 1–50, nextOffset 50, hasMore true +list_repos { offset: 50 } → repos 51–100, nextOffset 100, hasMore true +… +list_repos { offset: 400 } → repos 401–437, hasMore false (done) +``` + +Notes: `offset` ≥ `total` returns an empty page (with `total` still reported). Out-of-range or malformed `limit`/`offset` (non-integer, `limit` outside `[1, 200]`, `offset < 0`) are rejected with a clear error — `limit` above the max is rejected, not silently capped. The order is deterministic (lower-cased name, then path), so paging never skips or duplicates an entry while the registry is unchanged. ## Resources Reference diff --git a/.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md b/.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md index e19af28..45eb7ce 100644 --- a/.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md +++ b/.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md @@ -17,22 +17,22 @@ description: "Use when the user wants to know what will break if they change som ## Workflow ``` -1. gitnexus_impact({target: "X", direction: "upstream"}) → What depends on this +1. impact({target: "X", direction: "upstream"}) → What depends on this 2. READ gitnexus://repo/{name}/processes → Check affected execution flows -3. gitnexus_detect_changes() → Map current git changes to affected flows +3. detect_changes() → Map current git changes to affected flows 4. Assess risk and report to user ``` -> If "Index is stale" → run `npx gitnexus analyze` in terminal. +> If "Index is stale" → run `node .gitnexus/run.cjs analyze` in terminal. ## Checklist ``` -- [ ] gitnexus_impact({target, direction: "upstream"}) to find dependents +- [ ] impact({target, direction: "upstream"}) to find dependents - [ ] Review d=1 items first (these WILL BREAK) - [ ] Check high-confidence (>0.8) dependencies - [ ] READ processes to check affected execution flows -- [ ] gitnexus_detect_changes() for pre-commit check +- [ ] detect_changes() for pre-commit check - [ ] Assess risk level and report to user ``` @@ -55,10 +55,10 @@ description: "Use when the user wants to know what will break if they change som ## Tools -**gitnexus_impact** — the primary tool for symbol blast radius: +**impact** — the primary tool for symbol blast radius: ``` -gitnexus_impact({ +impact({ target: "validateUser", direction: "upstream", minConfidence: 0.8, @@ -73,10 +73,10 @@ gitnexus_impact({ - authRouter (src/routes/auth.ts:22) [CALLS, 95%] ``` -**gitnexus_detect_changes** — git-diff based impact analysis: +**detect_changes** — git-diff based impact analysis: ``` -gitnexus_detect_changes({scope: "staged"}) +detect_changes({scope: "staged"}) → Changed: 5 symbols in 3 files → Affected: LoginFlow, TokenRefresh, APIMiddlewarePipeline @@ -86,7 +86,7 @@ gitnexus_detect_changes({scope: "staged"}) ## Example: "What breaks if I change validateUser?" ``` -1. gitnexus_impact({target: "validateUser", direction: "upstream"}) +1. impact({target: "validateUser", direction: "upstream"}) → d=1: loginHandler, apiMiddleware (WILL BREAK) → d=2: authRouter, sessionManager (LIKELY AFFECTED) diff --git a/.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md b/.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md index f48cc01..e13c04e 100644 --- a/.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md +++ b/.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md @@ -16,78 +16,78 @@ description: "Use when the user wants to rename, extract, split, move, or restru ## Workflow ``` -1. gitnexus_impact({target: "X", direction: "upstream"}) → Map all dependents -2. gitnexus_query({query: "X"}) → Find execution flows involving X -3. gitnexus_context({name: "X"}) → See all incoming/outgoing refs +1. impact({target: "X", direction: "upstream"}) → Map all dependents +2. query({query: "X"}) → Find execution flows involving X +3. context({name: "X"}) → See all incoming/outgoing refs 4. Plan update order: interfaces → implementations → callers → tests ``` -> If "Index is stale" → run `npx gitnexus analyze` in terminal. +> If "Index is stale" → run `node .gitnexus/run.cjs analyze` in terminal. ## Checklists ### Rename Symbol ``` -- [ ] gitnexus_rename({symbol_name: "oldName", new_name: "newName", dry_run: true}) — preview all edits +- [ ] rename({symbol_name: "oldName", new_name: "newName", dry_run: true}) — preview all edits - [ ] Review graph edits (high confidence) and ast_search edits (review carefully) -- [ ] If satisfied: gitnexus_rename({..., dry_run: false}) — apply edits -- [ ] gitnexus_detect_changes() — verify only expected files changed +- [ ] If satisfied: rename({..., dry_run: false}) — apply edits +- [ ] detect_changes() — verify only expected files changed - [ ] Run tests for affected processes ``` ### Extract Module ``` -- [ ] gitnexus_context({name: target}) — see all incoming/outgoing refs -- [ ] gitnexus_impact({target, direction: "upstream"}) — find all external callers +- [ ] context({name: target}) — see all incoming/outgoing refs +- [ ] impact({target, direction: "upstream"}) — find all external callers - [ ] Define new module interface - [ ] Extract code, update imports -- [ ] gitnexus_detect_changes() — verify affected scope +- [ ] detect_changes() — verify affected scope - [ ] Run tests for affected processes ``` ### Split Function/Service ``` -- [ ] gitnexus_context({name: target}) — understand all callees +- [ ] context({name: target}) — understand all callees - [ ] Group callees by responsibility -- [ ] gitnexus_impact({target, direction: "upstream"}) — map callers to update +- [ ] impact({target, direction: "upstream"}) — map callers to update - [ ] Create new functions/services - [ ] Update callers -- [ ] gitnexus_detect_changes() — verify affected scope +- [ ] detect_changes() — verify affected scope - [ ] Run tests for affected processes ``` ## Tools -**gitnexus_rename** — automated multi-file rename: +**rename** — automated multi-file rename: ``` -gitnexus_rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: true}) +rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: true}) → 12 edits across 8 files → 10 graph edits (high confidence), 2 ast_search edits (review) → Changes: [{file_path, edits: [{line, old_text, new_text, confidence}]}] ``` -**gitnexus_impact** — map all dependents first: +**impact** — map all dependents first: ``` -gitnexus_impact({target: "validateUser", direction: "upstream"}) +impact({target: "validateUser", direction: "upstream"}) → d=1: loginHandler, apiMiddleware, testUtils → Affected Processes: LoginFlow, TokenRefresh ``` -**gitnexus_detect_changes** — verify your changes after refactoring: +**detect_changes** — verify your changes after refactoring: ``` -gitnexus_detect_changes({scope: "all"}) +detect_changes({scope: "all"}) → Changed: 8 files, 12 symbols → Affected processes: LoginFlow, TokenRefresh → Risk: MEDIUM ``` -**gitnexus_cypher** — custom reference queries: +**cypher** — custom reference queries: ```cypher MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(f:Function {name: "validateUser"}) @@ -98,24 +98,24 @@ RETURN caller.name, caller.filePath ORDER BY caller.filePath | Risk Factor | Mitigation | | ------------------- | ----------------------------------------- | -| Many callers (>5) | Use gitnexus_rename for automated updates | +| Many callers (>5) | Use rename for automated updates | | Cross-area refs | Use detect_changes after to verify scope | -| String/dynamic refs | gitnexus_query to find them | +| String/dynamic refs | query to find them | | External/public API | Version and deprecate properly | ## Example: Rename `validateUser` to `authenticateUser` ``` -1. gitnexus_rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: true}) +1. rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: true}) → 12 edits: 10 graph (safe), 2 ast_search (review) → Files: validator.ts, login.ts, middleware.ts, config.json... 2. Review ast_search edits (config.json: dynamic reference!) -3. gitnexus_rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: false}) +3. rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: false}) → Applied 12 edits across 8 files -4. gitnexus_detect_changes({scope: "all"}) +4. detect_changes({scope: "all"}) → Affected: LoginFlow, TokenRefresh → Risk: MEDIUM — run tests for these flows ``` diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 199b243..b545205 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -29,7 +29,7 @@ jobs: contents: write # required for committing version bump steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} @@ -40,7 +40,7 @@ jobs: python-version: "3.13" - name: Install uv - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 with: enable-cache: true diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3bc2e40..93b9d75 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,7 +15,7 @@ jobs: id-token: write # required for trusted publishing if using PyPI's OIDC steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Set up Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 781d29a..58eeae0 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -14,11 +14,11 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: "3.13" - - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 - name: Install dependencies run: uv sync --frozen --extra dev - name: Run pre-commit hooks @@ -43,13 +43,13 @@ jobs: python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: ${{ matrix.python-version }} - name: Install uv - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 with: # Avoid parallel matrix jobs racing on the same Actions cache reservation cache-suffix: py-${{ matrix.python-version }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dc8b1e1..cecadb9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,12 +13,12 @@ jobs: contents: write steps: - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: fetch-depth: 0 # Required for generating release notes - name: Create Release - uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 + uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1 with: generate_release_notes: true draft: false diff --git a/.github/workflows/trufflehog-full-scan.yml b/.github/workflows/trufflehog-full-scan.yml index 1cc69d8..1aea952 100644 --- a/.github/workflows/trufflehog-full-scan.yml +++ b/.github/workflows/trufflehog-full-scan.yml @@ -18,12 +18,12 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: fetch-depth: 0 # Full git history for complete scanning - name: Full Repository Scan if: inputs.scan_type == 'full-repo' - uses: trufflesecurity/trufflehog@37b77001d0174ebec2fcca2bd83ff83a6d45a3ab # v3.95.3 + uses: trufflesecurity/trufflehog@30d5bb91af1a771378349dbbb0c82129392acf70 # v3.95.6 with: extra_args: --results=verified,unknown diff --git a/.github/workflows/trufflehog-security.yml b/.github/workflows/trufflehog-security.yml index e3840a9..738e62a 100644 --- a/.github/workflows/trufflehog-security.yml +++ b/.github/workflows/trufflehog-security.yml @@ -16,11 +16,11 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: fetch-depth: 0 # Required for TruffleHog to scan git history - name: Secret Scanning - uses: trufflesecurity/trufflehog@37b77001d0174ebec2fcca2bd83ff83a6d45a3ab # v3.95.3 + uses: trufflesecurity/trufflehog@30d5bb91af1a771378349dbbb0c82129392acf70 # v3.95.6 with: extra_args: --results=verified,unknown diff --git a/AGENTS.md b/AGENTS.md index 79b9d25..b0c81ae 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -83,24 +83,24 @@ Ask. Refusing to act is always safer than taking an action that bypasses these r # GitNexus — Code Intelligence -This project is indexed by GitNexus as **supervaizer** (6225 symbols, 11375 relationships, 276 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. +This project is indexed by GitNexus as **supervaizer** (6281 symbols, 11689 relationships, 286 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. -> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. +> Index stale? Run `node .gitnexus/run.cjs analyze` from the project root — it auto-selects an available runner. No `.gitnexus/run.cjs` yet? `npx gitnexus analyze` (npm 11 crash → `npm i -g gitnexus`; #1939). ## Always Do -- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `gitnexus_impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user. -- **MUST run `gitnexus_detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows. +- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user. +- **MUST run `detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows. For regression review, compare against the default branch: `detect_changes({scope: "compare", base_ref: "main"})`. - **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits. -- When exploring unfamiliar code, use `gitnexus_query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance. -- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `gitnexus_context({name: "symbolName"})`. +- When exploring unfamiliar code, use `query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance. +- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `context({name: "symbolName"})`. ## Never Do -- NEVER edit a function, class, or method without first running `gitnexus_impact` on it. +- NEVER edit a function, class, or method without first running `impact` on it. - NEVER ignore HIGH or CRITICAL risk warnings from impact analysis. -- NEVER rename symbols with find-and-replace — use `gitnexus_rename` which understands the call graph. -- NEVER commit changes without running `gitnexus_detect_changes()` to check affected scope. +- NEVER rename symbols with find-and-replace — use `rename` which understands the call graph. +- NEVER commit changes without running `detect_changes()` to check affected scope. ## Resources @@ -113,7 +113,7 @@ This project is indexed by GitNexus as **supervaizer** (6225 symbols, 11375 rela ## Cross-Repo Groups -This repository is listed under GitNexus **group(s): runwaize** (see `~/.gitnexus/groups/`). For cross-repo analysis, use MCP tools `impact`, `query`, and `context` with `repo` set to `@` or `@/` (paths match keys in that group’s `group.yaml`). Use `group_list` / `group_sync` for membership and sync. From the terminal: `npx gitnexus group list`, `npx gitnexus group sync `, `npx gitnexus group impact --target --repo `. +This repository is listed under GitNexus **group(s): runwaize** (see `~/.gitnexus/groups/`). For cross-repo analysis, use MCP tools `impact`, `query`, and `context` with `repo` set to `@` or `@/` (paths match keys in that group’s `group.yaml`). Use `group_list` / `group_sync` for membership and sync. From the project root: `node .gitnexus/run.cjs group list`, `node .gitnexus/run.cjs group sync `, `node .gitnexus/run.cjs group impact --target --repo ` (the `.gitnexus/run.cjs` path is repo-root-relative). ## CLI diff --git a/docs/2026_05_SUPERVAIZER_v2.md b/docs/2026_05_SUPERVAIZER_v2.md index 7a09daa..670294e 100644 --- a/docs/2026_05_SUPERVAIZER_v2.md +++ b/docs/2026_05_SUPERVAIZER_v2.md @@ -375,12 +375,19 @@ Common action IDs: | `job.stop` | Stop or cancel agent work. | | `job.sync` | Return a convergent state snapshot for Studio. | | `step.awaiting.submit` | Submit operator input for a HITL step. | +| `context.assign` | Assign a frozen Studio context selection to a job; agent fetches content via `ContextClient` and stores provenance (`ref`, `version`, `hash`, `synced_at`). | | `resource..` | Run a resource operation. | | `dataset..query` | Query an agent-owned dataset. | | `artifact.get` | Load artifact content by reference. | Action requests include `actor`, `workspace`, `mission_id`, `agent_slug`, `surface`, `action`, `input`, and optional correlation fields such as `job_id`, `case_id`, `step_id`, `draft_session_id`, and `idempotency_key`. +### Context assignment semantics + +`context.assign` carries a `V2ContextAssignment` payload: the selected items (`ref`, `version`, `scope`, `title`), the `job_id`, an optional `mission_id`, and a Studio-stamped `assigned_at`. An empty `items` list is an explicit "no context" assignment, not an error. + +The payload intentionally contains references, not content. On receipt the agent fetches each item once through `ContextClient.open()` and freezes the returned content as its own snapshot with provenance (`ref`, `version`, content `hash`, `synced_at`). `ContextClient.open()` returns the item's current `version`; the agent MUST compare it against the assigned `version` and fail the whole assignment with an explicit error when they differ — the item changed between selection and sync, and the operator should re-assign. Agents must not silently store content under a version it does not match, and must not re-read Studio context during job execution; a new `context.assign` is the only refresh path. + ## Sync and Offline Semantics Studio owns operator-facing job lifecycle, but the agent owns actual business state. `job.sync` is the reconciliation point between the two. diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index e64bd25..c27c284 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -12,6 +12,22 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added + +- **`context.assign` contract** — New typed wire contract for assigning a frozen Studio context selection to a job: `V2ContextAssignment` (items, `mission_id`, `job_id`, `assigned_at`), `V2ContextAssignmentItem` (`ref`, `version`, `scope`, `title`), and the `V2_ACTION_CONTEXT_ASSIGN` action id in `supervaizer.contracts`, exported at package level. Additive only; dispatch uses the existing generic `Server.v2_action` machinery. + +### Tests + +- `tests/test_common.py` — structured JSON log output for API access-denial records +- `just test` + +| Status | Count | +| ---------- | ----- | +| ✅ Passed | 677 | +| 🤔 Skipped | 0 | +| 🔴 Failed | 0 | +| ⏱️ in | 65s | + ## [1.2.0] - 2026-05-27 ### Changed @@ -35,10 +51,10 @@ All notable changes to this project will be documented in this file. | Status | Count | | ---------- | ----- | -| ✅ Passed | 663 | +| ✅ Passed | 672 | | 🤔 Skipped | 0 | | 🔴 Failed | 0 | -| ⏱️ in | 136s | +| ⏱️ in | 86s | ## [1.1.1] - 2026-05-20 diff --git a/src/supervaizer/__init__.py b/src/supervaizer/__init__.py index a293f09..115f12b 100644 --- a/src/supervaizer/__init__.py +++ b/src/supervaizer/__init__.py @@ -35,6 +35,13 @@ "CaseNodeType": ("supervaizer.case", "CaseNodeType"), "CaseNodeUpdate": ("supervaizer.case", "CaseNodeUpdate"), "Cases": ("supervaizer.case", "Cases"), + "context": ("supervaizer.context", None), + "ContextCitation": ("supervaizer.context", "ContextCitation"), + "ContextClient": ("supervaizer.context", "ContextClient"), + "ContextOpenResponse": ("supervaizer.context", "ContextOpenResponse"), + "ContextSearchResponse": ("supervaizer.context", "ContextSearchResponse"), + "ContextSearchResult": ("supervaizer.context", "ContextSearchResult"), + "ContextScope": ("supervaizer.context", "ContextScope"), "DataResource": ("supervaizer.data_resource", "DataResource"), "DataResourceContext": ("supervaizer.data_resource", "DataResourceContext"), "DataResourceField": ("supervaizer.data_resource", "DataResourceField"), @@ -96,6 +103,10 @@ "supervaizer.contracts", "WORKSPACE_BINDING_OPTIONS_ACTION", ), + "V2_ACTION_CONTEXT_ASSIGN": ( + "supervaizer.contracts", + "V2_ACTION_CONTEXT_ASSIGN", + ), "AgentMethodContract": ("supervaizer.contracts", "AgentMethodContract"), "AgentMethodsContract": ("supervaizer.contracts", "AgentMethodsContract"), "AgentRegistrationContract": ("supervaizer.contracts", "AgentRegistrationContract"), @@ -143,6 +154,11 @@ "V2AwaitingState": ("supervaizer.contracts", "V2AwaitingState"), "V2CaseLaneDefinition": ("supervaizer.contracts", "V2CaseLaneDefinition"), "V2CaseSnapshot": ("supervaizer.contracts", "V2CaseSnapshot"), + "V2ContextAssignment": ("supervaizer.contracts", "V2ContextAssignment"), + "V2ContextAssignmentItem": ( + "supervaizer.contracts", + "V2ContextAssignmentItem", + ), "V2DashboardDefinition": ("supervaizer.contracts", "V2DashboardDefinition"), "V2DashboardWidgetDataRef": ( "supervaizer.contracts", diff --git a/src/supervaizer/account.py b/src/supervaizer/account.py index 4afe77f..4120baf 100644 --- a/src/supervaizer/account.py +++ b/src/supervaizer/account.py @@ -18,6 +18,7 @@ from supervaizer.__version__ import VERSION from supervaizer.common import ApiError, ApiResult, ApiSuccess, SvBaseModel +from supervaizer.context import ContextClient from supervaizer.telemetry import Telemetry if TYPE_CHECKING: @@ -128,6 +129,11 @@ def api_headers(self) -> dict[str, str]: "workspace": self.workspace_id, } + @property + def context(self) -> ContextClient: + """Client for Supervaize-managed runtime context.""" + return ContextClient(self) + @property def api_url_team(self) -> str: """URL for the Supervaize workspace team.""" diff --git a/src/supervaizer/context.py b/src/supervaizer/context.py new file mode 100644 index 0000000..f332e22 --- /dev/null +++ b/src/supervaizer/context.py @@ -0,0 +1,172 @@ +# Copyright (c) 2024-2026 Alain Prasquier - Supervaize.com. All rights reserved. +# +# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +# If a copy of the MPL was not distributed with this file, you can obtain one at +# https://mozilla.org/MPL/2.0/. + +from __future__ import annotations + +import atexit +import os +from typing import TYPE_CHECKING, Any, Literal + +import httpx +from pydantic import Field + +from supervaizer.common import SvBaseModel + +if TYPE_CHECKING: + from supervaizer.account import Account + +ContextScope = Literal["workspace", "mission"] + +_sync_httpx_transport = httpx.HTTPTransport( + retries=int(os.getenv("SUPERVAIZE_HTTP_MAX_RETRIES", 2)) +) +_sync_httpx_client = httpx.Client(transport=_sync_httpx_transport) + + +class ContextCitation(SvBaseModel): + ref: str + title: str + source_type: str + version: int + + +class ContextSearchResult(SvBaseModel): + ref: str + title: str + scope: ContextScope + source_type: str + version: int + tags: list[str] = Field(default_factory=list) + excerpt: str = "" + score: int = 0 + citation: ContextCitation | None = None + + +class ContextSearchResponse(SvBaseModel): + query: str + results: list[ContextSearchResult] = Field(default_factory=list) + + +class ContextOpenResponse(SvBaseModel): + ref: str + title: str + scope: ContextScope + source_type: str + version: int + instructions: str = "" + tags: list[str] = Field(default_factory=list) + content: str + citations: list[ContextCitation] = Field(default_factory=list) + + +class ContextClient: + def __init__(self, account: Account) -> None: + self.account: Account = account + + def search( + self, + *, + query: str, + mission_id: str | None = None, + scope: ContextScope | None = None, + tags: list[str] | None = None, + limit: int = 5, + expected_workspace_id: str | None = None, + ) -> ContextSearchResponse: + self._reject_conflicting_workspace(expected_workspace_id) + payload: dict[str, Any] = {"query": query, "limit": limit} + self._add_mission_id(payload, mission_id) + if scope: + payload["scope"] = scope + if tags: + payload["tags"] = tags + response = _sync_httpx_client.post( + self._url("search"), headers=self.account.api_headers, json=payload + ) + response.raise_for_status() + return ContextSearchResponse.model_validate(response.json()) + + def open( + self, + *, + ref: str, + mission_id: str | None = None, + query: str | None = None, + max_chars: int = 3000, + expected_workspace_id: str | None = None, + ) -> ContextOpenResponse: + self._reject_conflicting_workspace(expected_workspace_id) + payload: dict[str, Any] = {"ref": ref, "max_chars": max_chars} + self._add_mission_id(payload, mission_id) + if query: + payload["query"] = query + response = _sync_httpx_client.post( + self._url("open"), headers=self.account.api_headers, json=payload + ) + response.raise_for_status() + return ContextOpenResponse.model_validate(response.json()) + + def _url(self, action: str) -> str: + return f"{self.account.api_url_w_v1}/context/{action}/" + + def _reject_conflicting_workspace(self, expected_workspace_id: str | None) -> None: + if expected_workspace_id and expected_workspace_id != self.account.workspace_id: + raise ValueError( + f"expected_workspace_id {expected_workspace_id!r} does not match " + f"account.workspace_id {self.account.workspace_id!r}" + ) + + def _add_mission_id(self, payload: dict[str, Any], mission_id: str | None) -> None: + if mission_id is None: + return + if not mission_id.strip(): + raise ValueError("mission_id must be a non-empty string when provided") + payload["mission_id"] = mission_id + + +def search( + *, + account: Account, + query: str, + mission_id: str | None = None, + scope: ContextScope | None = None, + tags: list[str] | None = None, + limit: int = 5, + expected_workspace_id: str | None = None, +) -> ContextSearchResponse: + return ContextClient(account).search( + query=query, + mission_id=mission_id, + scope=scope, + tags=tags, + limit=limit, + expected_workspace_id=expected_workspace_id, + ) + + +def open( + *, + account: Account, + ref: str, + mission_id: str | None = None, + query: str | None = None, + max_chars: int = 3000, + expected_workspace_id: str | None = None, +) -> ContextOpenResponse: + return ContextClient(account).open( + ref=ref, + mission_id=mission_id, + query=query, + max_chars=max_chars, + expected_workspace_id=expected_workspace_id, + ) + + +def close_httpx_client_sync() -> None: + _sync_httpx_client.close() + + +atexit.register(close_httpx_client_sync) diff --git a/src/supervaizer/contracts.py b/src/supervaizer/contracts.py index 8c821a5..08827a5 100644 --- a/src/supervaizer/contracts.py +++ b/src/supervaizer/contracts.py @@ -804,6 +804,34 @@ class V2ActionRequest(ContractModel): workspace_authorization: V2VerifiedWorkspaceContext | None = None +V2_ACTION_CONTEXT_ASSIGN = "context.assign" + + +class V2ContextAssignmentItem(ContractModel): + ref: str # Studio ContextItem ref, e.g. "supervaize.context.mission.january-brief" + version: int # ContextItem version at selection time + scope: Literal["workspace", "mission"] + title: str + + +class V2ContextAssignment(ContractModel): + items: list[V2ContextAssignmentItem] # empty list = explicit "no context" + mission_id: str | None = None + job_id: str + assigned_at: str # ISO-8601, stamped by Studio + + @model_validator(mode="after") + def _require_mission_id_for_mission_items(self) -> "V2ContextAssignment": + if ( + any(item.scope == "mission" for item in self.items) + and not (self.mission_id or "").strip() + ): + raise ValueError( + "mission_id is required when the assignment contains mission-scoped items" + ) + return self + + class V2SurfaceRequest(ContractModel): request_id: str actor: V2ActorContext diff --git a/tests/test_context_client.py b/tests/test_context_client.py new file mode 100644 index 0000000..2cc9007 --- /dev/null +++ b/tests/test_context_client.py @@ -0,0 +1,162 @@ +# Copyright (c) 2024-2026 Alain Prasquier - Supervaize.com. All rights reserved. +# +# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +# If a copy of the MPL was not distributed with this file, you can obtain one at +# https://mozilla.org/MPL/2.0/. + +from __future__ import annotations + +from typing import Any + +import pytest + +from supervaizer import ( + Account, + ContextClient, + ContextOpenResponse, + ContextSearchResponse, + context, +) + + +class _Response: + def __init__(self, data: dict[str, Any]) -> None: + self._data: dict[str, Any] = data + + def raise_for_status(self) -> None: + return None + + def json(self) -> dict[str, Any]: + return self._data + + +def test_context_search_posts_to_workspace_context_search( + account_fixture: Account, mocker: Any +) -> None: + post = mocker.patch( + "supervaizer.context._sync_httpx_client.post", + return_value=_Response({ + "query": "billing", + "results": [ + { + "ref": "supervaize.context.workspace.billing", + "title": "Billing", + "scope": "workspace", + "source_type": "text", + "version": 1, + "tags": ["ops"], + "excerpt": "Billing policy", + "score": 3, + "citation": { + "ref": "supervaize.context.workspace.billing", + "title": "Billing", + "source_type": "text", + "version": 1, + }, + } + ], + }), + ) + + result = context.search( + account=account_fixture, + query="billing", + mission_id="mission-1", + scope="mission", + tags=["ops"], + limit=7, + ) + + assert isinstance(result, ContextSearchResponse) + assert result.results[0].ref == "supervaize.context.workspace.billing" + post.assert_called_once_with( + f"{account_fixture.api_url_w_v1}/context/search/", + headers=account_fixture.api_headers, + json={ + "query": "billing", + "limit": 7, + "mission_id": "mission-1", + "scope": "mission", + "tags": ["ops"], + }, + ) + + +def test_context_open_posts_to_workspace_context_open( + account_fixture: Account, mocker: Any +) -> None: + post = mocker.patch( + "supervaizer.context._sync_httpx_client.post", + return_value=_Response({ + "ref": "supervaize.context.workspace.billing", + "title": "Billing", + "scope": "workspace", + "source_type": "text", + "version": 2, + "instructions": "Use for policy answers.", + "tags": ["ops"], + "content": "Billing policy", + "citations": [ + { + "ref": "supervaize.context.workspace.billing", + "title": "Billing", + "source_type": "text", + "version": 2, + } + ], + }), + ) + + result = account_fixture.context.open( + ref="supervaize.context.workspace.billing", query="policy", max_chars=1000 + ) + + assert isinstance(result, ContextOpenResponse) + assert result.content == "Billing policy" + post.assert_called_once_with( + f"{account_fixture.api_url_w_v1}/context/open/", + headers=account_fixture.api_headers, + json={ + "ref": "supervaize.context.workspace.billing", + "max_chars": 1000, + "query": "policy", + }, + ) + + +def test_context_rejects_conflicting_workspace_id( + account_fixture: Account, mocker: Any +) -> None: + post = mocker.patch("supervaizer.context._sync_httpx_client.post") + client = ContextClient(account_fixture) + + with pytest.raises( + ValueError, match="expected_workspace_id .* does not match account.workspace_id" + ): + client.search(query="x", expected_workspace_id="other-workspace") + + post.assert_not_called() + + +def test_context_search_rejects_empty_mission_id( + account_fixture: Account, mocker: Any +) -> None: + post = mocker.patch("supervaizer.context._sync_httpx_client.post") + + with pytest.raises(ValueError, match="mission_id must be a non-empty string"): + context.search(account=account_fixture, query="billing", mission_id="") + + post.assert_not_called() + + +def test_context_open_rejects_empty_mission_id( + account_fixture: Account, mocker: Any +) -> None: + post = mocker.patch("supervaizer.context._sync_httpx_client.post") + + with pytest.raises(ValueError, match="mission_id must be a non-empty string"): + account_fixture.context.open( + ref="supervaize.context.workspace.billing", mission_id=" " + ) + + post.assert_not_called() diff --git a/tests/test_contracts.py b/tests/test_contracts.py index 5cbe2d2..844321b 100644 --- a/tests/test_contracts.py +++ b/tests/test_contracts.py @@ -32,6 +32,8 @@ V2AgentMethods, V2AwaitingState, V2CaseSnapshot, + V2ContextAssignment, + V2ContextAssignmentItem, V2DashboardWidgetDataRef, V2DashboardWidgetDefinition, V2DashboardWidgetVisualization, @@ -45,6 +47,7 @@ V2VerifiedWorkspaceContext, V2WorkspaceAuthorizationSettings, V2WorkspaceBindingDefinition, + V2_ACTION_CONTEXT_ASSIGN, build_data_resource_context_headers, build_v2_agent_registration, controller_contract_info, @@ -730,3 +733,88 @@ def test_v2_contract_models_are_public_sdk_exports() -> None: ) assert supervaizer.V2WorkspaceBindingDefinition is V2WorkspaceBindingDefinition assert supervaizer.build_v2_agent_registration is build_v2_agent_registration + + +def test_v2_context_assignment_parses_items() -> None: + assignment = V2ContextAssignment( + items=[ + V2ContextAssignmentItem( + ref="supervaize.context.mission.january-brief", + version=3, + scope="mission", + title="January brief", + ) + ], + mission_id="mis_1", + job_id="job_1", + assigned_at="2026-07-02T09:00:00+00:00", + ) + assert assignment.items[0].version == 3 + assert V2_ACTION_CONTEXT_ASSIGN == "context.assign" + + +def test_v2_context_assignment_allows_empty_items() -> None: + assignment = V2ContextAssignment( + items=[], job_id="job_1", assigned_at="2026-07-02T09:00:00+00:00" + ) + assert assignment.items == [] + assert assignment.mission_id is None + + +def test_v2_context_assignment_requires_mission_id_for_mission_items() -> None: + with pytest.raises(ValidationError, match="mission_id is required"): + V2ContextAssignment( + items=[ + V2ContextAssignmentItem( + ref="supervaize.context.mission.january-brief", + version=3, + scope="mission", + title="January brief", + ) + ], + job_id="job_1", + assigned_at="2026-07-02T09:00:00+00:00", + ) + + +def test_v2_context_assignment_rejects_blank_mission_id_for_mission_items() -> None: + with pytest.raises(ValidationError, match="mission_id is required"): + V2ContextAssignment( + items=[ + V2ContextAssignmentItem( + ref="supervaize.context.mission.january-brief", + version=3, + scope="mission", + title="January brief", + ) + ], + mission_id=" ", + job_id="job_1", + assigned_at="2026-07-02T09:00:00+00:00", + ) + + +def test_v2_context_assignment_workspace_items_do_not_require_mission_id() -> None: + assignment = V2ContextAssignment( + items=[ + V2ContextAssignmentItem( + ref="supervaize.context.workspace.brand-voice", + version=1, + scope="workspace", + title="Brand voice", + ) + ], + job_id="job_1", + assigned_at="2026-07-02T09:00:00+00:00", + ) + assert assignment.mission_id is None + + +def test_v2_context_assignment_item_rejects_unknown_scope() -> None: + with pytest.raises(ValidationError): + V2ContextAssignmentItem( + ref="supervaize.context.mission.january-brief", + version=3, + scope="missions", + title="January brief", + ) diff --git a/uv.lock b/uv.lock index cd5f062..4552fed 100644 --- a/uv.lock +++ b/uv.lock @@ -406,55 +406,55 @@ wheels = [ [[package]] name = "cryptography" -version = "48.0.0" +version = "48.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/a9/db8f313fdcd85d767d4973515e1db101f9c71f95fced83233de224673757/cryptography-48.0.0.tar.gz", hash = "sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920", size = 832984, upload-time = "2026-05-04T22:59:38.133Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/3d/01f6dd9190170a5a241e0e98c2d04be3664a9e6f5b9b872cde63aff1c3dd/cryptography-48.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:0c558d2cdffd8f4bbb30fc7134c74d2ca9a476f830bb053074498fbc86f41ed6", size = 8001587, upload-time = "2026-05-04T22:57:36.803Z" }, - { url = "https://files.pythonhosted.org/packages/b2/6e/e90527eef33f309beb811cf7c982c3aeffcce8e3edb178baa4ca3ae4a6fa/cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c", size = 4690433, upload-time = "2026-05-04T22:57:40.373Z" }, - { url = "https://files.pythonhosted.org/packages/90/04/673510ed51ddff56575f306cf1617d80411ee76831ccd3097599140efdfe/cryptography-48.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7995ef305d7165c3f11ae07f2517e5a4f1d5c18da1376a0a9ed496336b69e5f3", size = 4710620, upload-time = "2026-05-04T22:57:42.935Z" }, - { url = "https://files.pythonhosted.org/packages/14/d5/e9c4ef932c8d800490c34d8bd589d64a31d5890e27ec9e9ad532be893294/cryptography-48.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:40ba1f85eaa6959837b1d51c9767e230e14612eea4ef110ee8854ada22da1bf5", size = 4696283, upload-time = "2026-05-04T22:57:45.294Z" }, - { url = "https://files.pythonhosted.org/packages/0c/29/174b9dfb60b12d59ecfc6cfa04bc88c21b42a54f01b8aae09bb6e51e4c7f/cryptography-48.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:369a6348999f94bbd53435c894377b20ab95f25a9065c283570e70150d8abc3c", size = 5296573, upload-time = "2026-05-04T22:57:47.933Z" }, - { url = "https://files.pythonhosted.org/packages/95/38/0d29a6fd7d0d1373f0c0c88a04ba20e359b257753ac497564cd660fc1d55/cryptography-48.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a0e692c683f4df67815a2d258b324e66f4738bd7a96a218c826dce4f4bd05d8f", size = 4743677, upload-time = "2026-05-04T22:57:50.067Z" }, - { url = "https://files.pythonhosted.org/packages/30/be/eef653013d5c63b6a490529e0316f9ac14a37602965d4903efed1399f32b/cryptography-48.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:18349bbc56f4743c8b12dc32e2bccb2cf83ee8b69a3bba74ef8ae857e26b3d25", size = 4330808, upload-time = "2026-05-04T22:57:52.301Z" }, - { url = "https://files.pythonhosted.org/packages/84/9e/500463e87abb7a0a0f9f256ec21123ecde0a7b5541a15e840ea54551fd81/cryptography-48.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e8eac43dfca5c4cccc6dad9a80504436fca53bb9bc3100a2386d730fbe6b602", size = 4695941, upload-time = "2026-05-04T22:57:54.603Z" }, - { url = "https://files.pythonhosted.org/packages/e3/dc/7303087450c2ec9e7fbb750e17c2abfbc658f23cbd0e54009509b7cc4091/cryptography-48.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9ccdac7d40688ecb5a3b4a604b8a88c8002e3442d6c60aead1db2a89a041560c", size = 5252579, upload-time = "2026-05-04T22:57:57.207Z" }, - { url = "https://files.pythonhosted.org/packages/d0/c0/7101d3b7215edcdc90c45da544961fd8ed2d6448f77577460fa75a8443f7/cryptography-48.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:bd72e68b06bb1e96913f97dd4901119bc17f39d4586a5adf2d3e47bc2b9d58b5", size = 4743326, upload-time = "2026-05-04T22:57:59.535Z" }, - { url = "https://files.pythonhosted.org/packages/ac/d8/5b833bad13016f562ab9d063d68199a4bd121d18458e439515601d3357ec/cryptography-48.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:59baa2cb386c4f0b9905bd6eb4c2a79a69a128408fd31d32ca4d7102d4156321", size = 4826672, upload-time = "2026-05-04T22:58:01.996Z" }, - { url = "https://files.pythonhosted.org/packages/98/e1/7074eb8bf3c135558c73fc2bcf0f5633f912e6fb87e868a55c454080ef09/cryptography-48.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9249e3cd978541d665967ac2cb2787fd6a62bddf1e75b3e347a594d7dacf4f74", size = 4972574, upload-time = "2026-05-04T22:58:03.968Z" }, - { url = "https://files.pythonhosted.org/packages/04/70/e5a1b41d325f797f39427aa44ef8baf0be500065ab6d8e10369d850d4a4f/cryptography-48.0.0-cp311-abi3-win32.whl", hash = "sha256:9c459db21422be75e2809370b829a87eb37f74cd785fc4aa9ea1e5f43b47cda4", size = 3294868, upload-time = "2026-05-04T22:58:06.467Z" }, - { url = "https://files.pythonhosted.org/packages/f4/ac/8ac51b4a5fc5932eb7ee5c517ba7dc8cd834f0048962b6b352f00f41ebf9/cryptography-48.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:5b012212e08b8dd5edc78ef54da83dd9892fd9105323b3993eff6bea65dc21d7", size = 3817107, upload-time = "2026-05-04T22:58:08.845Z" }, - { url = "https://files.pythonhosted.org/packages/6b/84/70e3feea9feea87fd7cbe77efb2712ae1e3e6edf10749dc6e95f4e60e455/cryptography-48.0.0-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:3cb07a3ed6431663cd321ea8a000a1314c74211f823e4177fefa2255e057d1ec", size = 7986556, upload-time = "2026-05-04T22:58:11.172Z" }, - { url = "https://files.pythonhosted.org/packages/89/6e/18e07a618bb5442ba10cf4df16e99c071365528aa570dfcb8c02e25a303b/cryptography-48.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c7378637d7d88016fa6791c159f698b3d3eed28ebf844ac36b9dc04a14dae18", size = 4684776, upload-time = "2026-05-04T22:58:13.712Z" }, - { url = "https://files.pythonhosted.org/packages/be/6a/4ea3b4c6c6759794d5ee2103c304a5076dc4b19ae1f9fe47dba439e159e9/cryptography-48.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc90c0b39b2e3c65ef52c804b72e3c58f8a04ab2a1871272798e5f9572c17d20", size = 4698121, upload-time = "2026-05-04T22:58:16.448Z" }, - { url = "https://files.pythonhosted.org/packages/2f/59/6ff6ad6cae03bb887da2a5860b2c9805f8dac969ef01ce563336c49bd1d1/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:76341972e1eff8b4bea859f09c0d3e64b96ce931b084f9b9b7db8ef364c30eff", size = 4690042, upload-time = "2026-05-04T22:58:18.544Z" }, - { url = "https://files.pythonhosted.org/packages/ca/b4/fc334ed8cfd705aca282fe4d8f5ae64a8e0f74932e9feecb344610cf6e4d/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:55b7718303bf06a5753dcdccf2f3945cf18ad7bffde41b61226e4db31ab89a9c", size = 5282526, upload-time = "2026-05-04T22:58:20.75Z" }, - { url = "https://files.pythonhosted.org/packages/11/08/9f8c5386cc4cd90d8255c7cdd0f5baf459a08502a09de30dc51f553d38dc/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:a64697c641c7b1b2178e573cbc31c7c6684cd56883a478d75143dbb7118036db", size = 4733116, upload-time = "2026-05-04T22:58:23.627Z" }, - { url = "https://files.pythonhosted.org/packages/b8/77/99307d7574045699f8805aa500fa0fb83422d115b5400a064ddd306d7750/cryptography-48.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:561215ea3879cb1cbbf272867e2efda62476f240fb58c64de6b393ae19246741", size = 4316030, upload-time = "2026-05-04T22:58:25.581Z" }, - { url = "https://files.pythonhosted.org/packages/fd/36/a608b98337af3cb2aff4818e406649d30572b7031918b04c87d979495348/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ad64688338ed4bc1a6618076ba75fd7194a5f1797ac60b47afe926285adb3166", size = 4689640, upload-time = "2026-05-04T22:58:27.747Z" }, - { url = "https://files.pythonhosted.org/packages/dd/a6/825010a291b4438aecc1f568bc428189fc1175515223632477c07dc0a6df/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:906cbf0670286c6e0044156bc7d4af9cbb0ef6db9f73e52c3ec56ba6bdde5336", size = 5237657, upload-time = "2026-05-04T22:58:29.848Z" }, - { url = "https://files.pythonhosted.org/packages/b9/09/4e76a09b4caa29aad535ddc806f5d4c5d01885bd978bd984fbc6ca032cae/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:ea8990436d914540a40ab24b6a77c0969695ed52f4a4874c5137ccf7045a7057", size = 4732362, upload-time = "2026-05-04T22:58:32.009Z" }, - { url = "https://files.pythonhosted.org/packages/18/78/444fa04a77d0cb95f417dda20d450e13c56ba8e5220fc892a1658f44f882/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c18684a7f0cc9a3cb60328f496b8e3372def7c5d2df39ac267878b05565aaaae", size = 4819580, upload-time = "2026-05-04T22:58:34.254Z" }, - { url = "https://files.pythonhosted.org/packages/38/85/ea67067c70a1fd4be2c63d35eeed82658023021affccc7b17705f8527dd2/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9be5aafa5736574f8f15f262adc81b2a9869e2cfe9014d52a44633905b40d52c", size = 4963283, upload-time = "2026-05-04T22:58:36.376Z" }, - { url = "https://files.pythonhosted.org/packages/75/54/cc6d0f3deac3e81c7f847e8a189a12b6cdd65059b43dad25d4316abd849a/cryptography-48.0.0-cp314-cp314t-win32.whl", hash = "sha256:c17dfe85494deaeddc5ce251aebd1d60bbe6afc8b62071bb0b469431a000124f", size = 3270954, upload-time = "2026-05-04T22:58:38.791Z" }, - { url = "https://files.pythonhosted.org/packages/49/67/cc947e288c0758a4e5473d1dcb743037ab7785541265a969240b8885441a/cryptography-48.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27241b1dc9962e056062a8eef1991d02c3a24569c95975bd2322a8a52c6e5e12", size = 3797313, upload-time = "2026-05-04T22:58:40.746Z" }, - { url = "https://files.pythonhosted.org/packages/f2/63/61d4a4e1c6b6bab6ce1e213cd36a24c415d90e76d78c5eb8577c5541d2e8/cryptography-48.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:58d00498e8933e4a194f3076aee1b4a97dfec1a6da444535755822fe5d8b0b86", size = 7983482, upload-time = "2026-05-04T22:58:43.769Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ac/f5b5995b87770c693e2596559ffafe195b4033a57f14a82268a2842953f3/cryptography-48.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:614d0949f4790582d2cc25553abd09dd723025f0c0e7c67376a1d77196743d6e", size = 4683266, upload-time = "2026-05-04T22:58:46.064Z" }, - { url = "https://files.pythonhosted.org/packages/ec/c6/8b14f67e18338fbc4adb76f66c001f5c3610b3e2d1837f268f47a347dbbb/cryptography-48.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ce4bfae76319a532a2dc68f82cc32f5676ee792a983187dac07183690e5c66f", size = 4696228, upload-time = "2026-05-04T22:58:48.22Z" }, - { url = "https://files.pythonhosted.org/packages/ea/73/f808fbae9514bd91b47875b003f13e284c8c6bdfd904b7944e803937eec1/cryptography-48.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2eb992bbd4661238c5a397594c83f5b4dc2bc5b848c365c8f991b6780efcc5c7", size = 4689097, upload-time = "2026-05-04T22:58:50.9Z" }, - { url = "https://files.pythonhosted.org/packages/93/01/d86632d7d28db8ae83221995752eeb6639ffb374c2d22955648cf8d52797/cryptography-48.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:22a5cb272895dce158b2cacdfdc3debd299019659f42947dbdac6f32d68fe832", size = 5283582, upload-time = "2026-05-04T22:58:53.017Z" }, - { url = "https://files.pythonhosted.org/packages/02/e1/50edc7a50334807cc4791fc4a0ce7468b4a1416d9138eab358bfc9a3d70b/cryptography-48.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2b4d59804e8408e2fea7d1fbaf218e5ec984325221db76e6a241a9abd6cdd95c", size = 4730479, upload-time = "2026-05-04T22:58:55.611Z" }, - { url = "https://files.pythonhosted.org/packages/6f/af/99a582b1b1641ff5911ac559beb45097cf79efd4ead4657f578ef1af2d47/cryptography-48.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:984a20b0f62a26f48a3396c72e4bc34c66e356d356bf370053066b3b6d54634a", size = 4326481, upload-time = "2026-05-04T22:58:57.607Z" }, - { url = "https://files.pythonhosted.org/packages/90/ee/89aa26a06ef0a7d7611788ffd571a7c50e368cc6a4d5eef8b4884e866edb/cryptography-48.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5a5ed8fde7a1d09376ca0b40e68cd59c69fe23b1f9768bd5824f54681626032a", size = 4688713, upload-time = "2026-05-04T22:59:00.077Z" }, - { url = "https://files.pythonhosted.org/packages/70/ba/bcb1b0bb7a33d4c7c0c4d4c7874b4a62ae4f56113a5f4baefa362dfb1f0f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:8cd666227ef7af430aa5914a9910e0ddd703e75f039cef0825cd0da71b6b711a", size = 5238165, upload-time = "2026-05-04T22:59:02.317Z" }, - { url = "https://files.pythonhosted.org/packages/c9/70/ca4003b1ce5ca3dc3186ada51908c8a9b9ff7d5cab83cc0d43ee14ec144f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9071196d81abc88b3516ac8cdfad32e2b66dd4a5393a8e68a961e9161ddc6239", size = 4729947, upload-time = "2026-05-04T22:59:05.255Z" }, - { url = "https://files.pythonhosted.org/packages/44/a0/4ec7cf774207905aef1a8d11c3750d5a1db805eb380ee4e16df317870128/cryptography-48.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e2d54c8be6152856a36f0882ab231e70f8ec7f14e93cf87db8a2ed056bf160c", size = 4822059, upload-time = "2026-05-04T22:59:07.802Z" }, - { url = "https://files.pythonhosted.org/packages/1e/75/a2e55f99c16fcac7b5d6c1eb19ad8e00799854d6be5ca845f9259eae1681/cryptography-48.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5da777e32ffed6f85a7b2b3f7c5cbc88c146bfcd0a1d7baf5fcc6c52ee35dd4", size = 4960575, upload-time = "2026-05-04T22:59:09.851Z" }, - { url = "https://files.pythonhosted.org/packages/b8/23/6e6f32143ab5d8b36ca848a502c4bcd477ae75b9e1677e3530d669062578/cryptography-48.0.0-cp39-abi3-win32.whl", hash = "sha256:77a2ccbbe917f6710e05ba9adaa25fb5075620bf3ea6fb751997875aff4ae4bd", size = 3279117, upload-time = "2026-05-04T22:59:12.019Z" }, - { url = "https://files.pythonhosted.org/packages/9d/9a/0fea98a70cf1749d41d738836f6349d97945f7c89433a259a6c2642eefeb/cryptography-48.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:16cd65b9330583e4619939b3a3843eec1e6e789744bb01e7c7e2e62e33c239c8", size = 3792100, upload-time = "2026-05-04T22:59:14.884Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/12/45/870e7f4bef50e5f53b9f51d4428aee5290eedf58ba443f16b1ebb7ab8e66/cryptography-48.0.1.tar.gz", hash = "sha256:266f4ee051abb2f725b74ef8072b521ce1feacf685a3364fa6a6b45548db791a", size = 832989, upload-time = "2026-06-09T22:32:31.8Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/bc/ee4137cbbe105652c0ee4252792b78fc8e7afa4b8e61d9d5dc05a7f45731/cryptography-48.0.1-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:3e4a1a3232eef2e6c732827d5722db29a0cc8b27af2a4d865b094cf954be9ca1", size = 8008324, upload-time = "2026-06-09T22:31:00.702Z" }, + { url = "https://files.pythonhosted.org/packages/d5/85/6379d42181bfc713094f081360fc5784d6c816b599d45e7f082502d173ce/cryptography-48.0.1-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:32143b24adb918f078134e1e230f1eb8cc04886b92c28b5f0041aaf3e5699225", size = 4696243, upload-time = "2026-06-09T22:32:33.446Z" }, + { url = "https://files.pythonhosted.org/packages/9c/87/c85d147b53323c7eb4d850920c8901377323c2a0ff8d79c262d4fee89aa2/cryptography-48.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0d27a5696721ef7a672b8c810f6aded391058e0b9486e63e6d93baf765da691", size = 4713235, upload-time = "2026-06-09T22:31:40.141Z" }, + { url = "https://files.pythonhosted.org/packages/79/58/67cbf8cf1ee7c54b439ca07bbecf8362c07afc11a3724fea70f745784add/cryptography-48.0.1-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eb86ce1af36fe65041b6db9a8bb064ee621a7e5fded0f80d475ec243477cd242", size = 4702323, upload-time = "2026-06-09T22:31:42.191Z" }, + { url = "https://files.pythonhosted.org/packages/89/c6/24266ac10c47f6cd2a865f4446062b466da1d1f10b27189eac00e61bf0c9/cryptography-48.0.1-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:b024e784ad6c077ee0147b35ea9cbfc1e34e1fd4c1dcca214c2794d73a12df08", size = 5300085, upload-time = "2026-06-09T22:31:58.703Z" }, + { url = "https://files.pythonhosted.org/packages/d2/bb/cc4b78784f97efc8c5874c2a9743708d172be6663024b34a0467885ae0c8/cryptography-48.0.1-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3752f2dbc8f07a30aad2932c986cea495b03bb554887828225da104f732852b6", size = 4746137, upload-time = "2026-06-09T22:31:31.01Z" }, + { url = "https://files.pythonhosted.org/packages/1f/52/0c44de3f5267f8fbe8e835138017522a333436166e406f0db9b9e6e3033f/cryptography-48.0.1-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:bd81490cd5801d755cf97bb68ac191f14b708470b1c7cf4580f669b9c9264cd8", size = 4333867, upload-time = "2026-06-09T22:32:28.096Z" }, + { url = "https://files.pythonhosted.org/packages/9a/2e/772d7adbfa931537bc401640b7cac9976bff689bda187833e5d63b428e49/cryptography-48.0.1-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:66fd0771e7b9c6dcd44cf1120690d2338d16d72795cf40cae2786a39eba65429", size = 4701805, upload-time = "2026-06-09T22:31:38.284Z" }, + { url = "https://files.pythonhosted.org/packages/f8/a3/b06844f303873493c963caf581c04df31c7035e0c1b0f02c4814d319ec80/cryptography-48.0.1-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:3fd2ca57062b241c856670b073487d2e86c4637937ca5601e48f97bf8e11fc8f", size = 5258461, upload-time = "2026-06-09T22:31:04.187Z" }, + { url = "https://files.pythonhosted.org/packages/9f/13/8b765e2e12b07c74941caadb9d1c8fdc006c4dfbf2b8f2d610519758954d/cryptography-48.0.1-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:0ee6ea481db1ab889cba043ec1eda17bb9c1ea79db6722f779c3667f9f70322f", size = 4745488, upload-time = "2026-06-09T22:32:30.07Z" }, + { url = "https://files.pythonhosted.org/packages/2e/aa/48972bce55049b32a94f4907eda4d75fa385aad8a39506cc2fc72196ecf0/cryptography-48.0.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f2ceef93cb096aa3c4cc4b5c94ca6131f9196d28c64d6111533402a9b2054d41", size = 4830256, upload-time = "2026-06-09T22:31:43.868Z" }, + { url = "https://files.pythonhosted.org/packages/47/a2/e5079a032fb85cf6005046ca92bbd78b0c82dad2b5751ab8c311659da06f/cryptography-48.0.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9bd3f92d76217892b15df84ca256c2c113d386fdda7a7d8691aeeced976507c6", size = 4979117, upload-time = "2026-06-09T22:31:05.845Z" }, + { url = "https://files.pythonhosted.org/packages/b7/a0/8f50cae9c74e718ed769d63ed5c74bd0ea830c9550a74629cebd1b9c7bc7/cryptography-48.0.1-cp311-abi3-win32.whl", hash = "sha256:b9a32b876490d66c8bcc9963ef220199569748434ab01a9d6aaeabf88e7f5158", size = 3304154, upload-time = "2026-06-09T22:32:16.845Z" }, + { url = "https://files.pythonhosted.org/packages/c5/69/0572c77dbace6fef72f33755bd52ea399c71367250d366237f8691826b9e/cryptography-48.0.1-cp311-abi3-win_amd64.whl", hash = "sha256:39489bfca54c7a1f6b297efcd8bc608ab92d16c4ca631b0cad4da46724588b24", size = 3817138, upload-time = "2026-06-09T22:32:00.388Z" }, + { url = "https://files.pythonhosted.org/packages/42/06/3e768b4c3bc78201583fa35a0e18f640dd782ff41afba88f8545481a8874/cryptography-48.0.1-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:f817adc181390bd54f2f700107a7419040fb7c1bdf2fc26f36551a06a68c3345", size = 7989830, upload-time = "2026-06-09T22:31:07.8Z" }, + { url = "https://files.pythonhosted.org/packages/8a/13/6476736484b94041110c8340a3eb63962fea4975baea8cb4a512adb44d4d/cryptography-48.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d5d30989c6917b478b5817902e85fddaea2261efa8648383d965381ccb9e1ac4", size = 4689201, upload-time = "2026-06-09T22:31:09.745Z" }, + { url = "https://files.pythonhosted.org/packages/79/62/65a87f34d2a431546e2509b85d55e8c90df86d668f6731da64d538512ac2/cryptography-48.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:df637c05205ea7c1d7fbcbe54bbfea648a52951155f997af13d895d0ecc96991", size = 4702822, upload-time = "2026-06-09T22:32:24.409Z" }, + { url = "https://files.pythonhosted.org/packages/7f/59/810b5204b0a9b10f4b6bc06bd551a8b609803cd931806bc3b71884b225e5/cryptography-48.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:869c3b8a53bfe27147832df48b32adadf558249d50e76cb3769d40e986b13265", size = 4694875, upload-time = "2026-06-09T22:32:08.737Z" }, + { url = "https://files.pythonhosted.org/packages/24/dc/d8ca05ffea724eec6d232ea6f18e74c269eb6bdfdcc9bfba689790d1325f/cryptography-48.0.1-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:e361afba8918070d376df76f408a4f67fec0ee9cff81a99e48fe9a233ef59e17", size = 5290385, upload-time = "2026-06-09T22:31:15.212Z" }, + { url = "https://files.pythonhosted.org/packages/03/8c/3be6cb4da181f5bb6c19cf560c2359d60644a6b5fc5b57854e528f47b296/cryptography-48.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:d069066deead00ac7f090be101be875a06855908f7ec004c27b8fefb4acfb411", size = 4737082, upload-time = "2026-06-09T22:32:22.66Z" }, + { url = "https://files.pythonhosted.org/packages/aa/f6/d5f60a5a1434dbfd949e227fd0065d194c7e6b6ac526b17f5c06152b8231/cryptography-48.0.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:09f73a725d582cef64b91281a322cd798d14a33b2b6f2b7ad9531dc336d84c02", size = 4325328, upload-time = "2026-06-09T22:32:10.777Z" }, + { url = "https://files.pythonhosted.org/packages/17/b7/ba75dd947a14b6ad907b01ae8f6b5b348cdd1b48142f0063dee9e20c1d9d/cryptography-48.0.1-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:15254441469dd6bf027039453288e2072124f8b6603563f5d759e1c9b69273fa", size = 4694530, upload-time = "2026-06-09T22:31:53.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/29/50d6b9e8aff12d8b67afaeb3569335e32dc83a5723e3bbded24fdac9f809/cryptography-48.0.1-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:8ace4507d1e6533c125f4fac754f8bb8b6a74c08e92179dabd7e16571a3efbf3", size = 5245046, upload-time = "2026-06-09T22:31:25.774Z" }, + { url = "https://files.pythonhosted.org/packages/9f/04/618f4115cfc0add0838c82507aa18a346089428da8653ad38b3ff36f5cb3/cryptography-48.0.1-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:b4e391975f038e66432328639620a4aff2d307513b004f1ca06d6225bced815c", size = 4736660, upload-time = "2026-06-09T22:32:12.676Z" }, + { url = "https://files.pythonhosted.org/packages/24/9c/06e062462a0de28a3b3911322eded4c16deb9f441b1b7575d3dc59488ab5/cryptography-48.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42fcd8e26fe555d9b3577a135f5091fefa0aa4e99129c23fb56787a1bd4ada72", size = 4822229, upload-time = "2026-06-09T22:31:17.062Z" }, + { url = "https://files.pythonhosted.org/packages/f4/be/0561971eaaee4b8a0e7d5113c536921063ab91aaf23278ac374eaf881e11/cryptography-48.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c1400da5e32a43253392277eac7490a60e497d810a63dd5608d71bbd7af507c9", size = 4966364, upload-time = "2026-06-09T22:31:32.842Z" }, + { url = "https://files.pythonhosted.org/packages/a4/27/728c77876f12b000820b69ae490f3c4083775e79e07827e9e60be07ad209/cryptography-48.0.1-cp314-cp314t-win32.whl", hash = "sha256:0df56b056bc17c1b7d6821dfa65216e62bd232d8ab05eb3db44e71d235651471", size = 3278498, upload-time = "2026-06-09T22:31:29.154Z" }, + { url = "https://files.pythonhosted.org/packages/06/e3/79a612c6d7b1e6ee0edd43633d53035bec2cfb78c82b76f7864f39e36f34/cryptography-48.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:9de21387aa95e2a895823d0745b430bed4f33503ba9ab5e0b5311f33e37d66d2", size = 3798790, upload-time = "2026-06-09T22:31:56.697Z" }, + { url = "https://files.pythonhosted.org/packages/ca/6c/00fa2a95997164c8b2072ce327c23d4ab20809ccc323ea5fab91e53a4bba/cryptography-48.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:4fdc69f8e4316bcf0c8c8ec1f26f285d12e8142d88d96c876a59a03be3f6ae67", size = 7987408, upload-time = "2026-06-09T22:32:20.777Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d9/45f309a7e4e5f3f8f121d6d3be9e94024a7726ec598d6e08ae04edb2f04d/cryptography-48.0.1-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48fe40804d4caa2288f24e70ca8c64c42dd826da0ad7e4f1b41b2128d679e6c8", size = 4690196, upload-time = "2026-06-09T22:31:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9f/a1bc8bcc798811b8527eb374bbccf30a3f3e806829d967118222bf1125eb/cryptography-48.0.1-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:86be3b1b0b6bf09482fb50a979c508d2950ed95f5621ec77f4e385962006b83a", size = 4696782, upload-time = "2026-06-09T22:31:45.615Z" }, + { url = "https://files.pythonhosted.org/packages/66/c2/81a4fb4e4373c500bb526bc337ac5719dd31dd15b970b84a238168c6aa08/cryptography-48.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4ab0a343c807bbcd90c971cd1ecf072937cd01847a9e002bef88fb47ac6be577", size = 4696618, upload-time = "2026-06-09T22:31:11.564Z" }, + { url = "https://files.pythonhosted.org/packages/e5/0b/aa68b221dde92d09cb29a024ede17550ee21e77a404e59fc093c82bb51e1/cryptography-48.0.1-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9621de99d2da096006b629979efd8ae7eb2d8b822488d0c89ee4000c306c59b1", size = 5289970, upload-time = "2026-06-09T22:31:20.368Z" }, + { url = "https://files.pythonhosted.org/packages/78/13/fba657f958d2af66ea959a4ba01212632089249d34af1ae48054136344d7/cryptography-48.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:88c852a0ae366e262e5a1744b685e6a433dc8788dd2a277e418bf4904203609d", size = 4731873, upload-time = "2026-06-09T22:31:22.253Z" }, + { url = "https://files.pythonhosted.org/packages/4c/4c/9a964756d24a26b3e34dfcb16f961b89838786e6700b635b0d1e3adff4b6/cryptography-48.0.1-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:43c5835e2cb98c8733d86f57d6fc879b613f5c3478607281c3e36daffc6dd8a6", size = 4330804, upload-time = "2026-06-09T22:31:36.56Z" }, + { url = "https://files.pythonhosted.org/packages/4b/0f/a10f3a6eb12950a10e3a874070283aa2dd5875b2bfd15fad8a3e17b3f13e/cryptography-48.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:fe0180af5bf9236518a087e35bf2d9a347d5f5f51e63c579d683ddff424e3d46", size = 4696217, upload-time = "2026-06-09T22:31:13.351Z" }, + { url = "https://files.pythonhosted.org/packages/f3/6f/5cd12f951165ea73ef85266775d97e4c763b2474ccfd816dd69d3a18d6f8/cryptography-48.0.1-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:b7a2d1a937a738a881737cec135a38bb61470589b17515b9f73f571d0ae10401", size = 5245252, upload-time = "2026-06-09T22:32:02.193Z" }, + { url = "https://files.pythonhosted.org/packages/68/ab/8aaa12e4516ec4464033ab79b6f3b592bd5a92102467c4ace8a0d970203f/cryptography-48.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b74ca3b8e5ecdd833bf6a002ca41b4793bb27fb8f1c06ffaf2643c9e9140e31b", size = 4731388, upload-time = "2026-06-09T22:32:04.019Z" }, + { url = "https://files.pythonhosted.org/packages/1b/24/50027ea4dca85ec1f40688f3c24fb32ccacd520583c9592c3cc95628e6fb/cryptography-48.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2c37f2461406063b417837f5f3daab668652acd82423efcd7f0a9f04be972de1", size = 4824186, upload-time = "2026-06-09T22:32:18.707Z" }, + { url = "https://files.pythonhosted.org/packages/52/41/04cb5eb17085ade6f50cc611fb657df6a0f5885350de8764ece89c050197/cryptography-48.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:86fe77abb1bd87afb251d4d02ada7ecf53a32cee9b67d976abb2e45a13297475", size = 4964539, upload-time = "2026-06-09T22:31:18.793Z" }, + { url = "https://files.pythonhosted.org/packages/36/bf/ed70785c496e89d7e73b7cda2d21f2447fd6d4e821714b8d04ff217fed92/cryptography-48.0.1-cp39-abi3-win32.whl", hash = "sha256:6b2c0c3e6ccf3ade7750f836ef3ee36eea250cc467d45c256895573ac08cc6f1", size = 3282307, upload-time = "2026-06-09T22:30:53.162Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ff/371ea7d252656ee1eb6d83eeeef3d1d0c6baf1d6497687d081ea03814670/cryptography-48.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:9a49ca6c81417f6a5edb50375a60cccdd70fa0a91a5211829dbea74eba94d2ac", size = 3793408, upload-time = "2026-06-09T22:32:15.191Z" }, ] [[package]] @@ -1869,15 +1869,15 @@ wheels = [ [[package]] name = "starlette" -version = "0.50.0" +version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload-time = "2025-11-01T15:25:27.516Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/e3/7c1dc7381d9f8ab7d854328ebfa884e62cb3f3d8549ddfd37c7814f42afa/starlette-1.3.1.tar.gz", hash = "sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0", size = 2703240, upload-time = "2026-06-12T09:23:11.602Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bb/2799cc2ede3ed41131f8975621e7213dfc7ef4acbbaadfa440f32500c370/starlette-1.3.1-py3-none-any.whl", hash = "sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6", size = 73632, upload-time = "2026-06-12T09:23:10.017Z" }, ] [[package]] @@ -2121,28 +2121,28 @@ wheels = [ [[package]] name = "uv" -version = "0.11.14" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/a3/be4a946c7c2fc4094c020c8f7d8bd0a739bad55ebe4e2817d6e2b1bc6bff/uv-0.11.14.tar.gz", hash = "sha256:0ea006a117b586b2681b6dfd9703a540d2ad2a136ec0f48d272767e599cc3dfb", size = 4130699, upload-time = "2026-05-12T18:00:37.321Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/15/9b2138b16eb1fa8c2cd84b1037ad10c38b3acc36ce96c6d27000bfb7e716/uv-0.11.14-py3-none-linux_armv6l.whl", hash = "sha256:78411a883f230a710af19f2ac6e6f0ba8eae90f0e5af4605f923fd367539fff4", size = 23545199, upload-time = "2026-05-12T18:01:34.526Z" }, - { url = "https://files.pythonhosted.org/packages/75/81/c678e8b9a8e624f9c338c66cd57dd9cfc6b5a0501ad3c87fd0cc0bf8850a/uv-0.11.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:078f2e63da89c8fcf6d578f02156045c5990c57d76464aab3f3f798d3fff95cd", size = 22957064, upload-time = "2026-05-12T18:00:54.225Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ad/95fbd15b23f26f36d0cfb0ddf159b9602a1b1c0feced60a7f98385e919f1/uv-0.11.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:dcdad43d52c130e3159e84ab1844e04d819d2c4a2495a687d27f80d560a3650e", size = 21678307, upload-time = "2026-05-12T18:00:57.132Z" }, - { url = "https://files.pythonhosted.org/packages/8b/cb/b3da1c4d95d6dd507896bca16dbd643118013b2b151f5f35a08d3391728c/uv-0.11.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:9923da7c63d70de9fe71829503d7e7ebfd6304e804d7232aad5f716e190db25b", size = 23353409, upload-time = "2026-05-12T18:01:27.512Z" }, - { url = "https://files.pythonhosted.org/packages/51/ad/78c6b8d6bcc04c5043b50631e9b413422a03a0bd7c4a997748f8e9cbac25/uv-0.11.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:3b0759ca504e48dcd4fafb1a61ef69aeb24c5a60fbf5f504a7873c8db1b24718", size = 23103964, upload-time = "2026-05-12T18:01:31.094Z" }, - { url = "https://files.pythonhosted.org/packages/0f/7d/acb66e09bc54a74e4288e996d841af04d88588fd6bdbfbab2468ab7169a7/uv-0.11.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:78b51b117549ee4db7197ea5ece0848cecd443e464fb9dff9f254cdc1e4ed96f", size = 23104638, upload-time = "2026-05-12T18:01:10.093Z" }, - { url = "https://files.pythonhosted.org/packages/31/0a/8497be61accdb8e56d02e11edd3ac471466259420e0bd9c05c1966df134a/uv-0.11.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1ddbe8a2ab160affc179e9c3a40913b23a08cdf55254e1f3829cc22a51a0d8d", size = 24625888, upload-time = "2026-05-12T18:01:17.192Z" }, - { url = "https://files.pythonhosted.org/packages/95/91/f730799fd20a45777b255e20cf9f648a4e4e0979bf65e87a8633197cf7d9/uv-0.11.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3005a2db1e8d72e125630d4f22ac4ceddb2c033e1f9b94b7f3ea38ebac46dd6", size = 25445231, upload-time = "2026-05-12T18:00:40.012Z" }, - { url = "https://files.pythonhosted.org/packages/f5/4d/106463fc27e63e402aec2e791774dac2db5bd5e1c36cdcf38125aa97ab1c/uv-0.11.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5c8f9ea36274ef2f9d24f0522085e280844172e901d9213f66a21b212266706", size = 24571961, upload-time = "2026-05-12T18:00:43.713Z" }, - { url = "https://files.pythonhosted.org/packages/12/4d/163fe746b97bd1129627e8b1f943e17583ddc143eaab532d56a799a9ba5a/uv-0.11.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:379e64b236cf55f762a8308d7efe4365d5296ba29f3a4868761bc45b4e915a71", size = 24718523, upload-time = "2026-05-12T18:01:06.587Z" }, - { url = "https://files.pythonhosted.org/packages/19/fb/7a3673494a0cf70267559166398f9c50c4925ff20122f99a28d6c5a80d83/uv-0.11.14-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:29c12a562441fc2d604e6920c558cacce74a55f889468708683a79b35a6e18a1", size = 23454821, upload-time = "2026-05-12T18:00:51.166Z" }, - { url = "https://files.pythonhosted.org/packages/bb/43/6358394a567d865f3a5ce27b1e0d939549911e36d9b59f0c545a167f92f7/uv-0.11.14-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:e84069681c0334e07cbc7f114eb09d7fe1335e1db0297a66dbca80a1b393fe6d", size = 24087843, upload-time = "2026-05-12T18:00:47.272Z" }, - { url = "https://files.pythonhosted.org/packages/ef/f6/7d0ae1e1f52b85057ca24d8876d6a4cc87b541ea6aca627fe36594c06099/uv-0.11.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:b15bf7c146e38d7c938d3a207115d5fdd8ef764fe1f866c225b1bed27e88da1e", size = 24147611, upload-time = "2026-05-12T18:01:20.499Z" }, - { url = "https://files.pythonhosted.org/packages/5a/a2/511ad0c5da5697fd990b99569425b62b81cbc3458c35acc845211b55d6b5/uv-0.11.14-py3-none-musllinux_1_1_i686.whl", hash = "sha256:ddda5c5e41097814adac535c74851bae55e8097b9afc79aeae7fcffd8d86c06d", size = 23920348, upload-time = "2026-05-12T18:01:24.033Z" }, - { url = "https://files.pythonhosted.org/packages/6b/b6/7084e3401b1f1020f215a125136eec1ed2bd541e10a5fea1625515579599/uv-0.11.14-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:e54326703f1eca83a6fd73275e0f398b16b7d3f81531bf58899c2869bc403f6c", size = 24928981, upload-time = "2026-05-12T18:01:13.961Z" }, - { url = "https://files.pythonhosted.org/packages/4d/6a/7e81729fe729889c8cc63bbf64291734359bd7f6ba84852dc0504453511d/uv-0.11.14-py3-none-win32.whl", hash = "sha256:b384d873d0d18552c7524226125efd3965d921b7134c2f476c333771beb733e1", size = 22573503, upload-time = "2026-05-12T18:00:34.36Z" }, - { url = "https://files.pythonhosted.org/packages/94/5d/f8905f9af5cd46af2a688b2246dbb5a4d95b8557eeffd7f241e037659d9e/uv-0.11.14-py3-none-win_amd64.whl", hash = "sha256:f0a8b58b38e984241bca5d7a5a47bf9ffe1ca2ab392a640887db8a04c4a9ec95", size = 25175590, upload-time = "2026-05-12T18:01:00.38Z" }, - { url = "https://files.pythonhosted.org/packages/04/cb/7333d08d944f3018eb89242cd5e646e7b37faa1b567faeaf9254a8b59d53/uv-0.11.14-py3-none-win_arm64.whl", hash = "sha256:6a13e7e064563050c6606b3fd77091d427cdbdc5938b6f134baf8d8ec79bfdb7", size = 23594775, upload-time = "2026-05-12T18:01:03.55Z" }, +version = "0.11.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/34/609d5d01ba21dc8f0974610ca7802fbb2c946a0c38665cfe5c5aeddbefb5/uv-0.11.15.tar.gz", hash = "sha256:755f959ec6a2fd8ccb6ee76ad90ab759d2eb1f4797444078645dd1ee4bca92d6", size = 4159545, upload-time = "2026-05-18T19:57:48.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/7c/dcc230c5911884d8848145dabcac8fb95a5ed6f9fe1c57fae8242618f28a/uv-0.11.15-py3-none-linux_armv6l.whl", hash = "sha256:83b04ab49514a0a761ffedb36a748ee81f87746671e72088e5f32c9585e5f1a9", size = 23110183, upload-time = "2026-05-18T19:57:23.051Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f3/efd4e044b60eb9c3c12ee386be098d56c335538ccec7caa49349cfba9344/uv-0.11.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b6cae61f737be075b90be9e3f07d961072aed7019f4c9b8ed5c5d41c4d6cade3", size = 22637941, upload-time = "2026-05-18T19:57:26.752Z" }, + { url = "https://files.pythonhosted.org/packages/a6/b8/48627f895a1569e576822e0a8416aa4797eb4a4551de21a4ad97b9b5819d/uv-0.11.15-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9accae33619a9166e5c48531deb455d672cfb89f9357a00975e669c76b0bd49f", size = 21258803, upload-time = "2026-05-18T19:57:05.473Z" }, + { url = "https://files.pythonhosted.org/packages/af/50/4bc8a148274feabee2d9c9f1fa15009e10c0228dfe57981ee3ea2ef1d481/uv-0.11.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:c0cf52cd6d50bb9e05e2d968f45f80761107e4cbc8d4a26d9758f9d8274aaec1", size = 23066178, upload-time = "2026-05-18T19:57:33.058Z" }, + { url = "https://files.pythonhosted.org/packages/a9/56/139fc3bec9a8b0a25bfe2196123adb9f16124da437bf4fbcf0d21cfcafb2/uv-0.11.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:49dc6ed70bff00937384f96cdc4b1a4742d18e5504ec2c4a1214dba2dee5687a", size = 22705332, upload-time = "2026-05-18T19:57:36.714Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b0/b18b3dd204f8c213236a1ebd148e009861637129a8cce34df0e9aa22ed40/uv-0.11.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:adb9a89352539fdd8f7cd5f9966cf9f94fc5b98e0ccdf5003a04123dc6423bec", size = 22707534, upload-time = "2026-05-18T19:58:04.117Z" }, + { url = "https://files.pythonhosted.org/packages/76/36/3ca09f95572df99d361b49c96b1297149e96e120d8d1ecf074095a4b6da4/uv-0.11.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40ff67e3f8e8a7533781a2e892a534975a93acb83ea35460e64e7b2bf2111774", size = 24096607, upload-time = "2026-05-18T19:58:11.625Z" }, + { url = "https://files.pythonhosted.org/packages/64/be/3bdee21a296bbf5336a526e3613d0e7d4538dacc39c62d7fcba55d15f6b0/uv-0.11.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6463a299ed7e6b5a800ed6f108af8e1588352629424133ddef7572b0e1e1118", size = 25082562, upload-time = "2026-05-18T19:57:40.69Z" }, + { url = "https://files.pythonhosted.org/packages/cd/73/f371f3689ffe741066468d001d85f739fc4b5574de83b639ef19b5e8a7f4/uv-0.11.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68c1e62d4b78578b90b833553286b65d6a7e327537716441068583ba652ec4f5", size = 24253391, upload-time = "2026-05-18T19:57:18.47Z" }, + { url = "https://files.pythonhosted.org/packages/d3/16/fe392d618af6b00c064b3e718d585dcf791546a77c5123a5bec07ce53a0a/uv-0.11.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98edf1bdaf82447014852051d93e3ee95012509c567bf057fd117e6bdbd9a807", size = 24415871, upload-time = "2026-05-18T19:58:19.651Z" }, + { url = "https://files.pythonhosted.org/packages/6e/24/2e92a052fb6334fcd746d1c7cb57847c204b118c84f5da53c0f9e129f7b7/uv-0.11.15-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:be8f76d25bcf4c92bb384240ac1bf9aa7f51063d0bdeca4c9cf0ec3ed8b145e0", size = 23159007, upload-time = "2026-05-18T19:57:10.653Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2e/6923d0658d164bb2c435ed1868aa2d49b3074594679917a001ff92dc95bb/uv-0.11.15-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:f9f4fbbf4fe485522054f3c7496c6e8e932d6436e4200ff3daf718db0b7c7bd5", size = 23769385, upload-time = "2026-05-18T19:58:15.856Z" }, + { url = "https://files.pythonhosted.org/packages/a4/99/7e34cd949e57360814e8064cc9fb7104df445d0f6a663504e5f7473480aa/uv-0.11.15-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:0ed920e896b2fd13a35031707e307e42fbb2681458b967440a17272d86d49137", size = 23860973, upload-time = "2026-05-18T19:57:55.575Z" }, + { url = "https://files.pythonhosted.org/packages/28/98/8fe1f5f9d816e94569a0298dd8e0936801097625fa1952162951f0d628b6/uv-0.11.15-py3-none-musllinux_1_1_i686.whl", hash = "sha256:41d907611f3e6a13262807fd7f0a17849f76285ca80f536f6b3943732bdc6656", size = 23431392, upload-time = "2026-05-18T19:57:59.814Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6b/76a1ce2fa860026913a5941700cdc7d715fce9c3277a3fa3489cf2523ca0/uv-0.11.15-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:e3b68f8bf1a4568710f77e5bda9182ce7682811d89a8e7468c22460e032b234d", size = 24519478, upload-time = "2026-05-18T19:57:51.165Z" }, + { url = "https://files.pythonhosted.org/packages/43/60/1d58e8a05718cb50494763115710b73846cacb651fd735d285233fd72c59/uv-0.11.15-py3-none-win32.whl", hash = "sha256:8e2da3076761086a5b76869c3f38ef0509c836046ef41ddd19485dfd7271dca9", size = 22020178, upload-time = "2026-05-18T19:58:07.64Z" }, + { url = "https://files.pythonhosted.org/packages/55/53/40fcefcb348af660488597ed3c01363df7344e60611f8883750dc596f5c6/uv-0.11.15-py3-none-win_amd64.whl", hash = "sha256:cc3915ab291a1ecaf31de05f5d8bd70d09c66fe9911a53f70d9efa62ff0dbd8a", size = 24668779, upload-time = "2026-05-18T19:57:44.894Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7d/fa3a9960c95af9bbe2a629048760d0b9b4fead8ccd4f2235af747ec7cdf0/uv-0.11.15-py3-none-win_arm64.whl", hash = "sha256:4f39426a13dee24897aed60c4b98058c66f18bd983885ac5f4a54a04b24fbddf", size = 23198178, upload-time = "2026-05-18T19:57:14.68Z" }, ] [[package]]