From afcc5fd8c675d95741b2971150a583256f850cfd Mon Sep 17 00:00:00 2001 From: IWasZer0Cool Date: Fri, 31 Jul 2026 16:48:21 -0400 Subject: [PATCH 01/21] first cut at re-naming the fork --- .gitignore | 5 +- AGENTS.md | 36 ++ CHANGELOG.md | 78 ++-- HANDOFF.md | 76 ++-- README.md | 240 +++++------ benchmark/test_cli.py | 32 +- benchmark/test_mcp.py | 21 +- blastradius/__init__.py | 2 + {codeindex => blastradius}/analyze.py | 6 +- .../analyzers/__init__.py | 0 {codeindex => blastradius}/analyzers/base.py | 0 .../analyzers/ci_analyzer.py | 0 .../analyzers/cross_lang_analyzer.py | 0 .../analyzers/css_analyzer.py | 0 .../analyzers/docker_analyzer.py | 0 .../analyzers/go_analyzer.py | 0 .../analyzers/java_analyzer.py | 0 .../analyzers/js_analyzer.py | 0 .../analyzers/monorepo_analyzer.py | 0 .../analyzers/php_analyzer.py | 0 .../analyzers/python_analyzer.py | 0 .../analyzers/ruby_analyzer.py | 0 .../analyzers/rust_analyzer.py | 0 .../analyzers/schema_analyzer.py | 0 .../analyzers/terraform_analyzer.py | 0 {codeindex => blastradius}/cli.py | 123 +++--- {codeindex => blastradius}/hook.py | 26 +- {codeindex => blastradius}/impact.py | 0 {codeindex => blastradius}/index.py | 32 +- {codeindex => blastradius}/mcp_server.py | 60 +-- {codeindex => blastradius}/reporter.py | 2 +- .../semantic/__init__.py | 4 +- .../semantic/provider.py | 4 +- {codeindex => blastradius}/semantic/search.py | 10 +- {codeindex => blastradius}/store/__init__.py | 2 +- {codeindex => blastradius}/store/db.py | 20 +- .../symbol_extractor.py | 2 +- {codeindex => blastradius}/symbols.py | 12 +- .../temporal/__init__.py | 2 +- .../temporal/history.py | 4 +- {codeindex => blastradius}/viz_server.py | 10 +- codeindex/__init__.py | 2 - docs/CKG-INTERNALS.md | 20 +- ...> blastradius-temporal-knowledge-graph.md} | 64 +-- docs/evaluation-prompt.md | 60 +-- pyproject.toml | 8 +- tests/test_phase1.py | 20 +- tests/test_phase2.py | 36 +- tests/test_phase3.py | 38 +- uv.lock | 372 +++++++++--------- viz/explorer.html | 4 +- 51 files changed, 745 insertions(+), 688 deletions(-) create mode 100644 AGENTS.md create mode 100644 blastradius/__init__.py rename {codeindex => blastradius}/analyze.py (96%) rename {codeindex => blastradius}/analyzers/__init__.py (100%) rename {codeindex => blastradius}/analyzers/base.py (100%) rename {codeindex => blastradius}/analyzers/ci_analyzer.py (100%) rename {codeindex => blastradius}/analyzers/cross_lang_analyzer.py (100%) rename {codeindex => blastradius}/analyzers/css_analyzer.py (100%) rename {codeindex => blastradius}/analyzers/docker_analyzer.py (100%) rename {codeindex => blastradius}/analyzers/go_analyzer.py (100%) rename {codeindex => blastradius}/analyzers/java_analyzer.py (100%) rename {codeindex => blastradius}/analyzers/js_analyzer.py (100%) rename {codeindex => blastradius}/analyzers/monorepo_analyzer.py (100%) rename {codeindex => blastradius}/analyzers/php_analyzer.py (100%) rename {codeindex => blastradius}/analyzers/python_analyzer.py (100%) rename {codeindex => blastradius}/analyzers/ruby_analyzer.py (100%) rename {codeindex => blastradius}/analyzers/rust_analyzer.py (100%) rename {codeindex => blastradius}/analyzers/schema_analyzer.py (100%) rename {codeindex => blastradius}/analyzers/terraform_analyzer.py (100%) rename {codeindex => blastradius}/cli.py (88%) rename {codeindex => blastradius}/hook.py (73%) rename {codeindex => blastradius}/impact.py (100%) rename {codeindex => blastradius}/index.py (88%) rename {codeindex => blastradius}/mcp_server.py (90%) rename {codeindex => blastradius}/reporter.py (97%) rename {codeindex => blastradius}/semantic/__init__.py (58%) rename {codeindex => blastradius}/semantic/provider.py (93%) rename {codeindex => blastradius}/semantic/search.py (92%) rename {codeindex => blastradius}/store/__init__.py (74%) rename {codeindex => blastradius}/store/db.py (98%) rename {codeindex => blastradius}/symbol_extractor.py (99%) rename {codeindex => blastradius}/symbols.py (92%) rename {codeindex => blastradius}/temporal/__init__.py (74%) rename {codeindex => blastradius}/temporal/history.py (98%) rename {codeindex => blastradius}/viz_server.py (91%) delete mode 100644 codeindex/__init__.py rename docs/{codeindex-temporal-knowledge-graph.md => blastradius-temporal-knowledge-graph.md} (78%) diff --git a/.gitignore b/.gitignore index 688d41c..52c1776 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,8 @@ # Generated artifacts -codeindex.json +blastradius.json symbolindex.json repo_graph.json -symbolindex.json -.codeindex/ +.blastradius/ benchmark/results.json # Claude Code diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..f69eb73 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,36 @@ +# AGENTS.md + +## Developer Commands & Verification + +- **Run tests**: `uv run pytest` (set `$env:PYTHONPATH="."` if running outside an installed venv). +- **Run single test**: `uv run pytest tests/test_phase1.py` or `uv run pytest -k `. +- **Run CLI in dev**: `uv run python -m blastradius.cli ` or `uv run blastradius `. +- **Environment note**: Avoid system `pip` or system `python` if broken; prefer `uv run` or `.venv\Scripts\` execution. + +## Core Architecture & Package Structure + +- **Package name**: `blastradius` (source in `blastradius/`). Entry point: `blastradius.cli:main`. +- **Index artifacts**: Default dependency graph is written to `blastradius.json`; SQLite store is populated at `/.blastradius/index.db`. +- **Layering rule**: `blastradius.store`, `blastradius.temporal`, and core modules must **never** import from `blastradius.semantic` or `blastradius.analyzers`. +- **Backward compatibility**: The `blastradius.json` file export schema is frozen. SQLite DB synchronization is an additive side-effect of `index.build()`. + +## Storage & Database Gotchas (`blastradius/store/db.py`) + +- **FTS5 table**: Must remain a standalone table without the `content=` option. Using `content='symbols'` triggers `database disk image is malformed` errors during deletes in WAL mode. +- **Soft deletes**: `sync()` must set `last_seen_commit` on soft-delete for temporal `--as-of` queries to filter correctly. +- **SQLite transactions**: `executescript()` auto-commits pending transactions in Python `sqlite3`. Keep regular DDL and FTS DDL in separate `executescript()` calls. +- **`sqlite-vec` optional dependency**: Never import `sqlite_vec` at module top-level. Wrap imports in `try...except` for runtime feature detection and graceful fallback to FTS5 + graph search. + +## Temporal Layer (`blastradius/temporal/history.py`) + +- **Git history reading**: `backfill()` streams commit history using `git ls-tree` and `git cat-file --batch`. Do not replace this with `git checkout` or individual `git show` calls. + +## Coding Conventions + +- **Module header**: Every Python file starts with: + ```python + # Copyright 2026 David Scheiderman + # Licensed under the Apache License, Version 2.0 + from __future__ import annotations + ``` +- **Python version**: Targets Python 3.9+. Modern union (`X | Y`) and generic types (`list[str]`) are enabled via `from __future__ import annotations`. diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e1cc6e..2f5c422 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ All notable changes to this project will be documented in this file. ### Added -- **`codeindex symbol-blast `** — per-export blast radius for a file. Lists every +- **`blastradius symbol-blast `** — per-export blast radius for a file. Lists every exported symbol with a count and list of which importer files reference it by name. Answers "which routes use each export from lib/db/schema.ts" without manual grep. Supports `--json` for programmatic use. @@ -46,10 +46,10 @@ All notable changes to this project will be documented in this file. ### Fixed -- **`lookup` reads SQLite DB** — `codeindex lookup` and the `lookup_symbol` MCP tool - previously read `symbolindex.json`, which is only written by `codeindex symbols`. +- **`lookup` reads SQLite DB** — `blastradius lookup` and the `lookup_symbol` MCP tool + previously read `symbolindex.json`, which is only written by `blastradius symbols`. Both now query `Store.lookup_by_name()` from the SQLite DB (the same source as - `codeindex search`), falling back to `symbolindex.json` only when no DB is present. + `blastradius search`), falling back to `symbolindex.json` only when no DB is present. Symbols found via search — including those extracted from destructured re-exports — are now consistently reachable via lookup. - **`lookup` output shows symbol name** — plain-mode output was `auth.ts:8 (const)`; @@ -69,9 +69,9 @@ All notable changes to this project will be documented in this file. genuine API surfaces with broad real coupling. - **`changed-since` edge origin annotation** — added edges now carry `first_seen_commit`; when N edges share the `last_indexed_commit` value, the CLI prints a count. The message - branches on whether `codeindex history` has been run: - - History not run: `"run codeindex history to date them accurately"` - - History run: `"bootstrap-gap artifacts: existed before the first codeindex analyze + branches on whether `blastradius history` has been run: + - History not run: `"run blastradius history to date them accurately"` + - History run: `"bootstrap-gap artifacts: existed before the first blastradius analyze and cannot be dated further"` — correctly reflects the inherent limitation rather than implying a fixable error. MCP response gains `bootstrap_gap` boolean. @@ -113,11 +113,11 @@ All notable changes to this project will be documented in this file. when FTS (or semantic KNN) already returns ≥ k results, preventing structurally adjacent but semantically unrelated symbols from diluting high-quality keyword hits. -- **Search file aggregation** — `codeindex search` and the `semantic_search` +- **Search file aggregation** — `blastradius search` and the `semantic_search` MCP tool now include a `Files` section aggregating results by file (sorted by symbol hit count). The entry-point file appears even when no single symbol from it ranks at the top. -- **`db status` FTS row count** — `codeindex db status` now shows +- **`db status` FTS row count** — `blastradius db status` now shows `fts_symbols` (rows in `symbols_fts`) making it easy to diagnose whether the FTS index is populated. @@ -125,22 +125,22 @@ All notable changes to this project will be documented in this file. ### Fixed -- **Multi-word FTS search OR fallback** — `codeindex search "auth token"` previously +- **Multi-word FTS search OR fallback** — `blastradius search "auth token"` previously returned nothing when AND semantics found no single symbol containing all words. `fts_search()` now retries with `word1 OR word2 OR ...` automatically when the AND query returns zero results. -- **`changed-since` backfill warning** — when `codeindex history` has never been run, +- **`changed-since` backfill warning** — when `blastradius history` has never been run, all files share exactly one `first_seen_commit`, making `changed-since` results inaccurate against any older ref. The command now detects this via `COUNT(DISTINCT first_seen_commit) <= 1` and prints a clear warning on stderr (CLI) / includes a `"warning"` key in the response (JSON + MCP tool) directing - the user to run `codeindex history` first. + the user to run `blastradius history` first. ## [0.3.0] - 2026-06-07 ### Summary -codeindex evolves from a stateless point-in-time dependency analyzer into a +blastradius evolves from a stateless point-in-time dependency analyzer into a **temporal code knowledge graph** — persistent, incremental, and semantically queryable. Three new properties: persistence + incrementality, time, and meaning (semantic retrieval). All existing CLI commands, JSON schemas, and MCP @@ -149,36 +149,36 @@ tools are unchanged. ### Added #### Persistent SQLite store (Phase 1) -- `.codeindex/index.db` — SQLite graph store created automatically on - `codeindex analyze`; survives across runs, never touches `codeindex.json` +- `.blastradius/index.db` — SQLite graph store created automatically on + `blastradius analyze`; survives across runs, never touches `blastradius.json` - Incremental indexing: detects changed files via `git diff --name-status` between index runs; logs changed file count to stderr -- `codeindex db status` — schema version, last indexed commit, file/edge/symbol counts -- `codeindex db migrate` — applies pending schema migrations (runs automatically on open) -- `codeindex symbols` now syncs symbols to DB with FTS5 full-text index +- `blastradius db status` — schema version, last indexed commit, file/edge/symbol counts +- `blastradius db migrate` — applies pending schema migrations (runs automatically on open) +- `blastradius symbols` now syncs symbols to DB with FTS5 full-text index #### Temporal layer (Phase 2) - Every file, edge, and symbol carries `first_seen_commit` / `last_seen_commit` — facts are never hard-deleted, only soft-deleted with temporal stamps -- `codeindex history [--since REF] [--max-commits N]` — backfills temporal +- `blastradius history [--since REF] [--max-commits N]` — backfills temporal data from git history without any working-tree checkouts (uses `git ls-tree` + `git cat-file --batch`) -- `codeindex changed-since ` — files and edges added or removed since a +- `blastradius changed-since ` — files and edges added or removed since a commit, branch, or tag -- `codeindex impact --as-of ` — blast radius at a historical point +- `blastradius impact --as-of ` — blast radius at a historical point in time, not just HEAD #### Semantic layer (Phase 3) -- `codeindex search "" [--k N] [--as-of REF] [--json]` — hybrid +- `blastradius search "" [--k N] [--as-of REF] [--json]` — hybrid semantic + FTS5 keyword + graph expansion search, fused with Reciprocal Rank Fusion (RRF) -- `codeindex/semantic/provider.py` — `EmbeddingProvider` ABC + +- `blastradius/semantic/provider.py` — `EmbeddingProvider` ABC + `OpenAIEmbeddingProvider` HTTP client (stdlib `urllib` only, no new runtime deps) - `sqlite-vec` optional extension for KNN vector search; absent = graceful fallback to FTS + graph with a clear notice (no crash, no config required) -- Embeddings generated automatically during `codeindex analyze` when - `CODEINDEX_EMBEDDING_ENDPOINT` / `_MODEL` / `_DIMS` env vars are set -- `codeindex[semantic]` extra: `pip install 'codeindex[semantic]'` +- Embeddings generated automatically during `blastradius analyze` when + `BLASTRADIUS_EMBEDDING_ENDPOINT` / `_MODEL` / `_DIMS` env vars are set +- `blastradius[semantic]` extra: `pip install 'blastradius[semantic]'` #### MCP surface (Phase 4) — 4 new tools, existing 6 unchanged - `semantic_search` — hybrid search from an MCP client; degrades gracefully @@ -190,18 +190,18 @@ tools are unchanged. - `schema_version` bumped to `"2"` with forward migration from `"1"` - FTS5 `symbols_fts` rowid now equals `symbols.id` (enables direct FTS → symbol row mapping without a secondary lookup) -- `codeindex db status` output extended with `embedding_model`, `embedding_dims`, +- `blastradius db status` output extended with `embedding_model`, `embedding_dims`, `vec_symbols` fields - README rewritten to document all new commands, the SQLite store, semantic setup, and the full 10-tool MCP surface ### Internal -- `codeindex/store/db.py`: `Store` class — `init_vectors()`, `upsert_embeddings()`, +- `blastradius/store/db.py`: `Store` class — `init_vectors()`, `upsert_embeddings()`, `semantic_search()`, `fts_search()`, `graph_expand()`, `neighborhood()`, `symbol_visible_at()`, `get_symbol()`, `symbols_needing_embeddings()` -- `codeindex/temporal/history.py`: `backfill()` — BFS over git log via plumbing +- `blastradius/temporal/history.py`: `backfill()` — BFS over git log via plumbing commands; no checkout side-effects -- `codeindex/semantic/search.py`: `hybrid_search()` with RRF fusion +- `blastradius/semantic/search.py`: `hybrid_search()` with RRF fusion - Dependency direction enforced: `store/` and `temporal/` never import from `semantic/` or `graph/` - 7 new Phase 3 tests; 6 Phase 2 tests; 5 Phase 1 tests (18 total, all green) @@ -209,9 +209,9 @@ tools are unchanged. ## [0.2.0] - 2026-05-24 ### Added -- `codeindex lookup ` — find where a symbol is defined (file + line) -- `codeindex dependencies ` — show imports and imported-by for a file -- `codeindex high-blast` — list files above a blast score threshold +- `blastradius lookup ` — find where a symbol is defined (file + line) +- `blastradius dependencies ` — show imports and imported-by for a file +- `blastradius high-blast` — list files above a blast score threshold - All three new commands support `--json` for machine-readable output - `lookup_symbol` and `build_symbol_index` tools in MCP server - CLI integration test suite (`benchmark/test_cli.py`) — 37 assertions covering happy path, `--json` output, error cases, and sort-order invariants @@ -231,12 +231,12 @@ tools are unchanged. ### Added - Multi-language dependency analysis: Python, JavaScript/TypeScript, Go, Ruby, Rust, Java/Kotlin, PHP, CSS - Blast-radius impact scoring — every file gets a score based on direct and transitive dependents -- `codeindex analyze ` — analyze a repo and write `codeindex.json` -- `codeindex impact ` — show blast-radius impact report for a file -- `codeindex symbols ` — build `symbolindex.json` with functions, classes, and exports; supports `--inline` and `--claude-md` modes -- `codeindex serve --mcp` — MCP stdio server exposing `analyze_repo`, `get_impact`, `get_dependencies`, `get_high_blast_files`, `build_symbol_index`, `lookup_symbol` -- `codeindex serve --viz` — visualization UI server -- `codeindex install-hook` — pre-commit hook for blast-radius warnings +- `blastradius analyze ` — analyze a repo and write `blastradius.json` +- `blastradius impact ` — show blast-radius impact report for a file +- `blastradius symbols ` — build `symbolindex.json` with functions, classes, and exports; supports `--inline` and `--claude-md` modes +- `blastradius serve --mcp` — MCP stdio server exposing `analyze_repo`, `get_impact`, `get_dependencies`, `get_high_blast_files`, `build_symbol_index`, `lookup_symbol` +- `blastradius serve --viz` — visualization UI server +- `blastradius install-hook` — pre-commit hook for blast-radius warnings - Phase 4: Docker, CI/CD, and schema analyzers - Phase 5: monorepo and cross-language intelligence - Apache 2.0 license diff --git a/HANDOFF.md b/HANDOFF.md index 51b30d5..7452ac7 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -1,6 +1,6 @@ -# Engineering Handoff — codeindex Temporal Knowledge Graph +# Engineering Handoff — blastradius Temporal Knowledge Graph -**Repo:** `scheidydudes-github-repos/codeindex` +**Repo:** `scheidydudes-github-repos/blastradius` **Branch:** `main` (4 commits ahead of origin) **Last commit:** `e863853` — Phase 2 temporal layer **Test status:** 11/11 passing (`uv run pytest` or `.venv/bin/pytest`) @@ -9,7 +9,7 @@ ## 1. Mission -We're evolving `codeindex` from a stateless point-in-time dependency analyzer into a **self-hosted temporal code knowledge graph** — persistent, incremental, and semantically queryable. The design document is `docs/codeindex-temporal-knowledge-graph.md` (CKG-DESIGN-001). Phases 0–2 are complete and committed. Next up is Phase 3 (semantic retrieval via embeddings) then Phase 4 (MCP surface + README). +We're evolving `blastradius` from a stateless point-in-time dependency analyzer into a **self-hosted temporal code knowledge graph** — persistent, incremental, and semantically queryable. The design document is `docs/blastradius-temporal-knowledge-graph.md` (CKG-DESIGN-001). Phases 0–2 are complete and committed. Next up is Phase 3 (semantic retrieval via embeddings) then Phase 4 (MCP surface + README). --- @@ -19,30 +19,30 @@ We're evolving `codeindex` from a stateless point-in-time dependency analyzer in - **Phase 0** (`555c38d`): `docs/CKG-INTERNALS.md` — full internal map of the pre-change codebase. No behavior changes. -- **Phase 1** (`9b167d6`): SQLite store at `/.codeindex/index.db`. - - `Store` class in `codeindex/store/db.py` — schema v1, WAL mode, upsert/soft-delete for files/edges/symbols, FTS5 symbol index. +- **Phase 1** (`9b167d6`): SQLite store at `/.blastradius/index.db`. + - `Store` class in `blastradius/store/db.py` — schema v1, WAL mode, upsert/soft-delete for files/edges/symbols, FTS5 symbol index. - `index.build()` now syncs to DB as a side effect after every `analyze` run. JSON export unchanged. - - `codeindex db status` / `codeindex db migrate` CLI commands. + - `blastradius db status` / `blastradius db migrate` CLI commands. - Incremental detection: logs `N file(s) changed since ` based on `git diff --name-status`. - 5 tests: golden idempotency, DB population, incremental detection, status counts, soft-delete. - **Phase 2** (`e863853`): Temporal layer. - - `codeindex/temporal/history.py`: `backfill()` walks git log via `git ls-tree` + `git cat-file --batch`; never touches working tree; sets `first_seen_commit`/`last_seen_commit` on files and edges. + - `blastradius/temporal/history.py`: `backfill()` walks git log via `git ls-tree` + `git cat-file --batch`; never touches working tree; sets `first_seen_commit`/`last_seen_commit` on files and edges. - `Store.as_of_impact(file, reachable_set)`: blast radius at a historical point using SQLite temp table for commit ancestry. - `Store.changed_since(reachable_set)`: files/edges added or removed since a ref. - - `codeindex history [--since REF] [--max-commits N]` - - `codeindex changed-since ` - - `codeindex impact FILE --as-of ` + - `blastradius history [--since REF] [--max-commits N]` + - `blastradius changed-since ` + - `blastradius impact FILE --as-of ` - 6 tests: as-of differs from HEAD, changed_since edges, backfill populates commits table, first_seen_commit set, no working-tree modification, changed-since added file. ### What's NOT yet built -- **Phase 3** (semantic layer): `codeindex/semantic/` package, embedding provider, `vec_symbols` table, `codeindex search` command, hybrid RRF query engine. +- **Phase 3** (semantic layer): `blastradius/semantic/` package, embedding provider, `vec_symbols` table, `blastradius search` command, hybrid RRF query engine. - **Phase 4** (surface): 4 new MCP tools (`semantic_search`, `temporal_impact`, `graph_query`, `changed_since`), README update. ### Exact next action -Start Phase 3. The design doc (`docs/codeindex-temporal-knowledge-graph.md` §6.4, §6.5, §8 Phase 3) is the authoritative spec. Read it before writing code. +Start Phase 3. The design doc (`docs/blastradius-temporal-knowledge-graph.md` §6.4, §6.5, §8 Phase 3) is the authoritative spec. Read it before writing code. --- @@ -57,7 +57,7 @@ Start Phase 3. The design doc (`docs/codeindex-temporal-knowledge-graph.md` §6. **Decision:** JSON write path is fully unchanged; DB is an additive side-effect **Alternatives considered:** Replace JSON with DB-only export; migrate callers to read from DB -**Reason:** Backward compatibility — every existing CLI command, MCP tool, and viz server reads `codeindex.json`. Phase 4 will eventually add DB-backed read paths, but for now JSON stays as the source of truth for queries. +**Reason:** Backward compatibility — every existing CLI command, MCP tool, and viz server reads `blastradius.json`. Phase 4 will eventually add DB-backed read paths, but for now JSON stays as the source of truth for queries. **Reversibility:** Load-bearing for Phases 1–3; Phase 4 is the right time to revisit. --- @@ -86,7 +86,7 @@ Start Phase 3. The design doc (`docs/codeindex-temporal-knowledge-graph.md` §6. **Decision:** uv venv at `.venv/` with Python 3.11 **Alternatives considered:** System pip (broken on this machine due to packaging metadata corruption) **Reason:** System pip had a corrupted `packaging` package. uv created a clean 3.11 venv. -**Reversibility:** Easy. Run `.venv/bin/pytest` or `.venv/bin/codeindex` for everything. +**Reversibility:** Easy. Run `.venv/bin/pytest` or `.venv/bin/blastradius` for everything. --- @@ -96,10 +96,10 @@ Start Phase 3. The design doc (`docs/codeindex-temporal-knowledge-graph.md` §6. | File | Purpose | |---|---| -| `codeindex/store/__init__.py` | Re-exports `Store` | -| `codeindex/store/db.py` | `Store` class: schema, upsert/soft-delete, temporal queries, FTS, status | -| `codeindex/temporal/__init__.py` | Re-exports `backfill` | -| `codeindex/temporal/history.py` | `backfill()`, git plumbing helpers, lightweight import extractor | +| `blastradius/store/__init__.py` | Re-exports `Store` | +| `blastradius/store/db.py` | `Store` class: schema, upsert/soft-delete, temporal queries, FTS, status | +| `blastradius/temporal/__init__.py` | Re-exports `backfill` | +| `blastradius/temporal/history.py` | `backfill()`, git plumbing helpers, lightweight import extractor | | `tests/test_phase1.py` | 5 Phase 1 acceptance tests | | `tests/test_phase2.py` | 6 Phase 2 acceptance tests | | `tests/fixtures/simple_python/` | 3-file Python fixture repo (main, utils, models) | @@ -109,29 +109,29 @@ Start Phase 3. The design doc (`docs/codeindex-temporal-knowledge-graph.md` §6. | File | What changed | |---|---| -| `codeindex/index.py` | Added `git_reachable()`, `git_resolve()`, `db_path_for()`, `find_db()`. `build()` now syncs to Store after enrichment. Content hashes computed pre-sync, stripped before JSON write. | -| `codeindex/cli.py` | Added `_cmd_db`, `_cmd_history`, `_cmd_changed_since`. Updated `_cmd_impact` to handle `--as-of`. Added `history`, `changed-since`, `db` subparsers. Added `--as-of` arg to `impact`. | +| `blastradius/index.py` | Added `git_reachable()`, `git_resolve()`, `db_path_for()`, `find_db()`. `build()` now syncs to Store after enrichment. Content hashes computed pre-sync, stripped before JSON write. | +| `blastradius/cli.py` | Added `_cmd_db`, `_cmd_history`, `_cmd_changed_since`. Updated `_cmd_impact` to handle `--as-of`. Added `history`, `changed-since`, `db` subparsers. Added `--as-of` arg to `impact`. | | `pyproject.toml` | Added `dev = ["pytest>=7"]`, `semantic = ["sqlite-vec"]` extras. Added `[tool.pytest.ini_options]`. | ### Should NOT be touched | File | Why | |---|---| -| `codeindex/analyze.py` | Full repo analysis dispatcher — untouched by design. Per-file incremental parsing is a future optimization. | -| `codeindex/analyzers/*.py` | All existing analyzers — Phase 1–3 don't modify them. | -| `codeindex/mcp_server.py` | Existing 6 MCP tools — Phase 4 adds new ones alongside, never modifies existing. | -| `codeindex/impact.py` | `compute_blast_radius()` is called directly by `Store.as_of_impact()`. Don't change its signature. | -| `docs/codeindex-temporal-knowledge-graph.md` | The spec. Don't edit it; read it. | +| `blastradius/analyze.py` | Full repo analysis dispatcher — untouched by design. Per-file incremental parsing is a future optimization. | +| `blastradius/analyzers/*.py` | All existing analyzers — Phase 1–3 don't modify them. | +| `blastradius/mcp_server.py` | Existing 6 MCP tools — Phase 4 adds new ones alongside, never modifies existing. | +| `blastradius/impact.py` | `compute_blast_radius()` is called directly by `Store.as_of_impact()`. Don't change its signature. | +| `docs/blastradius-temporal-knowledge-graph.md` | The spec. Don't edit it; read it. | ### Phase 3 will create ``` -codeindex/semantic/__init__.py -codeindex/semantic/provider.py # EmbeddingProvider ABC + OpenAI-compat HTTP impl -codeindex/semantic/search.py # Hybrid query engine (semantic + FTS + graph, RRF) +blastradius/semantic/__init__.py +blastradius/semantic/provider.py # EmbeddingProvider ABC + OpenAI-compat HTTP impl +blastradius/semantic/search.py # Hybrid query engine (semantic + FTS + graph, RRF) ``` -And add to `Store` in `codeindex/store/db.py`: +And add to `Store` in `blastradius/store/db.py`: - `vec_symbols` virtual table (gated behind `sqlite-vec` import) - `upsert_embeddings(symbol_ids_and_vecs)` method - `semantic_search(query_vec, k, reachable)` method @@ -153,13 +153,13 @@ Python's `sqlite3.executescript()` commits any pending transaction before runnin The format is: ` blob \n\n` for each object. The parser in `history.py:_git_cat_file_batch()` reads byte-by-byte with `pos` tracking. Don't replace this with individual `git show` calls — it's deliberately batch for performance. **System pip is broken on this machine** -Don't use `pip3` or `python3` directly. Always use `.venv/bin/pytest`, `.venv/bin/codeindex`, `.venv/bin/python3`. The miniforge Python 3.9 has a corrupted `packaging` package that causes `pip install` to fail. +Don't use `pip3` or `python3` directly. Always use `.venv/bin/pytest`, `.venv/bin/blastradius`, `.venv/bin/python3`. The miniforge Python 3.9 has a corrupted `packaging` package that causes `pip install` to fail. **`sqlite-vec` for Phase 3 must be feature-detected at runtime** -The design doc requires graceful degradation: if `sqlite-vec` can't load (not installed, wrong platform), `codeindex search` falls back to FTS + graph with a clear log message. Never `import sqlite_vec` at module level — always try/except around the extension load, same pattern as the FTS DDL in `_apply_schema()`. +The design doc requires graceful degradation: if `sqlite-vec` can't load (not installed, wrong platform), `blastradius search` falls back to FTS + graph with a clear log message. Never `import sqlite_vec` at module level — always try/except around the extension load, same pattern as the FTS DDL in `_apply_schema()`. **Dependency direction is a hard constraint** -`codeindex/core/`, `store/`, and `temporal/` must NEVER import from `codeindex/graph/` or `codeindex/semantic/`. The import arrow points downward only. The current `store/db.py` has a local import of `compute_blast_radius` inside `as_of_impact()` — this is a `codeindex.impact` import (core layer), which is fine. +`blastradius/core/`, `store/`, and `temporal/` must NEVER import from `blastradius/graph/` or `blastradius/semantic/`. The import arrow points downward only. The current `store/db.py` has a local import of `compute_blast_radius` inside `as_of_impact()` — this is a `blastradius.impact` import (core layer), which is fine. --- @@ -188,7 +188,7 @@ except ImportError: **No comments on obvious code.** Comments only for non-obvious constraints or workarounds (e.g., the FTS content= issue). No docstrings on trivial methods. -**JSON schema is frozen.** `codeindex.json` and `symbolindex.json` schemas don't change. Any new data lives in the DB only, exposed via new CLI/MCP commands. +**JSON schema is frozen.** `blastradius.json` and `symbolindex.json` schemas don't change. Any new data lives in the DB only, exposed via new CLI/MCP commands. --- @@ -206,15 +206,15 @@ except ImportError: ## 8. Do Not Touch -- **`codeindex/mcp_server.py`** — existing 6 tools work and are not under change until Phase 4. Phase 4 adds new tools alongside; it doesn't modify existing ones. -- **`codeindex/analyze.py` and `codeindex/analyzers/`** — the parsing pipeline is deliberately untouched. Any incremental per-file parsing is a future optimization, not Phase 3. -- **The `codeindex.json` / `symbolindex.json` schema** — frozen per constraint #3. +- **`blastradius/mcp_server.py`** — existing 6 tools work and are not under change until Phase 4. Phase 4 adds new tools alongside; it doesn't modify existing ones. +- **`blastradius/analyze.py` and `blastradius/analyzers/`** — the parsing pipeline is deliberately untouched. Any incremental per-file parsing is a future optimization, not Phase 3. +- **The `blastradius.json` / `symbolindex.json` schema** — frozen per constraint #3. - **`.venv/`** — don't recreate or upgrade. Python 3.11, packages installed via `uv pip install -e ".[dev,yaml]"`. -- **`docs/codeindex-temporal-knowledge-graph.md`** — the spec document. Read-only. +- **`docs/blastradius-temporal-knowledge-graph.md`** — the spec document. Read-only. - **`SCHEMA_VERSION = "1"` in `store/db.py`** — don't bump until Phase 3 actually adds the `vec_symbols` table, and then also add a migration path. --- ## 9. Resume Command -> Read `HANDOFF.md` and `docs/codeindex-temporal-knowledge-graph.md` (§6.4, §6.5, §8 Phase 3, §9 Constraints). Then implement Phase 3: create `codeindex/semantic/` with an embedding provider interface and OpenAI-compatible HTTP client (stdlib urllib only, no new runtime deps for the client), add `vec_symbols` to the Store gated behind `sqlite-vec`, implement `codeindex search` with hybrid RRF retrieval (semantic + FTS + graph), and add degradation tests. Use `.venv/bin/pytest` to run tests. Do not modify existing analyzers, the JSON export schema, or any existing MCP tools. Run all 11 existing tests before committing — they must stay green. +> Read `HANDOFF.md` and `docs/blastradius-temporal-knowledge-graph.md` (§6.4, §6.5, §8 Phase 3, §9 Constraints). Then implement Phase 3: create `blastradius/semantic/` with an embedding provider interface and OpenAI-compatible HTTP client (stdlib urllib only, no new runtime deps for the client), add `vec_symbols` to the Store gated behind `sqlite-vec`, implement `blastradius search` with hybrid RRF retrieval (semantic + FTS + graph), and add degradation tests. Use `.venv/bin/pytest` to run tests. Do not modify existing analyzers, the JSON export schema, or any existing MCP tools. Run all 11 existing tests before committing — they must stay green. diff --git a/README.md b/README.md index 04f1915..e22889a 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# codeindex +# blastradius **Temporal code knowledge graph** with blast-radius impact scoring, semantic symbol search, and git-history-aware dependency analysis — for AI-assisted development. Point it at any project — Python, JavaScript/TypeScript, Go, Ruby, Rust, Java, PHP, and more — and get: -- A persistent **SQLite graph store** at `/.codeindex/index.db` — incremental, queryable, temporal -- A `codeindex.json` dependency index written directly into your repo (preserved for backward compatibility) +- A persistent **SQLite graph store** at `/.blastradius/index.db` — incremental, queryable, temporal +- A `blastradius.json` dependency index written directly into your repo (preserved for backward compatibility) - Per-file blast-radius scores (how many files break if this one changes), including **historical as-of queries** - A `symbolindex.json` symbol map so AI can find any function/class without scanning every file - **Hybrid semantic search** over symbols: natural-language queries fused with keyword + graph expansion @@ -20,14 +20,14 @@ No build step. No npm. Zero required runtime dependencies — SQLite is stdlib. ## Install ```bash -pip install codeindex +pip install blastradius ``` Or from source: ```bash -git clone https://github.com/scheidydude/codeindex -cd codeindex +git clone https://github.com/scheidydude/blastradius +cd blastradius pip install -e . ``` @@ -36,23 +36,23 @@ pip install -e . ## Quickstart ```bash -# Build the dependency index (also writes to .codeindex/index.db) -codeindex analyze ./myapp +# Build the dependency index (also writes to .blastradius/index.db) +blastradius analyze ./myapp # Build the symbol index (where every function and class lives) -codeindex symbols ./myapp +blastradius symbols ./myapp # See blast radius for a file before touching it -codeindex impact src/auth.py +blastradius impact src/auth.py # Search symbols with natural language (no embedding endpoint needed — FTS fallback) -codeindex search "validate auth token" +blastradius search "validate auth token" # See what changed since a release tag -codeindex changed-since v1.2.0 +blastradius changed-since v1.2.0 # Launch the visualization UI -codeindex serve --viz --repo ./myapp +blastradius serve --viz --repo ./myapp open http://localhost:8080 ``` @@ -60,26 +60,26 @@ open http://localhost:8080 ## Commands -### `codeindex analyze` +### `blastradius analyze` ```bash -codeindex analyze [REPO_PATH] [--output PATH] [--watch] +blastradius analyze [REPO_PATH] [--output PATH] [--watch] ``` -Analyzes the repo and writes `codeindex.json` to the repo root. Detects 12+ languages automatically. +Analyzes the repo and writes `blastradius.json` to the repo root. Detects 12+ languages automatically. | Flag | Default | Description | |------|---------|-------------| | `REPO_PATH` | `.` | Path to repo root | -| `--output` | `/codeindex.json` | Override output path | +| `--output` | `/blastradius.json` | Override output path | | `--watch` | off | Re-index on file changes (requires `watchdog`) | --- -### `codeindex symbols` +### `blastradius symbols` ```bash -codeindex symbols [REPO_PATH] [--output PATH] [--inline] [--index PATH] +blastradius symbols [REPO_PATH] [--output PATH] [--inline] [--index PATH] [--claude-md] [--claude-md-path PATH] [--all-symbols] ``` @@ -90,7 +90,7 @@ Builds a symbol index — a map of every function, class, struct, and type to it | Flag | Description | |------|-------------| | _(none)_ | Write a standalone `symbolindex.json` | -| `--inline` | Embed symbols into each node in `codeindex.json` instead | +| `--inline` | Embed symbols into each node in `blastradius.json` instead | | `--claude-md` | Append a compressed symbol summary to `CLAUDE.md` | Both `--inline` and `--claude-md` can be combined in a single run. @@ -101,7 +101,7 @@ Both `--inline` and `--claude-md` can be combined in a single run. |------|---------|-------------| | `REPO_PATH` | `.` | Path to repo root | | `--output` | `/symbolindex.json` | Output path (standalone mode) | -| `--index` | auto-discovered | Path to `codeindex.json` (for `--inline`) | +| `--index` | auto-discovered | Path to `blastradius.json` (for `--inline`) | | `--claude-md-path` | `/CLAUDE.md` | Override CLAUDE.md path | | `--all-symbols` | off | Include non-exported symbols in CLAUDE.md (default: exported only) | @@ -109,19 +109,19 @@ Both `--inline` and `--claude-md` can be combined in a single run. ```bash # Standalone symbol index -codeindex symbols ./myapp +blastradius symbols ./myapp -# Embed into codeindex.json (one file for blast radius + symbols) -codeindex symbols ./myapp --inline +# Embed into blastradius.json (one file for blast radius + symbols) +blastradius symbols ./myapp --inline # Write CLAUDE.md summary so Claude Code loads symbols automatically -codeindex symbols ./myapp --claude-md +blastradius symbols ./myapp --claude-md # All three at once -codeindex symbols ./myapp --inline --claude-md +blastradius symbols ./myapp --inline --claude-md # Re-generate when code changes -codeindex symbols ./myapp --inline --claude-md +blastradius symbols ./myapp --inline --claude-md ``` **Why it matters:** Claude Code and other AI tools normally scan every file to find a function definition. With a symbol index, Claude can load one file, do an O(1) lookup, and open only the relevant file — cutting token usage 60–90% on symbol-location tasks. @@ -130,10 +130,10 @@ codeindex symbols ./myapp --inline --claude-md --- -### `codeindex impact` +### `blastradius impact` ```bash -codeindex impact FILE [--index PATH] [--out FILE] [--json] [--as-of REF] +blastradius impact FILE [--index PATH] [--out FILE] [--json] [--as-of REF] ``` Shows the blast-radius impact for a specific file: direct dependents, transitive dependents, blast score, and risk level. @@ -158,23 +158,23 @@ Risk: HIGH — affects 7/42 files (16.7% of codebase) | Flag | Description | |------|-------------| -| `--index PATH` | Path to `codeindex.json` (auto-discovered if omitted) | +| `--index PATH` | Path to `blastradius.json` (auto-discovered if omitted) | | `--out FILE` | Write a markdown report to this file | | `--json` | Output raw JSON | | `--as-of REF` | Compute blast radius at a historical commit/ref instead of HEAD | --- -### `codeindex search` +### `blastradius search` ```bash -codeindex search QUERY [--k N] [--as-of REF] [--db PATH] [--json] +blastradius search QUERY [--k N] [--as-of REF] [--db PATH] [--json] ``` Hybrid semantic + keyword + graph symbol search. Finds relevant functions and classes without knowing their exact names. **Retrieval signals fused with Reciprocal Rank Fusion (RRF):** -1. **Semantic KNN** — embedding similarity (requires `codeindex[semantic]` + a configured endpoint) +1. **Semantic KNN** — embedding similarity (requires `blastradius[semantic]` + a configured endpoint) 2. **FTS5 keyword** — full-text search over symbol names, signatures, and docstrings 3. **Graph expansion** — structurally adjacent symbols from dependent/dependency files @@ -182,16 +182,16 @@ Degrades gracefully: if no embedding endpoint is configured, falls back to FTS + ```bash # Keyword + graph search (no embedding setup required) -codeindex search "validate auth token" +blastradius search "validate auth token" -# Full semantic search (requires CODEINDEX_EMBEDDING_* env vars) -CODEINDEX_EMBEDDING_ENDPOINT=http://localhost:11434 \ -CODEINDEX_EMBEDDING_MODEL=nomic-embed-text \ -CODEINDEX_EMBEDDING_DIMS=768 \ -codeindex search "validate auth token" +# Full semantic search (requires BLASTRADIUS_EMBEDDING_* env vars) +BLASTRADIUS_EMBEDDING_ENDPOINT=http://localhost:11434 \ +BLASTRADIUS_EMBEDDING_MODEL=nomic-embed-text \ +BLASTRADIUS_EMBEDDING_DIMS=768 \ +blastradius search "validate auth token" # Historical search — symbols visible at a release tag -codeindex search "token validation" --as-of v1.2.0 +blastradius search "token validation" --as-of v1.2.0 ``` | Flag | Default | Description | @@ -199,15 +199,15 @@ codeindex search "token validation" --as-of v1.2.0 | `QUERY` | — | Natural-language or keyword query | | `--k N` | `10` | Number of results to return | | `--as-of REF` | HEAD | Restrict to symbols visible at this commit/ref | -| `--db PATH` | auto-discovered | Path to `.codeindex/index.db` | +| `--db PATH` | auto-discovered | Path to `.blastradius/index.db` | | `--json` | off | Output raw JSON | --- -### `codeindex history` +### `blastradius history` ```bash -codeindex history [REPO_PATH] [--since REF] [--max-commits N] [--json] +blastradius history [REPO_PATH] [--since REF] [--max-commits N] [--json] ``` Backfills temporal graph data from git history — without any working-tree checkouts. Reads blobs via `git cat-file --batch` and stamps `first_seen_commit` / `last_seen_commit` on every file, edge, and symbol. @@ -216,10 +216,10 @@ Run this once after initial setup to enable `--as-of` queries across the full hi ```bash # Backfill up to 1000 commits (default) -codeindex history . +blastradius history . # Backfill only recent history -codeindex history . --since v1.0.0 --max-commits 200 +blastradius history . --since v1.0.0 --max-commits 200 ``` | Flag | Default | Description | @@ -231,16 +231,16 @@ codeindex history . --since v1.0.0 --max-commits 200 --- -### `codeindex changed-since` +### `blastradius changed-since` ```bash -codeindex changed-since REF [--repo PATH] [--db PATH] [--json] +blastradius changed-since REF [--repo PATH] [--db PATH] [--json] ``` Lists files and dependency edges added or removed since a commit, branch, or tag. ``` -$ codeindex changed-since v1.2.0 +$ blastradius changed-since v1.2.0 Changes since v1.2.0: Added files (2): @@ -255,22 +255,22 @@ Changes since v1.2.0: |------|---------|-------------| | `REF` | — | Commit hash, branch, or tag to compare against | | `--repo PATH` | `.` | Repo root (for git operations) | -| `--db PATH` | auto-discovered | Path to `.codeindex/index.db` | +| `--db PATH` | auto-discovered | Path to `.blastradius/index.db` | | `--json` | off | Output raw JSON | --- -### `codeindex db` +### `blastradius db` ```bash -codeindex db status [--db PATH] [--json] -codeindex db migrate [--db PATH] +blastradius db status [--db PATH] [--json] +blastradius db migrate [--db PATH] ``` -Manages the SQLite store at `/.codeindex/index.db`. +Manages the SQLite store at `/.blastradius/index.db`. ``` -$ codeindex db status +$ blastradius db status schema_version : 2 repo_root : /Users/alice/myapp last_indexed_commit : a3f2e1c8 @@ -282,20 +282,20 @@ embedding_dims : 768 vec_symbols : enabled ``` -`db migrate` applies any pending schema migrations automatically (also runs on every `codeindex analyze`). +`db migrate` applies any pending schema migrations automatically (also runs on every `blastradius analyze`). --- -### `codeindex serve` +### `blastradius serve` ```bash -codeindex serve --viz [--repo PATH] [--port PORT] [--watch] -codeindex serve --mcp +blastradius serve --viz [--repo PATH] [--port PORT] [--watch] +blastradius serve --mcp ``` `--viz` launches an interactive visualization UI in your browser (5 modes: 2D force graph, 3D network, dependency matrix, treemap, infrastructure graph). -`--mcp` starts a stdio MCP server that exposes codeindex tools directly to Claude and other MCP clients. +`--mcp` starts a stdio MCP server that exposes blastradius tools directly to Claude and other MCP clients. **MCP tools:** @@ -317,8 +317,8 @@ codeindex serve --mcp ```json { "mcpServers": { - "codeindex": { - "command": "codeindex", + "blastradius": { + "command": "blastradius", "args": ["serve", "--mcp"] } } @@ -327,23 +327,23 @@ codeindex serve --mcp --- -### `codeindex lookup` +### `blastradius lookup` ```bash -codeindex lookup SYMBOL [--index PATH] [--json] +blastradius lookup SYMBOL [--index PATH] [--json] ``` Finds where a function, class, struct, or other symbol is defined. Queries the SQLite DB (same source as `search`), falling back to `symbolindex.json` if no DB is present. Prints 5 lines of source context around the definition. ``` -$ codeindex lookup compute_blast_radius -codeindex/impact.py:6 compute_blast_radius (function) +$ blastradius lookup compute_blast_radius +blastradius/impact.py:6 compute_blast_radius (function) > 6 | def compute_blast_radius(nodes: list[dict], links: list[dict]) -> dict[str, dict]: 7 | """...""" 8 | ... -$ codeindex lookup AuthService +$ blastradius lookup AuthService src/auth.py:44 AuthService (class) methods: login, logout, refresh > 44 | class AuthService: @@ -359,16 +359,16 @@ If a symbol isn't found, it's likely a third-party import — only symbols defin --- -### `codeindex dependencies` +### `blastradius dependencies` ```bash -codeindex dependencies FILE [--index PATH] [--json] +blastradius dependencies FILE [--index PATH] [--json] ``` Shows what a file imports and what imports it, plus its blast score. ``` -$ codeindex dependencies src/auth.py +$ blastradius dependencies src/auth.py File: src/auth.py (blast score: 8.5) Imports (3): @@ -383,21 +383,21 @@ Imported by (2): | Flag | Description | |------|-------------| -| `--index PATH` | Path to `codeindex.json` (auto-discovered if omitted) | +| `--index PATH` | Path to `blastradius.json` (auto-discovered if omitted) | | `--json` | Output raw JSON | --- -### `codeindex high-blast` +### `blastradius high-blast` ```bash -codeindex high-blast [--threshold N] [--index PATH] [--json] +blastradius high-blast [--threshold N] [--index PATH] [--json] ``` Lists all files whose blast score exceeds the threshold, sorted by score descending. Useful for identifying the riskiest files before a refactor. ``` -$ codeindex high-blast --threshold 5 +$ blastradius high-blast --threshold 5 Files with blast score ≥ 5.0 (3 found) 13.0 src/db.py (12d / 2t) @@ -410,21 +410,21 @@ Files with blast score ≥ 5.0 (3 found) | Flag | Default | Description | |------|---------|-------------| | `--threshold N` | `5` | Minimum blast score to include | -| `--index PATH` | auto-discovered | Path to `codeindex.json` | +| `--index PATH` | auto-discovered | Path to `blastradius.json` | | `--json` | off | Output raw JSON | --- -### `codeindex symbol-blast` +### `blastradius symbol-blast` ```bash -codeindex symbol-blast FILE [--json] +blastradius symbol-blast FILE [--json] ``` Per-export blast radius for a file. Lists every exported symbol with the count and exact file paths of importers that reference it by name. Useful when a file exports many symbols and you only need to touch one — lets you confirm the others are safe. ``` -$ codeindex symbol-blast lib/db/schema.ts +$ blastradius symbol-blast lib/db/schema.ts Symbol-level blast radius: lib/db/schema.ts @@ -449,10 +449,10 @@ Symbol-level blast radius: lib/db/schema.ts --- -### `codeindex install-hook` +### `blastradius install-hook` ```bash -codeindex install-hook [--repo PATH] [--threshold N] [--strict] [--remove] +blastradius install-hook [--repo PATH] [--threshold N] [--strict] [--remove] ``` Installs a git pre-commit hook that warns when staged files exceed the blast score threshold. @@ -465,7 +465,7 @@ Installs a git pre-commit hook that warns when staged files exceed the blast sco --- -## Using codeindex in another repo with Claude +## Using blastradius in another repo with Claude Three workflows, ordered by automation level. @@ -476,25 +476,25 @@ Claude gets symbol lookup, dependency, and impact tools it calls automatically. **One-time setup:** ```bash cd /your/other/repo -codeindex analyze . -codeindex symbols . +blastradius analyze . +blastradius symbols . ``` Register the MCP server with Claude Code using `claude mcp add`. Use `--scope project` to limit it to this repo, or `--scope global` to use it everywhere: ```bash # Project-scoped (recommended — stored in .claude/settings.json) -claude mcp add --scope project codeindex -- /path/to/codeindex serve --mcp +claude mcp add --scope project blastradius -- /path/to/blastradius serve --mcp # Global (available in all repos) -claude mcp add --scope global codeindex -- /path/to/codeindex serve --mcp +claude mcp add --scope global blastradius -- /path/to/blastradius serve --mcp ``` -Find the full path to your codeindex binary with `which codeindex`, then substitute it above. +Find the full path to your blastradius binary with `which blastradius`, then substitute it above. ```bash # Example with conda install -claude mcp add --scope project codeindex -- /opt/homebrew/Caskroom/miniforge/base/bin/codeindex serve --mcp +claude mcp add --scope project blastradius -- /opt/homebrew/Caskroom/miniforge/base/bin/blastradius serve --mcp ``` Verify it registered: @@ -502,14 +502,14 @@ Verify it registered: claude mcp list ``` -> **Note:** Do not use `"command": "codeindex"` with a bare name — Claude Code does not inherit your shell PATH, so the binary won't be found unless you use the absolute path. +> **Note:** Do not use `"command": "blastradius"` with a bare name — Claude Code does not inherit your shell PATH, so the binary won't be found unless you use the absolute path. Claude now has all 10 MCP tools available in every session. When it needs to find `processPayment`, it calls `lookup_symbol("processPayment")` and gets `src/billing.py:142` back in one shot — no file scanning. When it needs to find code that validates auth tokens without knowing the exact name, it calls `semantic_search("validate auth token")`. **Keep the index fresh:** ```bash # Auto-rebuild on file changes (leave running in a terminal) -codeindex symbols . --watch +blastradius symbols . --watch ``` --- @@ -520,7 +520,7 @@ Symbol table is embedded in `CLAUDE.md` so it loads into every session automatic ```bash cd /your/other/repo -codeindex symbols . --claude-md +blastradius symbols . --claude-md ``` This upserts a `symbolindex` code fence into `CLAUDE.md`. Every Claude Code session in that repo loads it at startup. Claude can answer "where is `X` defined?" from context alone with zero tool calls. @@ -530,7 +530,7 @@ This upserts a `symbolindex` code fence into `CLAUDE.md`. Every Claude Code sess **Keep it fresh:** ```bash # Re-run after significant refactors -codeindex analyze . && codeindex symbols . --claude-md +blastradius analyze . && blastradius symbols . --claude-md ``` --- @@ -540,15 +540,15 @@ codeindex analyze . && codeindex symbols . --claude-md For large repos where the `--claude-md` section would be too large, use the MCP server for lookups and add a short hint to `CLAUDE.md` so Claude reaches for the tool first: ```bash -codeindex analyze . -codeindex symbols . +blastradius analyze . +blastradius symbols . ``` Then add to `CLAUDE.md`: ```markdown -## Codeindex +## Blastradius Symbol index: `symbolindex.json` — use the `lookup_symbol` MCP tool before grepping for any function or class. -Dependency index: `codeindex.json` — use `get_impact` before modifying high-blast files. +Dependency index: `blastradius.json` — use `get_impact` before modifying high-blast files. ``` This costs almost no tokens but primes Claude to use the index rather than defaulting to grep. @@ -561,20 +561,20 @@ The same data available via MCP is also accessible directly from the terminal: ```bash # Find where a symbol is defined (shows code snippet) -codeindex lookup MyClassName -codeindex lookup process_payment --json +blastradius lookup MyClassName +blastradius lookup process_payment --json # Show what a file imports and what depends on it -codeindex dependencies src/auth.py +blastradius dependencies src/auth.py # List the riskiest files to change -codeindex high-blast --threshold 5 +blastradius high-blast --threshold 5 # Blast-radius report before touching a file -codeindex impact src/auth.py +blastradius impact src/auth.py # Per-export blast radius — which importers use each exported symbol -codeindex symbol-blast lib/db/schema.ts +blastradius symbol-blast lib/db/schema.ts ``` --- @@ -586,7 +586,7 @@ codeindex symbol-blast lib/db/schema.ts | Daily driver repo, active feature work | MCP server | | Medium repo, frequent symbol lookups | CLAUDE.md injection | | Large repo (1000+ files) | MCP server + short CLAUDE.md hint | -| Quick one-off in an unfamiliar repo | `codeindex symbols . --claude-md`, delete after | +| Quick one-off in an unfamiliar repo | `blastradius symbols . --claude-md`, delete after | | Terminal / scripting use | CLI commands (`lookup`, `dependencies`, `high-blast`) | --- @@ -612,7 +612,7 @@ codeindex symbol-blast lib/db/schema.ts ## Output schemas -### `codeindex.json` +### `blastradius.json` ```json { @@ -647,7 +647,7 @@ codeindex symbol-blast lib/db/schema.ts } ``` -The `symbols` field is only present when `codeindex symbols --inline` has been run. +The `symbols` field is only present when `blastradius symbols --inline` has been run. --- @@ -694,7 +694,7 @@ The `symbols` field is only present when `codeindex symbols --inline` has been r - *"Where is `verify_token` defined?"* → `symbols["verify_token"][0].file` + `.line` — O(1) - *"What symbols live in `src/auth.py`?"* → `file_symbols["src/auth.py"]` — O(1) -- *"What's the blast radius of changing `verify_token`?"* → cross-reference `codeindex.json` via the file +- *"What's the blast radius of changing `verify_token`?"* → cross-reference `blastradius.json` via the file --- @@ -703,15 +703,15 @@ The `symbols` field is only present when `codeindex symbols --inline` has been r When `--claude-md` is used, a compact section is upserted into `CLAUDE.md` bounded by HTML comment markers so re-runs update in place: ``` - + ## Symbol Index -_Generated by codeindex. Update: `codeindex symbols --claude-md`_ +_Generated by blastradius. Update: `blastradius symbols --claude-md`_ ```symbolindex src/auth.py: verify_token:fn:18 AuthService:cls:44[login,logout,refresh] src/db.py: connect:fn:12 query:fn:28 close:fn:55 ``` - + ``` Format per symbol: `name:kind_abbr:line[methods...]` @@ -721,10 +721,10 @@ Kind abbreviations: `fn` function · `cls` class · `st` struct · `en` enum · ## AI workflow comparison -| Task | Without codeindex | With symbolindex.json | +| Task | Without blastradius | With symbolindex.json | |------|-------------------|----------------------| | Find where `process_payment` is defined | Grep / scan ~200 files | Load 1 file, O(1) lookup | -| Understand blast radius of a change | Manual tracing | `codeindex impact ` | +| Understand blast radius of a change | Manual tracing | `blastradius impact ` | | Load only relevant context | Full repo scan | File + line from symbol map | | Estimated token savings | baseline | **60–90% on symbol tasks** | @@ -734,31 +734,31 @@ Kind abbreviations: `fn` function · `cls` class · `st` struct · `en` enum · | Package | Purpose | Install | |---------|---------|---------| -| `sqlite-vec` | Semantic vector search in `codeindex search` and `semantic_search` MCP tool | `pip install 'codeindex[semantic]'` | -| `watchdog` | `--watch` file change detection | `pip install 'codeindex[watch]'` | -| `PyYAML` | Better Docker Compose / CI YAML parsing | `pip install 'codeindex[yaml]'` | -| `tomli` | Rust `Cargo.toml` on Python < 3.11 | `pip install 'codeindex[toml]'` | +| `sqlite-vec` | Semantic vector search in `blastradius search` and `semantic_search` MCP tool | `pip install 'blastradius[semantic]'` | +| `watchdog` | `--watch` file change detection | `pip install 'blastradius[watch]'` | +| `PyYAML` | Better Docker Compose / CI YAML parsing | `pip install 'blastradius[yaml]'` | +| `tomli` | Rust `Cargo.toml` on Python < 3.11 | `pip install 'blastradius[toml]'` | ### Semantic search configuration Semantic search requires a self-hosted embedding endpoint (Ollama, LM Studio, llama.cpp server, or any OpenAI-compatible `/v1/embeddings` API) and the `sqlite-vec` extension. ```bash -pip install 'codeindex[semantic]' +pip install 'blastradius[semantic]' # Set env vars (add to your shell profile or .env) -export CODEINDEX_EMBEDDING_ENDPOINT=http://localhost:11434 -export CODEINDEX_EMBEDDING_MODEL=nomic-embed-text -export CODEINDEX_EMBEDDING_DIMS=768 +export BLASTRADIUS_EMBEDDING_ENDPOINT=http://localhost:11434 +export BLASTRADIUS_EMBEDDING_MODEL=nomic-embed-text +export BLASTRADIUS_EMBEDDING_DIMS=768 # Re-index to generate embeddings -codeindex analyze ./myapp +blastradius analyze ./myapp # Search -codeindex search "validate JWT token" +blastradius search "validate JWT token" ``` -Without these env vars, `codeindex search` and the `semantic_search` MCP tool fall back to FTS5 keyword + graph search — no crash, no config required. +Without these env vars, `blastradius search` and the `semantic_search` MCP tool fall back to FTS5 keyword + graph search — no crash, no config required. --- diff --git a/benchmark/test_cli.py b/benchmark/test_cli.py index 333b8f6..f113936 100644 --- a/benchmark/test_cli.py +++ b/benchmark/test_cli.py @@ -3,9 +3,9 @@ CLI integration tests for: lookup, dependencies, high-blast. Usage: - python benchmark/test_cli.py [--repo PATH] [--codeindex PATH] + python benchmark/test_cli.py [--repo PATH] [--blastradius PATH] -Reads codeindex.json and symbolindex.json from --repo to build fixtures, +Reads blastradius.json and symbolindex.json from --repo to build fixtures, then invokes the CLI as a subprocess and validates stdout/exit codes. """ from __future__ import annotations @@ -170,27 +170,39 @@ def test_high_blast(exe: str, repo: str, r: Results, index_path: str) -> None: # Entry point # --------------------------------------------------------------------------- def main() -> None: + if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(encoding="utf-8", errors="replace") + if hasattr(sys.stderr, "reconfigure"): + sys.stderr.reconfigure(encoding="utf-8", errors="replace") + import argparse parser = argparse.ArgumentParser(description="CLI integration tests: lookup, dependencies, high-blast") parser.add_argument("--repo", default=".", help="Repo path (default: .)") - parser.add_argument("--codeindex", default="codeindex", help="Path to codeindex executable (default: codeindex)") + parser.add_argument("--blastradius", default="blastradius", help="Path to blastradius executable (default: blastradius)") args = parser.parse_args() repo = str(Path(args.repo).resolve()) - exe = args.codeindex + exe = args.blastradius print(f"Repo : {repo}") print(f"Command : {exe}") - index_path = str(Path(repo) / "codeindex.json") + index_path = str(Path(repo) / "blastradius.json") sym_path = str(Path(repo) / "symbolindex.json") if not Path(index_path).exists(): - print(f"ERROR: {index_path} not found. Run: codeindex analyze {repo}", file=sys.stderr) - sys.exit(1) + print(f"Building index: {exe} analyze {repo}") + rc, out, err = run([exe, "analyze", repo], repo) + if rc != 0 or not Path(index_path).exists(): + print(f"ERROR: Failed to build {index_path}.\n{err}", file=sys.stderr) + sys.exit(1) + if not Path(sym_path).exists(): - print(f"ERROR: {sym_path} not found. Run: codeindex symbols {repo}", file=sys.stderr) - sys.exit(1) + print(f"Building symbol index: {exe} symbols {repo}") + rc, out, err = run([exe, "symbols", repo], repo) + if rc != 0 or not Path(sym_path).exists(): + print(f"ERROR: Failed to build {sym_path}.\n{err}", file=sys.stderr) + sys.exit(1) # Pick probe fixtures from actual index data index_data = json.loads(Path(index_path).read_text()) @@ -206,7 +218,7 @@ def main() -> None: # Probe symbol: first symbol in index symbols = sym_data.get("symbols", {}) if not symbols: - print("ERROR: symbolindex.json has no symbols. Run: codeindex symbols {repo}", file=sys.stderr) + print("ERROR: symbolindex.json has no symbols. Run: blastradius symbols {repo}", file=sys.stderr) sys.exit(1) sym_name, sym_matches = next(iter(symbols.items())) expected_file = Path(sym_matches[0]["file"]).name diff --git a/benchmark/test_mcp.py b/benchmark/test_mcp.py index 73b3fa0..db7866a 100644 --- a/benchmark/test_mcp.py +++ b/benchmark/test_mcp.py @@ -1,12 +1,12 @@ #!/usr/bin/env python3 """ -MCP server integration test — exercises all 6 tools via real JSON-RPC stdio. +MCP server integration test — exercises all tools via real JSON-RPC stdio. Usage: - python benchmark/test_mcp.py [--repo PATH] [--codeindex PATH] + python benchmark/test_mcp.py [--repo PATH] [--blastradius PATH] Starts the MCP server as a subprocess, sends JSON-RPC messages, validates responses. -All 6 tools tested: analyze_repo, get_impact, get_dependencies, +All tools tested: analyze_repo, get_impact, get_dependencies, get_high_blast_files, build_symbol_index, lookup_symbol. """ from __future__ import annotations @@ -252,18 +252,23 @@ def test_unknown_tool(client: MCPClient, r: Results) -> None: # Entry point # --------------------------------------------------------------------------- def main() -> None: + if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(encoding="utf-8", errors="replace") + if hasattr(sys.stderr, "reconfigure"): + sys.stderr.reconfigure(encoding="utf-8", errors="replace") + import argparse parser = argparse.ArgumentParser(description="MCP server integration tests") parser.add_argument("--repo", default=".", help="Repo path (default: .)") parser.add_argument( - "--codeindex", - default="codeindex", - help="Path to codeindex executable (default: codeindex)", + "--blastradius", + default="blastradius", + help="Path to blastradius executable (default: blastradius)", ) args = parser.parse_args() repo = str(Path(args.repo).resolve()) - command = [args.codeindex, "serve", "--mcp"] + command = [args.blastradius, "serve", "--mcp"] print(f"Repo : {repo}") print(f"Command : {' '.join(command)}") @@ -273,7 +278,7 @@ def main() -> None: time.sleep(0.3) # let server start # Discover test fixtures from the repo's own indexes - index_file = Path(repo) / "codeindex.json" + index_file = Path(repo) / "blastradius.json" sym_file = Path(repo) / "symbolindex.json" probe_file = "." diff --git a/blastradius/__init__.py b/blastradius/__init__.py new file mode 100644 index 0000000..70440c1 --- /dev/null +++ b/blastradius/__init__.py @@ -0,0 +1,2 @@ +"""blastradius — repo dependency analyzer with blast-radius impact scoring.""" +__version__ = "0.1.0" diff --git a/codeindex/analyze.py b/blastradius/analyze.py similarity index 96% rename from codeindex/analyze.py rename to blastradius/analyze.py index efcced5..e81ff46 100644 --- a/codeindex/analyze.py +++ b/blastradius/analyze.py @@ -3,13 +3,13 @@ import sys from pathlib import Path -from codeindex.analyzers import ( +from blastradius.analyzers import ( python_analyzer, js_analyzer, css_analyzer, go_analyzer, ruby_analyzer, rust_analyzer, java_analyzer, php_analyzer, docker_analyzer, ci_analyzer, schema_analyzer, terraform_analyzer, ) -from codeindex.analyzers.cross_lang_analyzer import find_api_boundaries -from codeindex.analyzers.monorepo_analyzer import detect_workspaces, assign_packages +from blastradius.analyzers.cross_lang_analyzer import find_api_boundaries +from blastradius.analyzers.monorepo_analyzer import detect_workspaces, assign_packages _SKIP = { "__pycache__", ".venv", "venv", "env", ".git", diff --git a/codeindex/analyzers/__init__.py b/blastradius/analyzers/__init__.py similarity index 100% rename from codeindex/analyzers/__init__.py rename to blastradius/analyzers/__init__.py diff --git a/codeindex/analyzers/base.py b/blastradius/analyzers/base.py similarity index 100% rename from codeindex/analyzers/base.py rename to blastradius/analyzers/base.py diff --git a/codeindex/analyzers/ci_analyzer.py b/blastradius/analyzers/ci_analyzer.py similarity index 100% rename from codeindex/analyzers/ci_analyzer.py rename to blastradius/analyzers/ci_analyzer.py diff --git a/codeindex/analyzers/cross_lang_analyzer.py b/blastradius/analyzers/cross_lang_analyzer.py similarity index 100% rename from codeindex/analyzers/cross_lang_analyzer.py rename to blastradius/analyzers/cross_lang_analyzer.py diff --git a/codeindex/analyzers/css_analyzer.py b/blastradius/analyzers/css_analyzer.py similarity index 100% rename from codeindex/analyzers/css_analyzer.py rename to blastradius/analyzers/css_analyzer.py diff --git a/codeindex/analyzers/docker_analyzer.py b/blastradius/analyzers/docker_analyzer.py similarity index 100% rename from codeindex/analyzers/docker_analyzer.py rename to blastradius/analyzers/docker_analyzer.py diff --git a/codeindex/analyzers/go_analyzer.py b/blastradius/analyzers/go_analyzer.py similarity index 100% rename from codeindex/analyzers/go_analyzer.py rename to blastradius/analyzers/go_analyzer.py diff --git a/codeindex/analyzers/java_analyzer.py b/blastradius/analyzers/java_analyzer.py similarity index 100% rename from codeindex/analyzers/java_analyzer.py rename to blastradius/analyzers/java_analyzer.py diff --git a/codeindex/analyzers/js_analyzer.py b/blastradius/analyzers/js_analyzer.py similarity index 100% rename from codeindex/analyzers/js_analyzer.py rename to blastradius/analyzers/js_analyzer.py diff --git a/codeindex/analyzers/monorepo_analyzer.py b/blastradius/analyzers/monorepo_analyzer.py similarity index 100% rename from codeindex/analyzers/monorepo_analyzer.py rename to blastradius/analyzers/monorepo_analyzer.py diff --git a/codeindex/analyzers/php_analyzer.py b/blastradius/analyzers/php_analyzer.py similarity index 100% rename from codeindex/analyzers/php_analyzer.py rename to blastradius/analyzers/php_analyzer.py diff --git a/codeindex/analyzers/python_analyzer.py b/blastradius/analyzers/python_analyzer.py similarity index 100% rename from codeindex/analyzers/python_analyzer.py rename to blastradius/analyzers/python_analyzer.py diff --git a/codeindex/analyzers/ruby_analyzer.py b/blastradius/analyzers/ruby_analyzer.py similarity index 100% rename from codeindex/analyzers/ruby_analyzer.py rename to blastradius/analyzers/ruby_analyzer.py diff --git a/codeindex/analyzers/rust_analyzer.py b/blastradius/analyzers/rust_analyzer.py similarity index 100% rename from codeindex/analyzers/rust_analyzer.py rename to blastradius/analyzers/rust_analyzer.py diff --git a/codeindex/analyzers/schema_analyzer.py b/blastradius/analyzers/schema_analyzer.py similarity index 100% rename from codeindex/analyzers/schema_analyzer.py rename to blastradius/analyzers/schema_analyzer.py diff --git a/codeindex/analyzers/terraform_analyzer.py b/blastradius/analyzers/terraform_analyzer.py similarity index 100% rename from codeindex/analyzers/terraform_analyzer.py rename to blastradius/analyzers/terraform_analyzer.py diff --git a/codeindex/cli.py b/blastradius/cli.py similarity index 88% rename from codeindex/cli.py rename to blastradius/cli.py index fcd59d6..3f3c6dd 100644 --- a/codeindex/cli.py +++ b/blastradius/cli.py @@ -1,4 +1,4 @@ -"""codeindex CLI entry point.""" +"""blastradius CLI entry point.""" from __future__ import annotations import argparse import json @@ -7,7 +7,7 @@ def _cmd_analyze(args: argparse.Namespace) -> None: - from codeindex.index import build + from blastradius.index import build repo = args.repo output = Path(args.output) if args.output else None @@ -16,7 +16,7 @@ def _cmd_analyze(args: argparse.Namespace) -> None: from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler except ImportError: - print("watchdog not installed — run: pip install 'codeindex[watch]'", file=sys.stderr) + print("watchdog not installed — run: pip install 'blastradius[watch]'", file=sys.stderr) sys.exit(1) import threading @@ -26,7 +26,7 @@ def _cmd_analyze(args: argparse.Namespace) -> None: ".rb", ".go", ".rs", ".java", ".kt", ".php", ".yml", ".yaml", ".sql", ".prisma", } - dest = output or (Path(repo).resolve() / "codeindex.json") + dest = output or (Path(repo).resolve() / "blastradius.json") class _Watcher(FileSystemEventHandler): def __init__(self): @@ -62,16 +62,16 @@ def on_modified(self, event): def _cmd_impact(args: argparse.Namespace) -> None: - from codeindex.index import load, find_index, find_db, db_path_for, INDEX_FILENAME - from codeindex.index import git_reachable, git_resolve - from codeindex.impact import compute_blast_radius - from codeindex.reporter import format_stdout, format_markdown + from blastradius.index import load, find_index, find_db, db_path_for, INDEX_FILENAME + from blastradius.index import git_reachable, git_resolve + from blastradius.impact import compute_blast_radius + from blastradius.reporter import format_stdout, format_markdown as_of = getattr(args, "as_of", None) if as_of: # ── temporal path: query DB for historical blast radius ──────────────── - from codeindex.store import Store + from blastradius.store import Store repo = Path(args.file).resolve().parent db_path = find_db(repo) or find_db(Path.cwd()) @@ -79,7 +79,7 @@ def _cmd_impact(args: argparse.Namespace) -> None: # Try discovering from cwd upward db_path = find_db(Path.cwd()) if not db_path or not db_path.exists(): - print("No .codeindex/index.db found — run: codeindex analyze ", file=sys.stderr) + print("No .blastradius/index.db found — run: blastradius analyze ", file=sys.stderr) sys.exit(1) # Resolve repo root from DB meta @@ -122,7 +122,7 @@ def _cmd_impact(args: argparse.Namespace) -> None: if blast is None: print( f"No temporal data for {file_id} at {as_of}. " - "Run `codeindex history` to backfill, or `codeindex analyze` at each commit.", + "Run `blastradius history` to backfill, or `blastradius analyze` at each commit.", file=sys.stderr, ) sys.exit(1) @@ -152,7 +152,7 @@ def _cmd_impact(args: argparse.Namespace) -> None: index_path = find_index(Path.cwd()) if not index_path: print( - f"No {INDEX_FILENAME} found. Run: codeindex analyze ", + f"No {INDEX_FILENAME} found. Run: blastradius analyze ", file=sys.stderr, ) sys.exit(1) @@ -201,10 +201,10 @@ def _cmd_impact(args: argparse.Namespace) -> None: def _cmd_serve(args: argparse.Namespace) -> None: if args.mcp: - from codeindex.mcp_server import serve + from blastradius.mcp_server import serve serve() else: - from codeindex.viz_server import serve + from blastradius.viz_server import serve output = Path(args.output) if getattr(args, "output", None) else None serve( repo_path=args.repo, @@ -215,11 +215,11 @@ def _cmd_serve(args: argparse.Namespace) -> None: def _cmd_symbols(args: argparse.Namespace) -> None: - from codeindex.symbols import ( + from blastradius.symbols import ( build_symbol_index, write_standalone, write_inline, write_claude_md, SYMBOL_INDEX_FILENAME, ) - from codeindex.index import find_index, INDEX_FILENAME + from blastradius.index import find_index, INDEX_FILENAME repo = Path(args.repo).resolve() symbol_data = build_symbol_index(str(repo)) @@ -231,7 +231,7 @@ def _cmd_symbols(args: argparse.Namespace) -> None: index_path = find_index(repo) if not index_path: print( - f"No {INDEX_FILENAME} found — run: codeindex analyze first, " + f"No {INDEX_FILENAME} found — run: blastradius analyze first, " "or pass --index ", file=sys.stderr, ) @@ -247,8 +247,8 @@ def _cmd_symbols(args: argparse.Namespace) -> None: def _cmd_lookup(args: argparse.Namespace) -> None: - from codeindex.index import db_path_for, find_index, INDEX_FILENAME - from codeindex.store import Store + from blastradius.index import db_path_for, find_index, INDEX_FILENAME + from blastradius.store import Store name = args.name matches = [] @@ -274,13 +274,13 @@ def _cmd_lookup(args: argparse.Namespace) -> None: # Fall back to symbolindex.json when DB not available if not matches and not args.index: try: - from codeindex.mcp_server import _resolve_symbol_index + from blastradius.mcp_server import _resolve_symbol_index sym_data = _resolve_symbol_index(None) matches = sym_data.get("symbols", {}).get(name, []) except FileNotFoundError: pass elif not matches and args.index: - from codeindex.mcp_server import _resolve_symbol_index + from blastradius.mcp_server import _resolve_symbol_index sym_data = _resolve_symbol_index(args.index) matches = sym_data.get("symbols", {}).get(name, []) @@ -309,13 +309,13 @@ def _cmd_lookup(args: argparse.Namespace) -> None: def _cmd_dependencies(args: argparse.Namespace) -> None: - from codeindex.index import load, find_index, INDEX_FILENAME + from blastradius.index import load, find_index, INDEX_FILENAME if args.index: index_path = Path(args.index) else: index_path = find_index(Path(args.file).parent) or find_index(Path.cwd()) if not index_path: - print(f"No {INDEX_FILENAME} found. Run: codeindex analyze ", file=sys.stderr) + print(f"No {INDEX_FILENAME} found. Run: blastradius analyze ", file=sys.stderr) sys.exit(1) data = load(index_path) fp = args.file @@ -347,13 +347,13 @@ def _cmd_dependencies(args: argparse.Namespace) -> None: def _cmd_high_blast(args: argparse.Namespace) -> None: - from codeindex.index import load, find_index, INDEX_FILENAME + from blastradius.index import load, find_index, INDEX_FILENAME if args.index: index_path = Path(args.index) else: index_path = find_index(Path.cwd()) if not index_path: - print(f"No {INDEX_FILENAME} found. Run: codeindex analyze ", file=sys.stderr) + print(f"No {INDEX_FILENAME} found. Run: blastradius analyze ", file=sys.stderr) sys.exit(1) data = load(index_path) threshold = args.threshold @@ -371,7 +371,7 @@ def _cmd_high_blast(args: argparse.Namespace) -> None: for n in results ]}, indent=2)) else: - print(f"Files with blast score ≥ {threshold} ({len(results)} found)\n") + print(f"Files with blast score >= {threshold} ({len(results)} found)\n") for n in results: loc = n.get("loc", 0) loc_str = f" {loc} loc" if loc else "" @@ -381,15 +381,15 @@ def _cmd_high_blast(args: argparse.Namespace) -> None: def _cmd_symbol_blast(args: argparse.Namespace) -> None: """Per-export blast radius: which importers reference each exported symbol.""" - from codeindex.index import find_db + from blastradius.index import find_db import re as _re db_path = find_db(Path.cwd()) if not db_path or not db_path.exists(): - print("No .codeindex/index.db found — run: codeindex analyze ", file=sys.stderr) + print("No .blastradius/index.db found — run: blastradius analyze ", file=sys.stderr) sys.exit(1) - from codeindex.store import Store + from blastradius.store import Store store = Store(db_path) file_path = args.file @@ -444,12 +444,12 @@ def _cmd_symbol_blast(args: argparse.Namespace) -> None: def _cmd_db(args: argparse.Namespace) -> None: - from codeindex.index import find_db - from codeindex.store import Store + from blastradius.index import find_db + from blastradius.store import Store db_path = Path(args.db) if getattr(args, "db", None) else find_db(Path.cwd()) if not db_path or not db_path.exists(): - print("No .codeindex/index.db found — run: codeindex analyze ", file=sys.stderr) + print("No .blastradius/index.db found — run: blastradius analyze ", file=sys.stderr) sys.exit(1) if args.db_command == "status": @@ -477,9 +477,9 @@ def _cmd_db(args: argparse.Namespace) -> None: def _cmd_history(args: argparse.Namespace) -> None: - from codeindex.index import find_db, db_path_for - from codeindex.store import Store - from codeindex.temporal import backfill + from blastradius.index import find_db, db_path_for + from blastradius.store import Store + from blastradius.temporal import backfill repo = Path(args.repo).resolve() db_path = find_db(repo) or db_path_for(repo) @@ -503,12 +503,12 @@ def _cmd_history(args: argparse.Namespace) -> None: def _cmd_changed_since(args: argparse.Namespace) -> None: - from codeindex.index import find_db, git_reachable, git_resolve, git_modified - from codeindex.store import Store + from blastradius.index import find_db, git_reachable, git_resolve, git_modified + from blastradius.store import Store db_path = Path(args.db) if getattr(args, "db", None) else find_db(Path.cwd()) if not db_path or not db_path.exists(): - print("No .codeindex/index.db found — run: codeindex analyze ", file=sys.stderr) + print("No .blastradius/index.db found — run: blastradius analyze ", file=sys.stderr) sys.exit(1) repo = Path(args.repo).resolve() if getattr(args, "repo", None) else Path.cwd() @@ -592,36 +592,36 @@ def _cmd_changed_since(args: argparse.Namespace) -> None: if result.get("warning"): # history has never been run — backfill will resolve these print(f"\n ({analyze_origin_count} of {len(ae)} added edge(s) first seen at last-analyzed HEAD" - f" — run `codeindex history` to date them accurately)") + f" — run `blastradius history` to date them accurately)") else: # history has been run; these edges predate the first analyze print(f"\n ({analyze_origin_count} of {len(ae)} added edge(s) are bootstrap-gap artifacts:" - f" they existed before the first `codeindex analyze` and cannot be dated further)") + f" they existed before the first `blastradius analyze` and cannot be dated further)") if not any([mf, af, rf, ae, re_]): print(" (no changes detected)") def _cmd_search(args: argparse.Namespace) -> None: - from codeindex.index import find_db, git_reachable, git_resolve - from codeindex.store import Store - from codeindex.semantic.search import hybrid_search + from blastradius.index import find_db, git_reachable, git_resolve + from blastradius.store import Store + from blastradius.semantic.search import hybrid_search db_path = Path(args.db) if getattr(args, "db", None) else find_db(Path.cwd()) if not db_path or not db_path.exists(): - print("No .codeindex/index.db found — run: codeindex analyze ", file=sys.stderr) + print("No .blastradius/index.db found — run: blastradius analyze ", file=sys.stderr) sys.exit(1) store = Store(db_path) provider = None import os - endpoint = os.environ.get("CODEINDEX_EMBEDDING_ENDPOINT", "") - model = os.environ.get("CODEINDEX_EMBEDDING_MODEL", "") - dims_str = os.environ.get("CODEINDEX_EMBEDDING_DIMS", "") + endpoint = os.environ.get("BLASTRADIUS_EMBEDDING_ENDPOINT", "") + model = os.environ.get("BLASTRADIUS_EMBEDDING_MODEL", "") + dims_str = os.environ.get("BLASTRADIUS_EMBEDDING_DIMS", "") if endpoint and model and dims_str: try: dims = int(dims_str) - from codeindex.semantic.provider import OpenAIEmbeddingProvider + from blastradius.semantic.provider import OpenAIEmbeddingProvider provider = OpenAIEmbeddingProvider(endpoint=endpoint, model=model, dims=dims) except Exception: pass @@ -673,7 +673,7 @@ def _cmd_search(args: argparse.Namespace) -> None: def _cmd_install_hook(args: argparse.Namespace) -> None: - from codeindex.hook import install + from blastradius.hook import install install( repo_path=args.repo, threshold=args.threshold, @@ -684,21 +684,21 @@ def _cmd_install_hook(args: argparse.Namespace) -> None: def _build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( - prog="codeindex", + prog="blastradius", description="Repo dependency analyzer with blast-radius impact scoring.", ) sub = parser.add_subparsers(dest="command", required=True) # ── analyze ──────────────────────────────────────────────────────────── - p_analyze = sub.add_parser("analyze", help="Analyze a repo and write codeindex.json") + p_analyze = sub.add_parser("analyze", help="Analyze a repo and write blastradius.json") p_analyze.add_argument("repo", nargs="?", default=".", help="Path to repo root (default: .)") - p_analyze.add_argument("--output", help="Output path (default: /codeindex.json)") + p_analyze.add_argument("--output", help="Output path (default: /blastradius.json)") p_analyze.add_argument("--watch", action="store_true", help="Re-index on file changes") # ── impact ───────────────────────────────────────────────────────────── p_impact = sub.add_parser("impact", help="Show blast-radius impact for a file") p_impact.add_argument("file", help="File path to assess") - p_impact.add_argument("--index", help="Path to codeindex.json (auto-discovered if omitted)") + p_impact.add_argument("--index", help="Path to blastradius.json (auto-discovered if omitted)") p_impact.add_argument("--out", help="Write markdown report to this file") p_impact.add_argument("--json", action="store_true", help="Output raw JSON") p_impact.add_argument("--as-of", dest="as_of", metavar="REF", @@ -712,7 +712,7 @@ def _build_parser() -> argparse.ArgumentParser: p_serve.add_argument("--repo", default=".", help="Repo to analyze (viz mode)") p_serve.add_argument("--port", type=int, default=8080, help="Port for viz server") p_serve.add_argument("--watch", action="store_true", help="Watch for file changes (viz mode)") - p_serve.add_argument("--output", help="codeindex.json path override (viz mode)") + p_serve.add_argument("--output", help="blastradius.json path override (viz mode)") # ── symbols ──────────────────────────────────────────────────────────────── p_sym = sub.add_parser("symbols", help="Build a symbol index (functions, classes, exports)") @@ -722,11 +722,11 @@ def _build_parser() -> argparse.ArgumentParser: ) p_sym.add_argument( "--inline", action="store_true", - help="Embed symbols into codeindex.json nodes instead of a separate file", + help="Embed symbols into blastradius.json nodes instead of a separate file", ) p_sym.add_argument( "--index", - help="Path to codeindex.json for --inline mode (auto-discovered if omitted)", + help="Path to blastradius.json for --inline mode (auto-discovered if omitted)", ) p_sym.add_argument( "--claude-md", dest="claude_md", action="store_true", @@ -750,13 +750,13 @@ def _build_parser() -> argparse.ArgumentParser: # ── dependencies ─────────────────────────────────────────────────────── p_deps = sub.add_parser("dependencies", help="Show imports and imported-by for a file") p_deps.add_argument("file", help="File path to inspect") - p_deps.add_argument("--index", help="Path to codeindex.json (auto-discovered if omitted)") + p_deps.add_argument("--index", help="Path to blastradius.json (auto-discovered if omitted)") p_deps.add_argument("--json", action="store_true", help="Output raw JSON") # ── high-blast ───────────────────────────────────────────────────────── p_hb = sub.add_parser("high-blast", help="List files above a blast score threshold") p_hb.add_argument("--threshold", type=float, default=5.0, help="Minimum blast score (default: 5)") - p_hb.add_argument("--index", help="Path to codeindex.json (auto-discovered if omitted)") + p_hb.add_argument("--index", help="Path to blastradius.json (auto-discovered if omitted)") p_hb.add_argument("--json", action="store_true", help="Output raw JSON") # ── symbol-blast ────────────────────────────────────────────────────────── @@ -768,7 +768,7 @@ def _build_parser() -> argparse.ArgumentParser: p_sb.add_argument("--json", action="store_true", help="Output raw JSON") # ── db ───────────────────────────────────────────────────────────────────── - p_db = sub.add_parser("db", help="Manage the SQLite store (.codeindex/index.db)") + p_db = sub.add_parser("db", help="Manage the SQLite store (.blastradius/index.db)") p_db.add_argument("--db", help="Path to index.db (auto-discovered if omitted)") p_db.add_argument("--json", action="store_true", help="Output raw JSON (status only)") db_sub = p_db.add_subparsers(dest="db_command", required=True) @@ -824,6 +824,11 @@ def _build_parser() -> argparse.ArgumentParser: def main() -> None: + if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(encoding="utf-8", errors="replace") + if hasattr(sys.stderr, "reconfigure"): + sys.stderr.reconfigure(encoding="utf-8", errors="replace") + parser = _build_parser() args = parser.parse_args() diff --git a/codeindex/hook.py b/blastradius/hook.py similarity index 73% rename from codeindex/hook.py rename to blastradius/hook.py index d171b6f..62b685c 100644 --- a/codeindex/hook.py +++ b/blastradius/hook.py @@ -5,21 +5,21 @@ import sys from pathlib import Path -HOOK_MARKER = "# codeindex-hook" +HOOK_MARKER = "# blastradius-hook" HOOK_TEMPLATE = """\ #!/usr/bin/env bash {marker} -# Installed by codeindex. Remove this file or run `codeindex install-hook --remove` to disable. +# Installed by blastradius. Remove this file or run `blastradius install-hook --remove` to disable. THRESHOLD={threshold} STRICT={strict} -# Find codeindex.json by walking up from repo root -INDEX=$(git rev-parse --show-toplevel)/codeindex.json +# Find blastradius.json by walking up from repo root +INDEX=$(git rev-parse --show-toplevel)/blastradius.json if [ ! -f "$INDEX" ]; then - echo "[codeindex] No codeindex.json found — skipping impact check. Run: codeindex analyze ." + echo "[blastradius] No blastradius.json found — skipping impact check. Run: blastradius analyze ." exit 0 fi @@ -33,14 +33,14 @@ WARNED=0 while IFS= read -r FILE; do if [ -z "$FILE" ]; then continue; fi - OUTPUT=$(codeindex impact "$FILE" --index "$INDEX" 2>/dev/null) + OUTPUT=$(blastradius impact "$FILE" --index "$INDEX" 2>/dev/null) if [ $? -ne 0 ]; then continue; fi SCORE=$(echo "$OUTPUT" | grep -oP 'Blast Score: \\K[0-9.]+') if [ -z "$SCORE" ]; then continue; fi SCORE_INT=$(echo "$SCORE" | cut -d. -f1) if [ "$SCORE_INT" -ge "$THRESHOLD" ] 2>/dev/null; then echo "" - echo "[codeindex] HIGH BLAST RADIUS: $FILE (score: $SCORE)" + echo "[blastradius] HIGH BLAST RADIUS: $FILE (score: $SCORE)" echo "$OUTPUT" echo "" WARNED=1 @@ -48,7 +48,7 @@ done <<< "$STAGED" if [ "$WARNED" -eq 1 ] && [ "$STRICT" = "1" ]; then - echo "[codeindex] Commit blocked (--strict mode). Review impact above." + echo "[blastradius] Commit blocked (--strict mode). Review impact above." exit 1 fi @@ -70,9 +70,9 @@ def install(repo_path: str, threshold: int = 10, strict: bool = False, remove: b content = hook_path.read_text() if HOOK_MARKER in content: hook_path.unlink() - print(f"Removed codeindex pre-commit hook from {hook_path}") + print(f"Removed blastradius pre-commit hook from {hook_path}") else: - print("Pre-commit hook exists but was not installed by codeindex — not removing.") + print("Pre-commit hook exists but was not installed by blastradius — not removing.") else: print("No pre-commit hook found.") return @@ -81,7 +81,7 @@ def install(repo_path: str, threshold: int = 10, strict: bool = False, remove: b content = hook_path.read_text() if HOOK_MARKER not in content: print( - f"A pre-commit hook already exists at {hook_path} and was not installed by codeindex.\n" + f"A pre-commit hook already exists at {hook_path} and was not installed by blastradius.\n" "Add the following manually or use a hook manager (husky, pre-commit, lefthook).", file=sys.stderr, ) @@ -99,7 +99,7 @@ def install(repo_path: str, threshold: int = 10, strict: bool = False, remove: b mode = "strict (blocks commit)" if strict else "warn-only" print( - f"Installed codeindex pre-commit hook → {hook_path}\n" + f"Installed blastradius pre-commit hook → {hook_path}\n" f" threshold: {threshold} mode: {mode}\n" - "Run `codeindex install-hook --remove` to uninstall." + "Run `blastradius install-hook --remove` to uninstall." ) diff --git a/codeindex/impact.py b/blastradius/impact.py similarity index 100% rename from codeindex/impact.py rename to blastradius/impact.py diff --git a/codeindex/index.py b/blastradius/index.py similarity index 88% rename from codeindex/index.py rename to blastradius/index.py index 5519977..be006f1 100644 --- a/codeindex/index.py +++ b/blastradius/index.py @@ -1,9 +1,9 @@ # Copyright 2026 David Scheiderman # Licensed under the Apache License, Version 2.0 -"""Build and persist codeindex.json in the target repo root. +"""Build and persist blastradius.json in the target repo root. Phase-1 change: build() now also syncs graph data to a SQLite store at -/.codeindex/index.db. The JSON write path is unchanged so existing +/.blastradius/index.db. The JSON write path is unchanged so existing consumers keep working without modification. """ from __future__ import annotations @@ -14,12 +14,12 @@ import sys from pathlib import Path -from codeindex.analyze import analyze -from codeindex.impact import compute_blast_radius, enrich_nodes, enrich_links -from codeindex.store import Store +from blastradius.analyze import analyze +from blastradius.impact import compute_blast_radius, enrich_nodes, enrich_links +from blastradius.store import Store -INDEX_FILENAME = "codeindex.json" -_DB_DIR = ".codeindex" +INDEX_FILENAME = "blastradius.json" +_DB_DIR = ".blastradius" _DB_NAME = "index.db" @@ -109,13 +109,13 @@ def _content_hash(root: Path, rel_path: str) -> str | None: def _embed_new_symbols(store) -> None: """Embed symbols that lack a vector entry, if an embedding endpoint is configured. - Reads CODEINDEX_EMBEDDING_ENDPOINT / _MODEL / _DIMS from env. + Reads BLASTRADIUS_EMBEDDING_ENDPOINT / _MODEL / _DIMS from env. Silently skips if any required env var is missing or if the call fails. """ import os - endpoint = os.environ.get("CODEINDEX_EMBEDDING_ENDPOINT", "") - model = os.environ.get("CODEINDEX_EMBEDDING_MODEL", "") - dims_str = os.environ.get("CODEINDEX_EMBEDDING_DIMS", "") + endpoint = os.environ.get("BLASTRADIUS_EMBEDDING_ENDPOINT", "") + model = os.environ.get("BLASTRADIUS_EMBEDDING_MODEL", "") + dims_str = os.environ.get("BLASTRADIUS_EMBEDDING_DIMS", "") if not (endpoint and model and dims_str): return try: @@ -131,7 +131,7 @@ def _embed_new_symbols(store) -> None: return try: - from codeindex.semantic.provider import OpenAIEmbeddingProvider + from blastradius.semantic.provider import OpenAIEmbeddingProvider provider = OpenAIEmbeddingProvider(endpoint=endpoint, model=model, dims=dims) ids = [p[0] for p in pairs] texts = [p[1] for p in pairs] @@ -184,7 +184,7 @@ def build(repo_path: str, output: Path | None = None) -> dict: # Build and sync symbol index try: - from codeindex.symbols import build_symbol_index + from blastradius.symbols import build_symbol_index symbol_data = build_symbol_index(str(root)) store.sync_symbols(symbol_data, commit=head_commit) except Exception as exc: @@ -216,13 +216,13 @@ def build(repo_path: str, output: Path | None = None) -> dict: def load(index_path: Path) -> dict: if not index_path.exists(): raise FileNotFoundError( - f"{index_path} not found — run: codeindex analyze " + f"{index_path} not found — run: blastradius analyze " ) return json.loads(index_path.read_text()) def find_index(start: Path) -> Path | None: - """Walk up from start looking for codeindex.json.""" + """Walk up from start looking for blastradius.json.""" current = start.resolve() for _ in range(10): candidate = current / INDEX_FILENAME @@ -236,7 +236,7 @@ def find_index(start: Path) -> Path | None: def find_db(start: Path) -> Path | None: - """Walk up from start looking for .codeindex/index.db.""" + """Walk up from start looking for .blastradius/index.db.""" current = start.resolve() for _ in range(10): candidate = current / _DB_DIR / _DB_NAME diff --git a/codeindex/mcp_server.py b/blastradius/mcp_server.py similarity index 90% rename from codeindex/mcp_server.py rename to blastradius/mcp_server.py index 19a9882..fc7de0b 100644 --- a/codeindex/mcp_server.py +++ b/blastradius/mcp_server.py @@ -1,21 +1,21 @@ # Copyright 2026 David Scheiderman # Licensed under the Apache License, Version 2.0 -"""Stdio MCP server — exposes codeindex tools to Claude and other MCP clients.""" +"""Stdio MCP server — exposes blastradius tools to Claude and other MCP clients.""" from __future__ import annotations import json import sys from pathlib import Path -from codeindex.index import build, load, find_index, find_db, INDEX_FILENAME -from codeindex.index import git_reachable, git_resolve, git_modified -from codeindex.impact import compute_blast_radius -from codeindex.reporter import format_markdown -from codeindex.symbols import SYMBOL_INDEX_FILENAME +from blastradius.index import build, load, find_index, find_db, INDEX_FILENAME +from blastradius.index import git_reachable, git_resolve, git_modified +from blastradius.impact import compute_blast_radius +from blastradius.reporter import format_markdown +from blastradius.symbols import SYMBOL_INDEX_FILENAME TOOLS = [ { "name": "analyze_repo", - "description": "Analyze a repository and build/refresh its codeindex.json dependency index.", + "description": "Analyze a repository and build/refresh its blastradius.json dependency index.", "inputSchema": { "type": "object", "properties": { @@ -43,7 +43,7 @@ }, "index_path": { "type": "string", - "description": "Path to codeindex.json. Auto-discovered if omitted.", + "description": "Path to blastradius.json. Auto-discovered if omitted.", }, }, "required": ["file_path"], @@ -61,7 +61,7 @@ }, "index_path": { "type": "string", - "description": "Path to codeindex.json. Auto-discovered if omitted.", + "description": "Path to blastradius.json. Auto-discovered if omitted.", }, }, "required": ["file_path"], @@ -79,7 +79,7 @@ }, "index_path": { "type": "string", - "description": "Path to codeindex.json. Auto-discovered if omitted.", + "description": "Path to blastradius.json. Auto-discovered if omitted.", }, }, }, @@ -150,7 +150,7 @@ }, "db_path": { "type": "string", - "description": "Path to .codeindex/index.db. Auto-discovered from cwd if omitted.", + "description": "Path to .blastradius/index.db. Auto-discovered from cwd if omitted.", }, }, "required": ["query"], @@ -161,8 +161,8 @@ "description": ( "Compute blast-radius impact for a file at a historical commit/ref. " "Shows which files depended on it at that point in time, not just at HEAD. " - "Requires codeindex analyze to have been run at (or near) the target commit, " - "or codeindex history to have backfilled temporal data." + "Requires blastradius analyze to have been run at (or near) the target commit, " + "or blastradius history to have backfilled temporal data." ), "inputSchema": { "type": "object", @@ -177,7 +177,7 @@ }, "db_path": { "type": "string", - "description": "Path to .codeindex/index.db. Auto-discovered from cwd if omitted.", + "description": "Path to .blastradius/index.db. Auto-discovered from cwd if omitted.", }, }, "required": ["file"], @@ -208,7 +208,7 @@ }, "db_path": { "type": "string", - "description": "Path to .codeindex/index.db. Auto-discovered from cwd if omitted.", + "description": "Path to .blastradius/index.db. Auto-discovered from cwd if omitted.", }, }, "required": ["file"], @@ -230,7 +230,7 @@ }, "db_path": { "type": "string", - "description": "Path to .codeindex/index.db. Auto-discovered from cwd if omitted.", + "description": "Path to .blastradius/index.db. Auto-discovered from cwd if omitted.", }, }, "required": ["ref"], @@ -245,7 +245,7 @@ def _resolve_index(index_path: str | None) -> dict: discovered = find_index(Path.cwd()) if not discovered: raise FileNotFoundError( - f"No {INDEX_FILENAME} found. Run: codeindex analyze " + f"No {INDEX_FILENAME} found. Run: blastradius analyze " ) return load(discovered) @@ -342,13 +342,13 @@ def _resolve_symbol_index(symbol_index_path: str | None) -> dict: p = _find_symbol_index(Path.cwd()) if not p or not p.exists(): raise FileNotFoundError( - f"No {SYMBOL_INDEX_FILENAME} found. Run: codeindex symbols " + f"No {SYMBOL_INDEX_FILENAME} found. Run: blastradius symbols " ) return json.loads(p.read_text()) def _call_lookup_symbol(params: dict) -> dict: - from codeindex.store import Store + from blastradius.store import Store name = params["name"] matches = [] @@ -394,7 +394,7 @@ def _call_lookup_symbol(params: dict) -> dict: def _call_build_symbol_index(params: dict) -> dict: - from codeindex.symbols import build_symbol_index as _build, write_standalone # noqa: PLC0415 + from blastradius.symbols import build_symbol_index as _build, write_standalone # noqa: PLC0415 repo_path = params["repo_path"] symbol_data = _build(repo_path) out = Path(repo_path) / SYMBOL_INDEX_FILENAME @@ -409,7 +409,7 @@ def _call_build_symbol_index(params: dict) -> dict: def _resolve_db(params: dict): """Return an open Store for the db_path in params or auto-discovered from cwd.""" - from codeindex.store import Store + from blastradius.store import Store db_path_str = params.get("db_path") if db_path_str: db_path = Path(db_path_str) @@ -417,24 +417,24 @@ def _resolve_db(params: dict): db_path = find_db(Path.cwd()) if not db_path or not db_path.exists(): raise FileNotFoundError( - "No .codeindex/index.db found — run: codeindex analyze " + "No .blastradius/index.db found — run: blastradius analyze " ) return Store(db_path) def _call_semantic_search(params: dict) -> dict: - from codeindex.semantic.search import hybrid_search + from blastradius.semantic.search import hybrid_search import os store = _resolve_db(params) provider = None - endpoint = os.environ.get("CODEINDEX_EMBEDDING_ENDPOINT", "") - model = os.environ.get("CODEINDEX_EMBEDDING_MODEL", "") - dims_str = os.environ.get("CODEINDEX_EMBEDDING_DIMS", "") + endpoint = os.environ.get("BLASTRADIUS_EMBEDDING_ENDPOINT", "") + model = os.environ.get("BLASTRADIUS_EMBEDDING_MODEL", "") + dims_str = os.environ.get("BLASTRADIUS_EMBEDDING_DIMS", "") if endpoint and model and dims_str: try: - from codeindex.semantic.provider import OpenAIEmbeddingProvider + from blastradius.semantic.provider import OpenAIEmbeddingProvider provider = OpenAIEmbeddingProvider( endpoint=endpoint, model=model, dims=int(dims_str) ) @@ -504,7 +504,7 @@ def _call_temporal_impact(params: dict) -> dict: return { "error": ( f"No temporal data for {file_id} at {as_of}. " - "Run `codeindex history` to backfill or `codeindex analyze` at each commit." + "Run `blastradius history` to backfill or `blastradius analyze` at each commit." ) } return { @@ -657,7 +657,7 @@ def err(code, message): return ok({ "protocolVersion": "2024-11-05", "capabilities": {"tools": {}}, - "serverInfo": {"name": "codeindex", "version": "0.1.0"}, + "serverInfo": {"name": "blastradius", "version": "0.1.0"}, }) if method == "notifications/initialized": @@ -690,7 +690,7 @@ def err(code, message): def serve() -> None: - print("[codeindex MCP] ready on stdio", file=sys.stderr) + print("[blastradius MCP] ready on stdio", file=sys.stderr) for line in sys.stdin: line = line.strip() if not line: diff --git a/codeindex/reporter.py b/blastradius/reporter.py similarity index 97% rename from codeindex/reporter.py rename to blastradius/reporter.py index 58f6381..50492b6 100644 --- a/codeindex/reporter.py +++ b/blastradius/reporter.py @@ -105,7 +105,7 @@ def format_markdown( ) lines.append("") lines.append( - "_Generated by [codeindex](https://github.com/scheidydudes/codeindex)_" + "_Generated by [blastradius](https://github.com/scheidydudes/blastradius)_" ) return "\n".join(lines) diff --git a/codeindex/semantic/__init__.py b/blastradius/semantic/__init__.py similarity index 58% rename from codeindex/semantic/__init__.py rename to blastradius/semantic/__init__.py index 0b56cb7..9e68cf6 100644 --- a/codeindex/semantic/__init__.py +++ b/blastradius/semantic/__init__.py @@ -2,7 +2,7 @@ # Licensed under the Apache License, Version 2.0 from __future__ import annotations -from codeindex.semantic.provider import EmbeddingProvider, OpenAIEmbeddingProvider -from codeindex.semantic.search import hybrid_search +from blastradius.semantic.provider import EmbeddingProvider, OpenAIEmbeddingProvider +from blastradius.semantic.search import hybrid_search __all__ = ["EmbeddingProvider", "OpenAIEmbeddingProvider", "hybrid_search"] diff --git a/codeindex/semantic/provider.py b/blastradius/semantic/provider.py similarity index 93% rename from codeindex/semantic/provider.py rename to blastradius/semantic/provider.py index cde1bd0..e981e93 100644 --- a/codeindex/semantic/provider.py +++ b/blastradius/semantic/provider.py @@ -2,8 +2,8 @@ # Licensed under the Apache License, Version 2.0 """Embedding provider interface and OpenAI-compatible HTTP client. -Dependency rule: this module must not import from codeindex.store, codeindex.graph, -or any other codeindex layer. It is a pure provider abstraction. +Dependency rule: this module must not import from blastradius.store, blastradius.graph, +or any other blastradius layer. It is a pure provider abstraction. """ from __future__ import annotations diff --git a/codeindex/semantic/search.py b/blastradius/semantic/search.py similarity index 92% rename from codeindex/semantic/search.py rename to blastradius/semantic/search.py index c438253..c201b38 100644 --- a/codeindex/semantic/search.py +++ b/blastradius/semantic/search.py @@ -7,8 +7,8 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from codeindex.store.db import Store - from codeindex.semantic.provider import EmbeddingProvider + from blastradius.store.db import Store + from blastradius.semantic.provider import EmbeddingProvider _RRF_K = 60 # standard RRF constant @@ -67,10 +67,10 @@ def _note(sid: int, signal: str) -> None: if sem_ids: ranked_lists.append(sem_ids) except Exception as exc: - print(f"[codeindex] semantic search skipped: {exc}", file=sys.stderr) + print(f"[blastradius] semantic search skipped: {exc}", file=sys.stderr) elif provider is not None and not store._has_vec: print( - "[codeindex] sqlite-vec not available — falling back to FTS + graph search.", + "[blastradius] sqlite-vec not available — falling back to FTS + graph search.", file=sys.stderr, ) @@ -82,7 +82,7 @@ def _note(sid: int, signal: str) -> None: if fts_ids: ranked_lists.append(fts_ids) except Exception as exc: - print(f"[codeindex] FTS search skipped: {exc}", file=sys.stderr) + print(f"[blastradius] FTS search skipped: {exc}", file=sys.stderr) # 3. Graph expansion: supplement when primary signals are sparse. # Skip if FTS (or semantic) already returned enough results — graph neighbours diff --git a/codeindex/store/__init__.py b/blastradius/store/__init__.py similarity index 74% rename from codeindex/store/__init__.py rename to blastradius/store/__init__.py index 7107058..87fd348 100644 --- a/codeindex/store/__init__.py +++ b/blastradius/store/__init__.py @@ -2,6 +2,6 @@ # Licensed under the Apache License, Version 2.0 from __future__ import annotations -from codeindex.store.db import Store, SCHEMA_VERSION +from blastradius.store.db import Store, SCHEMA_VERSION __all__ = ["Store", "SCHEMA_VERSION"] diff --git a/codeindex/store/db.py b/blastradius/store/db.py similarity index 98% rename from codeindex/store/db.py rename to blastradius/store/db.py index cb2ffb3..9a2c758 100644 --- a/codeindex/store/db.py +++ b/blastradius/store/db.py @@ -1,9 +1,9 @@ # Copyright 2026 David Scheiderman # Licensed under the Apache License, Version 2.0 -"""SQLite-backed persistent store for codeindex graph data. +"""SQLite-backed persistent store for blastradius graph data. -Dependency rule: this module must not import from codeindex.graph or -codeindex.semantic. The dependency arrow points only upward into this layer. +Dependency rule: this module must not import from blastradius.graph or +blastradius.semantic. The dependency arrow points only upward into this layer. """ from __future__ import annotations @@ -185,7 +185,7 @@ def _maybe_load_vec(self) -> None: self._vec_dims = int(dims_str) self._has_vec = True except Exception as exc: - print(f"[codeindex] sqlite-vec load failed: {exc}", file=sys.stderr) + print(f"[blastradius] sqlite-vec load failed: {exc}", file=sys.stderr) self._has_vec = False # ── meta ────────────────────────────────────────────────────────────────── @@ -551,7 +551,7 @@ def as_of_impact(self, file_path: str, reachable: set) -> dict | None: obtained via `git log --format=%H `. Returns None if no temporal data is available. """ - from codeindex.impact import compute_blast_radius # avoid circular at module level + from blastradius.impact import compute_blast_radius # avoid circular at module level self._populate_reachable_temp(reachable) @@ -607,7 +607,7 @@ def _backfill_warning(self) -> str | None: if row and row[0] <= 1: return ( "Git history has not been backfilled — all files share the same " - "first_seen_commit. Run `codeindex history` first for accurate " + "first_seen_commit. Run `blastradius history` first for accurate " "changed_since results." ) return None @@ -690,8 +690,8 @@ def init_vectors(self, dims: int) -> bool: """ if not _HAS_SQLITE_VEC: print( - "[codeindex] sqlite-vec not installed — semantic search unavailable. " - "Install with: pip install 'codeindex[semantic]'", + "[blastradius] sqlite-vec not installed — semantic search unavailable. " + "Install with: pip install 'blastradius[semantic]'", file=sys.stderr, ) return False @@ -711,7 +711,7 @@ def init_vectors(self, dims: int) -> bool: self._conn.commit() return True except Exception as exc: - print(f"[codeindex] sqlite-vec init failed: {exc}", file=sys.stderr) + print(f"[blastradius] sqlite-vec init failed: {exc}", file=sys.stderr) self._has_vec = False return False @@ -1014,7 +1014,7 @@ def neighborhood(self, file_path: str, direction: str, depth: int) -> dict: # ── status ──────────────────────────────────────────────────────────────── def status(self) -> dict: - """Return a snapshot of store state for `codeindex db status`.""" + """Return a snapshot of store state for `blastradius db status`.""" try: fts_rows = self._conn.execute( "SELECT COUNT(*) FROM symbols_fts" diff --git a/codeindex/symbol_extractor.py b/blastradius/symbol_extractor.py similarity index 99% rename from codeindex/symbol_extractor.py rename to blastradius/symbol_extractor.py index 8e16ceb..f484280 100644 --- a/codeindex/symbol_extractor.py +++ b/blastradius/symbol_extractor.py @@ -1,4 +1,4 @@ -"""Per-language symbol extraction for the codeindex symbol index.""" +"""Per-language symbol extraction for the blastradius symbol index.""" from __future__ import annotations import ast import re diff --git a/codeindex/symbols.py b/blastradius/symbols.py similarity index 92% rename from codeindex/symbols.py rename to blastradius/symbols.py index dd277a8..9ce1299 100644 --- a/codeindex/symbols.py +++ b/blastradius/symbols.py @@ -5,13 +5,13 @@ from datetime import date from pathlib import Path -from codeindex.analyzers.base import load_gitignore_patterns, is_ignored, is_skip_dir -from codeindex.symbol_extractor import EXTRACTORS, extract_symbols +from blastradius.analyzers.base import load_gitignore_patterns, is_ignored, is_skip_dir +from blastradius.symbol_extractor import EXTRACTORS, extract_symbols SYMBOL_INDEX_FILENAME = "symbolindex.json" -_CLAUDE_START = "" -_CLAUDE_END = "" +_CLAUDE_START = "" +_CLAUDE_END = "" _SUPPORTED_EXTS = frozenset(EXTRACTORS.keys()) @@ -91,7 +91,7 @@ def write_standalone(symbol_data: dict, output: Path) -> None: def write_inline(symbol_data: dict, index_path: Path) -> None: - """Embed symbols into each node in an existing codeindex.json.""" + """Embed symbols into each node in an existing blastradius.json.""" data = json.loads(index_path.read_text()) by_file = symbol_data["file_symbols"] enriched = 0 @@ -136,7 +136,7 @@ def _build_claude_section(symbol_data: dict, exported_only: bool = True) -> str: lines = [ _CLAUDE_START, "## Symbol Index", - "_Generated by codeindex. Update: `codeindex symbols --claude-md`_", + "_Generated by blastradius. Update: `blastradius symbols --claude-md`_", "", "```symbolindex", ] diff --git a/codeindex/temporal/__init__.py b/blastradius/temporal/__init__.py similarity index 74% rename from codeindex/temporal/__init__.py rename to blastradius/temporal/__init__.py index 8d0e19f..ed3460c 100644 --- a/codeindex/temporal/__init__.py +++ b/blastradius/temporal/__init__.py @@ -2,6 +2,6 @@ # Licensed under the Apache License, Version 2.0 from __future__ import annotations -from codeindex.temporal.history import backfill +from blastradius.temporal.history import backfill __all__ = ["backfill"] diff --git a/codeindex/temporal/history.py b/blastradius/temporal/history.py similarity index 98% rename from codeindex/temporal/history.py rename to blastradius/temporal/history.py index 9fb3544..1dae791 100644 --- a/codeindex/temporal/history.py +++ b/blastradius/temporal/history.py @@ -2,7 +2,7 @@ # Licensed under the Apache License, Version 2.0 """Git history backfill via plumbing — never touches the working tree. -Dependency rule: must not import from codeindex.graph or codeindex.semantic. +Dependency rule: must not import from blastradius.graph or blastradius.semantic. """ from __future__ import annotations @@ -221,7 +221,7 @@ def _is_parseable(rel_path: str) -> bool: def backfill( root: Path, - store: object, # codeindex.store.Store — avoid circular import + store: object, # blastradius.store.Store — avoid circular import since: str | None = None, max_commits: int | None = 1000, ) -> tuple[int, int]: diff --git a/codeindex/viz_server.py b/blastradius/viz_server.py similarity index 91% rename from codeindex/viz_server.py rename to blastradius/viz_server.py index bc6d49c..5f9a9c4 100644 --- a/codeindex/viz_server.py +++ b/blastradius/viz_server.py @@ -1,4 +1,4 @@ -"""Minimal HTTP server for the codeindex visualization UI.""" +"""Minimal HTTP server for the blastradius visualization UI.""" from __future__ import annotations import json import os @@ -12,11 +12,11 @@ VIZ_HTML = HERE.parent / "viz" / "explorer.html" REPO_PATH = "." -INDEX_FILE: Path = Path("codeindex.json") +INDEX_FILE: Path = Path("blastradius.json") def _run_analysis(repo_path: str, output: Path) -> bool: - from codeindex.index import build + from blastradius.index import build try: build(repo_path, output) return True @@ -60,7 +60,7 @@ def do_GET(self): if INDEX_FILE.exists(): self._send_file(INDEX_FILE, "application/json") else: - self._send_json({"error": "codeindex.json not found — run: codeindex analyze "}, 404) + self._send_json({"error": "blastradius.json not found — run: blastradius analyze "}, 404) elif path == "/refresh": ok = _run_analysis(REPO_PATH, INDEX_FILE) @@ -111,7 +111,7 @@ def on_modified(self, event): def serve(repo_path: str, port: int = 8080, watch: bool = False, output: Path | None = None) -> None: global REPO_PATH, INDEX_FILE REPO_PATH = repo_path - INDEX_FILE = output or (Path(repo_path).resolve() / "codeindex.json") + INDEX_FILE = output or (Path(repo_path).resolve() / "blastradius.json") print(f"Analyzing {repo_path} …", file=sys.stderr) _run_analysis(repo_path, INDEX_FILE) diff --git a/codeindex/__init__.py b/codeindex/__init__.py deleted file mode 100644 index 3830db6..0000000 --- a/codeindex/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -"""codeindex — repo dependency analyzer with blast-radius impact scoring.""" -__version__ = "0.1.0" diff --git a/docs/CKG-INTERNALS.md b/docs/CKG-INTERNALS.md index d865450..74e90ce 100644 --- a/docs/CKG-INTERNALS.md +++ b/docs/CKG-INTERNALS.md @@ -1,4 +1,4 @@ -# CKG-INTERNALS: codeindex Internal Architecture +# CKG-INTERNALS: blastradius Internal Architecture | | | |---|---| @@ -14,7 +14,7 @@ Produced per Phase 0 of CKG-DESIGN-001. Documents current module structure, data ## 1. Module Map ``` -codeindex/ +blastradius/ __init__.py — package metadata; version "0.1.0" cli.py — argparse CLI; 8 commands; dispatch table at line 353 index.py — build() orchestrator; load() reader; find_index() discovery @@ -115,10 +115,10 @@ def build(repo_path, output): enrich_links(data["nodes"], data["links"]) # impact.py:81–99 — adds imports/imported_by data["meta"]["indexed"] = True dest = output or (root / INDEX_FILENAME) - dest.write_text(json.dumps(data, indent=2)) # writes codeindex.json + dest.write_text(json.dumps(data, indent=2)) # writes blastradius.json ``` -`INDEX_FILENAME = "codeindex.json"` (index.py top). +`INDEX_FILENAME = "blastradius.json"` (index.py top). --- @@ -148,7 +148,7 @@ Result keyed by node ID: ## 5. JSON Output -### 5.1 `codeindex.json` — written by `index.py:25` +### 5.1 `blastradius.json` — written by `index.py:25` ```json { @@ -186,7 +186,7 @@ Result keyed by node ID: ```json { - "meta": {"generated": "2026-06-07", "repo": "codeindex/", "total_symbols": 156}, + "meta": {"generated": "2026-06-07", "repo": "blastradius/", "total_symbols": 156}, "symbols": { "MyClass": [{"file": "src/main.py", "line": 42, "kind": "class", "exported": true, "methods": ["run"]}] }, @@ -201,7 +201,7 @@ Result keyed by node ID: | Function | Location | Purpose | |---|---|---| | `write_standalone()` | `symbols.py:77–85` | Writes `symbolindex.json` | -| `write_inline()` | `symbols.py:88–114` | Merges symbol data into `codeindex.json` nodes | +| `write_inline()` | `symbols.py:88–114` | Merges symbol data into `blastradius.json` nodes | | `write_claude_md()` | `symbols.py:146–171` | Upserts a symbol section into `CLAUDE.md` | --- @@ -261,7 +261,7 @@ Protocol: JSON-RPC 2.0 over stdio. Entry: `serve()` at line 331. | `build_symbol_index` | `_call_build_symbol_index` | `symbols.build_symbol_index()` | **Key helpers**: -- `_resolve_index()` (line 127) — loads `codeindex.json`; auto-discovers by walking up from cwd +- `_resolve_index()` (line 127) — loads `blastradius.json`; auto-discovers by walking up from cwd - `_resolve_file_id()` (line 138) — fuzzy-matches a path to a node ID by suffix - `_handle()` (line 284) — routes JSON-RPC method → handler @@ -274,7 +274,7 @@ HTTP server using stdlib `http.server`. Routes: | Route | Handler | Response | |---|---|---| | `/`, `/index.html` | `do_GET` (line 53) | Serves `viz/explorer.html` | -| `/graph` | `do_GET` (line 60) | Serves `codeindex.json` as JSON | +| `/graph` | `do_GET` (line 60) | Serves `blastradius.json` as JSON | | `/refresh` | `do_GET` (line 65) | Calls `_run_analysis()` → `index.build()` | Watch mode (`viz_server.py:73–109`): watchdog observer + 1 s debounce timer triggers `_run_analysis()` on file changes. @@ -344,7 +344,7 @@ cli.py main() ├─ enrich_nodes() ← mutate nodes with blast fields ├─ enrich_links() ← add imports/imported_by to nodes │ ← SEAM A: upsert to SQLite here - └─ write codeindex.json + └─ write blastradius.json cli.py / mcp_server.py (query commands) └─ index.load(path) ← SEAM B: replace with DB read diff --git a/docs/codeindex-temporal-knowledge-graph.md b/docs/blastradius-temporal-knowledge-graph.md similarity index 78% rename from docs/codeindex-temporal-knowledge-graph.md rename to docs/blastradius-temporal-knowledge-graph.md index 368604f..796cd25 100644 --- a/docs/codeindex-temporal-knowledge-graph.md +++ b/docs/blastradius-temporal-knowledge-graph.md @@ -1,10 +1,10 @@ -# codeindex → Temporal Code Knowledge Graph +# blastradius → Temporal Code Knowledge Graph | | | |---|---| | **Doc ID** | CKG-DESIGN-001 | | **Status** | Draft — ready for implementation | -| **Target repo** | `scheidydude/codeindex` | +| **Target repo** | `scheidydude/blastradius` | | **Audience** | Claude Code (implementing agent) + maintainer | | **License constraint** | Apache 2.0 (preserve existing headers) | @@ -15,7 +15,7 @@ ## 1. Problem statement -`codeindex` today is a **stateless, point-in-time, exact-match** dependency and symbol analyzer. It parses a repo, computes a dependency graph with blast-radius scores plus a symbol map, and writes `codeindex.json` / `symbolindex.json`. That design is excellent for a one-shot snapshot but blocks three capabilities needed to serve as a durable knowledge graph for AI agents: +`blastradius` today is a **stateless, point-in-time, exact-match** dependency and symbol analyzer. It parses a repo, computes a dependency graph with blast-radius scores plus a symbol map, and writes `blastradius.json` / `symbolindex.json`. That design is excellent for a one-shot snapshot but blocks three capabilities needed to serve as a durable knowledge graph for AI agents: 1. **Stateless.** Every run recomputes the entire index from scratch. There is no persistent store, no incremental update, and no cheap repeated querying. This does not scale to an always-on agent asking many questions, or to large repos. 2. **Atemporal.** The graph only ever represents *now*. It cannot answer how structure evolved — when a dependency was introduced, what the blast radius of a file was at a past release, or which files churn most — which is core to real impact analysis and code archaeology. @@ -23,7 +23,7 @@ ## 2. Goal -Evolve `codeindex` into a **self-hosted, local-first temporal code knowledge graph with hybrid retrieval** — conceptually "Graphiti applied to code instead of conversations" — while preserving everything that makes the current tool good: deterministic AST extraction, near-zero-friction install, and the existing CLI / JSON / MCP / viz surfaces. +Evolve `blastradius` into a **self-hosted, local-first temporal code knowledge graph with hybrid retrieval** — conceptually "Graphiti applied to code instead of conversations" — while preserving everything that makes the current tool good: deterministic AST extraction, near-zero-friction install, and the existing CLI / JSON / MCP / viz surfaces. The end state adds three properties to the existing graph: **persistence + incrementality**, **time**, and **meaning (semantic retrieval)**. @@ -31,10 +31,10 @@ The end state adds three properties to the existing graph: **persistence + incre These are out of scope. Do not implement them, and do not refactor toward them. -- **Not conversational / agent memory.** This project does not store user preferences, chat history, or "who the user is." That is a separate concern that belongs in a downstream memory layer. `codeindex` is a *code-context provider*, not a personal-memory store. Keep the two truth models separate (deterministic code facts vs. probabilistic conversational facts). +- **Not conversational / agent memory.** This project does not store user preferences, chat history, or "who the user is." That is a separate concern that belongs in a downstream memory layer. `blastradius` is a *code-context provider*, not a personal-memory store. Keep the two truth models separate (deterministic code facts vs. probabilistic conversational facts). - **Not LLM-based extraction.** Do not replace deterministic AST/regex extraction with an LLM. The deterministic model is a reliability advantage and must remain the source of graph structure. LLMs/embeddings are used *only* for the semantic retrieval layer, never to infer edges or symbols. - **Not a server-based graph database.** No Neo4j, FalkorDB, or any external graph service. The store stays embedded. -- **No breaking changes to the public contract.** The `codeindex.json` / `symbolindex.json` schemas and existing CLI commands must keep working unchanged. +- **No breaking changes to the public contract.** The `blastradius.json` / `symbolindex.json` schemas and existing CLI commands must keep working unchanged. - **No cloud dependency.** Embeddings must be obtainable from a local/self-hosted endpoint. Nothing leaves the machine by default. ## 4. Architecture overview @@ -69,10 +69,10 @@ The migration is deliberately layered so each layer is shippable on its own: ### 4.1 Package layout & extraction boundary -This work ships as a **feature release on the existing `codeindex` repo**, *not* a fork and *not* (yet) a separate project. But it is laid out so the heavy layer can be extracted into its own package later with a clean lift, if it grows an independent identity. To keep that door open, the codebase is partitioned into a **lean core** and **optional layers**, and the dependency direction is strictly one-way. +This work ships as a **feature release on the existing `blastradius` repo**, *not* a fork and *not* (yet) a separate project. But it is laid out so the heavy layer can be extracted into its own package later with a clean lift, if it grows an independent identity. To keep that door open, the codebase is partitioned into a **lean core** and **optional layers**, and the dependency direction is strictly one-way. ``` -codeindex/ +blastradius/ core/ # deterministic parsers, blast scoring — the lean primitive store/ # SQLite store + incremental indexing (core dependency-free) temporal/ # commit stamping + git-history ingestion (core dependency-free) @@ -83,19 +83,19 @@ codeindex/ **Hard partitioning rules:** - `core/`, `store/`, and `temporal/` **must not import** from `graph/` or `semantic/`. The dependency arrow points only *up*, from the heavy layers down into the core — never the reverse. This is what makes a future extraction a move, not a rewrite. -- `graph/` and `semantic/` are gated behind optional extras (`codeindex[graph]`, `codeindex[semantic]`). The default `pip install codeindex` installs only the lean core + store + temporal and pulls in **no** new runtime dependencies. -- The heavy layers consume the core through its public store/graph API only — never by reaching into core internals — so that when `graph/`+`semantic/` are lifted into a standalone package, the only change is declaring `codeindex` as a dependency. +- `graph/` and `semantic/` are gated behind optional extras (`blastradius[graph]`, `blastradius[semantic]`). The default `pip install blastradius` installs only the lean core + store + temporal and pulls in **no** new runtime dependencies. +- The heavy layers consume the core through its public store/graph API only — never by reaching into core internals — so that when `graph/`+`semantic/` are lifted into a standalone package, the only change is declaring `blastradius` as a dependency. ## 5. Data model -The canonical store becomes a single SQLite database (default: `/.codeindex/index.db`). `codeindex.json` / `symbolindex.json` become **exports** (a view rendered from the DB), preserving the current public schema exactly. +The canonical store becomes a single SQLite database (default: `/.blastradius/index.db`). `blastradius.json` / `symbolindex.json` become **exports** (a view rendered from the DB), preserving the current public schema exactly. ### 5.1 Temporal model Bi-temporal, kept simple: - **Commit time** — where a fact lives in git history. Every node/edge/symbol carries `first_seen_commit` and `last_seen_commit` (nullable; `NULL` last-seen = still present at HEAD). -- **Index time** — when `codeindex` observed the fact (`first_seen_at`, `last_seen_at`, ISO-8601). +- **Index time** — when `blastradius` observed the fact (`first_seen_at`, `last_seen_at`, ISO-8601). Facts are **never hard-deleted**. When a file/edge/symbol disappears, set its `last_seen_commit` and `last_seen_at` and flag it inactive. This is what enables "as-of" queries and churn analysis. @@ -190,8 +190,8 @@ Bump `schema_version` and ship a forward migration whenever this changes. ## 6. Component design -### 6.1 Storage layer (`codeindex/store/`) -A thin module wrapping the SQLite connection: schema creation, versioned migrations, upsert/close helpers for files/edges/symbols, and JSON export that reproduces the current `codeindex.json` / `symbolindex.json` byte-for-byte (golden-tested). The store is the new source of truth; everything else reads/writes through it. +### 6.1 Storage layer (`blastradius/store/`) +A thin module wrapping the SQLite connection: schema creation, versioned migrations, upsert/close helpers for files/edges/symbols, and JSON export that reproduces the current `blastradius.json` / `symbolindex.json` byte-for-byte (golden-tested). The store is the new source of truth; everything else reads/writes through it. ### 6.2 Incremental indexer Replaces full recompute. Algorithm: @@ -206,7 +206,7 @@ Replaces full recompute. Algorithm: 6. Refresh FTS rows for changed symbols. (Embeddings handled in Phase 3.) 7. Update `index_meta.last_indexed_commit` and export JSON. -### 6.3 Temporal ingestion (`codeindex history`) +### 6.3 Temporal ingestion (`blastradius history`) Backfills the temporal graph from git history **without checkouts**, to keep it safe and fast: - Walk `git log` oldest→newest within bounds (`--since`, `--max-commits`). @@ -218,8 +218,8 @@ This is the most performance-sensitive component — see Risks. Default to **bou ### 6.4 Semantic layer (Phase 3) - **What gets embedded:** per symbol, the concatenation of `name + signature + doc` (skip empties). -- **Embedding provider interface** (`codeindex/semantic/`): a small abstraction with one shipped implementation — an **OpenAI-compatible `/v1/embeddings` HTTP client** built on stdlib `urllib` (no new dependency for the client itself). Endpoint URL, model name, and dimensions come from config/env. This targets a self-hosted local inference server out of the box. -- **Vector store:** `sqlite-vec` `vec0` virtual table. This is the single new optional dependency, gated behind the `codeindex[semantic]` extra. If the extension can't load, semantic features degrade gracefully (log a clear message, fall back to FTS + exact) — they never hard-fail the tool. +- **Embedding provider interface** (`blastradius/semantic/`): a small abstraction with one shipped implementation — an **OpenAI-compatible `/v1/embeddings` HTTP client** built on stdlib `urllib` (no new dependency for the client itself). Endpoint URL, model name, and dimensions come from config/env. This targets a self-hosted local inference server out of the box. +- **Vector store:** `sqlite-vec` `vec0` virtual table. This is the single new optional dependency, gated behind the `blastradius[semantic]` extra. If the extension can't load, semantic features degrade gracefully (log a clear message, fall back to FTS + exact) — they never hard-fail the tool. - Embeddings are (re)generated only for new/changed symbols during indexing, batched. ### 6.5 Hybrid query engine @@ -237,12 +237,12 @@ Combine the ranked lists with **Reciprocal Rank Fusion** (`score = Σ 1/(60 + ra ### 7.1 CLI (additions; existing commands unchanged) | Command | Purpose | |---|---| -| `codeindex analyze` | Existing — now incremental, writes to SQLite, still exports `codeindex.json` | -| `codeindex history [--since REF] [--max-commits N]` | Backfill the temporal graph from git history | -| `codeindex search "" [--k N] [--as-of REF] [--json]` | Hybrid semantic + keyword + graph search | -| `codeindex impact FILE [--as-of REF]` | Existing — gains optional point-in-time blast radius | -| `codeindex db status` | Show schema version, last indexed commit, counts, embedding config | -| `codeindex db migrate` | Apply pending schema migrations | +| `blastradius analyze` | Existing — now incremental, writes to SQLite, still exports `blastradius.json` | +| `blastradius history [--since REF] [--max-commits N]` | Backfill the temporal graph from git history | +| `blastradius search "" [--k N] [--as-of REF] [--json]` | Hybrid semantic + keyword + graph search | +| `blastradius impact FILE [--as-of REF]` | Existing — gains optional point-in-time blast radius | +| `blastradius db status` | Show schema version, last indexed commit, counts, embedding config | +| `blastradius db migrate` | Apply pending schema migrations | ### 7.2 MCP tools (additions; existing four unchanged) | Tool | Description | @@ -253,29 +253,29 @@ Combine the ranked lists with **Reciprocal Rank Fusion** (`score = Σ 1/(60 + ra | `changed_since` | `ref` → files/edges/symbols added or removed since a ref | ### 7.3 Configuration -Read from `[tool.codeindex]` in `pyproject.toml` and/or `.codeindex.toml`, env vars override. Keys: `db_path`, `embedding_endpoint`, `embedding_model`, `embedding_dims`, `history_max_commits`. All have safe defaults; semantic config is optional. +Read from `[tool.blastradius]` in `pyproject.toml` and/or `.blastradius.toml`, env vars override. Keys: `db_path`, `embedding_endpoint`, `embedding_model`, `embedding_dims`, `history_max_commits`. All have safe defaults; semantic config is optional. ## 8. Implementation phases > Each phase ends with: all existing tests green, new tests added, acceptance checks passing, a commit. Keep the core dependency-free; every new dependency is an optional extra. ### Phase 0 — Orient (no behavior change) -- Read the existing package layout under `codeindex/` and `viz/`. Produce `docs/CKG-INTERNALS.md`: current module map, where parsing happens, where JSON is written, how the MCP server and CLI dispatch are wired. +- Read the existing package layout under `blastradius/` and `viz/`. Produce `docs/CKG-INTERNALS.md`: current module map, where parsing happens, where JSON is written, how the MCP server and CLI dispatch are wired. - Identify the integration seams for the new store without changing behavior. - **Acceptance:** internals doc committed; no source changes; existing tests still pass. ### Phase 1 — SQLite store + incremental indexing (no new deps) -- Implement `codeindex/store/` (schema, migrations, upsert/close, JSON export). -- Wire `analyze` to populate the DB and export `codeindex.json` / `symbolindex.json` from it. +- Implement `blastradius/store/` (schema, migrations, upsert/close, JSON export). +- Wire `analyze` to populate the DB and export `blastradius.json` / `symbolindex.json` from it. - Implement incremental change detection (git diff + content-hash fallback) and affected-subgraph blast recompute. - Populate `symbols_fts`. - **Acceptance:** - Exported JSON is byte-identical to pre-change output on a fixture repo (golden test). - Re-running `analyze` after editing one file re-parses only that file's change set (assert via instrumentation/log). - - `codeindex db status` reports correct counts. + - `blastradius db status` reports correct counts. ### Phase 2 — Temporal layer (no new deps) -- Implement `codeindex history` via `git ls-tree` + `git cat-file --batch` (no checkouts), bounded by `--since` / `--max-commits`. +- Implement `blastradius history` via `git ls-tree` + `git cat-file --batch` (no checkouts), bounded by `--since` / `--max-commits`. - Maintain `first_seen_commit` / `last_seen_commit` on nodes/edges/symbols; populate `commits`. - Add `--as-of` to `impact`; implement `changed_since`. - **Acceptance:** @@ -286,7 +286,7 @@ Read from `[tool.codeindex]` in `pyproject.toml` and/or `.codeindex.toml`, env v ### Phase 3 — Semantic layer (optional dep: `sqlite-vec`) - Implement the embedding-provider interface + stdlib OpenAI-compatible client. - Add `vec_symbols`; embed `name+signature+doc` for new/changed symbols, batched. -- Implement the hybrid query engine (semantic + FTS + graph, RRF fusion, optional temporal filter) behind `codeindex search`. +- Implement the hybrid query engine (semantic + FTS + graph, RRF fusion, optional temporal filter) behind `blastradius search`. - Graceful degradation when the extension or endpoint is unavailable. - **Acceptance:** - With a local endpoint configured, `search "validate auth token"` surfaces the auth-validation symbol without its name in the query. @@ -300,7 +300,7 @@ Read from `[tool.codeindex]` in `pyproject.toml` and/or `.codeindex.toml`, env v ## 9. Constraints for the implementing agent (hard rules) -1. **Preserve the zero-dependency core.** Phases 0–2 add **no** runtime dependencies. `sqlite-vec` (Phase 3) is the only new dependency and must be an optional extra (`codeindex[semantic]`); the tool must run fully without it. +1. **Preserve the zero-dependency core.** Phases 0–2 add **no** runtime dependencies. `sqlite-vec` (Phase 3) is the only new dependency and must be an optional extra (`blastradius[semantic]`); the tool must run fully without it. 2. **Respect the partitioning and dependency direction (§4.1).** `core/`, `store/`, and `temporal/` must never import from `graph/` or `semantic/`. The heavy layers depend on the core, never the reverse, and consume it only through its public store/graph API. This keeps a future extraction into a standalone package a clean lift. Any change that creates a core→heavy import is a hard failure. 3. **Do not change the public JSON schema or existing CLI commands.** JSON output stays byte-compatible (golden-tested). 4. **Deterministic extraction is sacred.** No LLM or heuristic inference of edges/symbols. Embeddings serve retrieval only. @@ -325,7 +325,7 @@ Read from `[tool.codeindex]` in `pyproject.toml` and/or `.codeindex.toml`, env v | `sqlite-vec` extension load fails on a platform | Feature-detect at startup; fall back; document install per OS | | Schema evolution breaks existing DBs | Versioned migrations + `schema_version` in `index_meta`; `db migrate` command | | Incremental diff misses a change | Content-hash fallback validates against git diff; provide `analyze --full` escape hatch to force a rebuild | -| Scope creep into conversational memory | Section 3 forbids it; `codeindex` stays a code-context provider only | +| Scope creep into conversational memory | Section 3 forbids it; `blastradius` stays a code-context provider only | ## 12. Definition of done The project is complete when: `analyze` is incremental and SQLite-backed with byte-identical JSON export; `history` builds a queryable temporal graph; `impact --as-of` and `changed_since` work; `search` performs hybrid retrieval with graceful degradation; all four new MCP tools are registered; the README documents everything; and the full test suite (golden + temporal + retrieval + degradation) passes with the core remaining dependency-free. diff --git a/docs/evaluation-prompt.md b/docs/evaluation-prompt.md index 66af280..582549f 100644 --- a/docs/evaluation-prompt.md +++ b/docs/evaluation-prompt.md @@ -1,22 +1,22 @@ -# codeindex Evaluation Prompt +# blastradius Evaluation Prompt Copy and paste the following prompt into Claude Code from inside the repo you want to evaluate. -Requires codeindex v0.3.6+ installed and `codeindex analyze` already run. +Requires blastradius v0.3.6+ installed and `blastradius analyze` already run. --- ``` -You are evaluating codeindex against this repo. Your job is to run 5 realistic tasks two ways each — once with standard shell tools (grep, git, find, cat) and once with codeindex — then produce a structured comparison report. Make no changes to this repo or the codeindex repo. +You are evaluating blastradius against this repo. Your job is to run 5 realistic tasks two ways each — once with standard shell tools (grep, git, find, cat) and once with blastradius — then produce a structured comparison report. Make no changes to this repo or the blastradius repo. ## Setup check -First, verify codeindex is available and the DB exists: +First, verify blastradius is available and the DB exists: ```bash -codeindex db status +blastradius db status ``` -If the DB is missing, run `codeindex analyze .` first, then continue. +If the DB is missing, run `blastradius analyze .` first, then continue. ## The 5 tasks @@ -28,16 +28,16 @@ Run each task both ways. Time both approaches (use `time`). Record what you actu Pick the most important user-facing concept in this codebase (auth, payment, scheduling, API handler, etc. — infer it from the file structure). Answer: "Where does this process begin? What function is the entry point?" -**Without codeindex:** +**Without blastradius:** ```bash grep -r "" --include="*.py" --include="*.ts" --include="*.go" -l | grep -v test | grep -v __pycache__ | head -20 # then open the most likely file and read it ``` -**With codeindex:** +**With blastradius:** ```bash -codeindex search "" -codeindex lookup +blastradius search "" +blastradius lookup ``` Note: `lookup` prints the definition line plus 4 lines of source context. If a symbol isn't found, it's likely a third-party import (only repo-defined symbols are indexed). @@ -48,15 +48,15 @@ Note: `lookup` prints the definition line plus 4 lines of source context. If a s Pick the file that looks most central (lots of imports, core module name, etc.). Answer: "How many files would be affected if this changed? Is it safe to touch?" -**Without codeindex:** +**Without blastradius:** ```bash grep -r "from \|import " --include="*.py" --include="*.ts" --include="*.go" -l | grep -v test | grep -v __pycache__ # count the results and note: this is only direct importers ``` -**With codeindex:** +**With blastradius:** ```bash -codeindex impact +blastradius impact ``` --- @@ -65,15 +65,15 @@ codeindex impact Pick a mid-level module (not the God object, not a leaf utility). Answer: "What does this depend on, and what depends on it?" -**Without codeindex:** +**Without blastradius:** ```bash grep -n "^import\|^from" | head -20 # what it imports grep -r "from.*\|import.*" --include="*.py" -l | grep -v test # who imports it ``` -**With codeindex:** +**With blastradius:** ```bash -codeindex dependencies +blastradius dependencies ``` --- @@ -82,12 +82,12 @@ codeindex dependencies Answer: "If I had 30 minutes to read code before a major refactor, which files must I understand first?" -**Without codeindex:** +**Without blastradius:** No direct equivalent. Describe what you would do manually (read entry points, follow imports, build a mental model). -**With codeindex:** +**With blastradius:** ```bash -codeindex high-blast --threshold +blastradius high-blast --threshold ``` --- @@ -97,19 +97,19 @@ codeindex high-blast --threshold Pick a commit from 1–3 weeks ago (or a release tag if one exists). Answer: "What new dependencies were introduced? What was removed?" -**Without codeindex:** +**Without blastradius:** ```bash git log --oneline -10 # pick a ref git diff --name-only ..HEAD ``` -**With codeindex:** +**With blastradius:** ```bash git log --oneline -10 # same ref -codeindex changed-since +blastradius changed-since ``` -Note: if you see "Warning: Git history has not been backfilled", run `codeindex history .` first, then repeat the command. +Note: if you see "Warning: Git history has not been backfilled", run `blastradius history .` first, then repeat the command. --- @@ -117,16 +117,16 @@ Note: if you see "Warning: Git history has not been backfilled", run `codeindex Pick a high-blast file that exports multiple symbols (a schema, a utility module, a shared config). Answer: "Which specific exports are actually used, and by which files? Is it safe to change just one of them?" -**Without codeindex:** +**Without blastradius:** ```bash # For each exported name, grep importers separately: grep -r "ExportedName" --include="*.ts" --include="*.py" -l # repeat for each export — tedious for files with 5+ exports ``` -**With codeindex:** +**With blastradius:** ```bash -codeindex symbol-blast +blastradius symbol-blast ``` This lists every exported symbol with a count and the exact importer files that reference it by name — so you can see that changing `userSchema` affects 8 routes while `legacySchema` affects only 1. @@ -147,9 +147,9 @@ After running all 6 tasks, fill in this table: | 6. Per-export blast | | | | | | Then answer: -- Which codeindex result surprised you most? (Something you wouldn't have found with grep alone?) -- Did any codeindex command return wrong or misleading results? -- What query or task did you try that codeindex couldn't answer? +- Which blastradius result surprised you most? (Something you wouldn't have found with grep alone?) +- Did any blastradius command return wrong or misleading results? +- What query or task did you try that blastradius couldn't answer? ## Report format @@ -164,5 +164,5 @@ Write the final report as: **Rough edges found:** -**Verdict:** one sentence — would you add codeindex to your workflow for this repo? +**Verdict:** one sentence — would you add blastradius to your workflow for this repo? ``` diff --git a/pyproject.toml b/pyproject.toml index 8b219c1..d995078 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["setuptools>=68"] build-backend = "setuptools.build_meta" [project] -name = "codeindex" +name = "blastradius" version = "0.3.6" description = "Repo dependency analyzer with blast-radius impact scoring for AI-assisted development" readme = "README.md" @@ -11,7 +11,7 @@ license = { text = "Apache-2.0" } requires-python = ">=3.9" [project.scripts] -codeindex = "codeindex.cli:main" +blastradius = "blastradius.cli:main" [project.optional-dependencies] watch = ["watchdog"] @@ -25,7 +25,7 @@ testpaths = ["tests"] [tool.setuptools.packages.find] where = ["."] -include = ["codeindex*"] +include = ["blastradius*"] [tool.setuptools.package-data] -"codeindex" = ["py.typed"] +"blastradius" = ["py.typed"] diff --git a/tests/test_phase1.py b/tests/test_phase1.py index d51325d..4f88bfb 100644 --- a/tests/test_phase1.py +++ b/tests/test_phase1.py @@ -7,7 +7,7 @@ repo (golden test). 2. Re-running analyze after editing one file logs only that file's change set (incremental detection test). - 3. `codeindex db status` reports correct counts. + 3. `blastradius db status` reports correct counts. """ from __future__ import annotations @@ -41,10 +41,10 @@ def _init_git(repo: Path) -> str: def _run_analyze(repo: Path) -> dict: - """Run codeindex.index.build() and return the parsed JSON output.""" + """Run blastradius.index.build() and return the parsed JSON output.""" # Import here so we get the Phase-1 version, not a cached import import importlib - import codeindex.index as idx_mod + import blastradius.index as idx_mod importlib.reload(idx_mod) data = idx_mod.build(str(repo)) @@ -87,8 +87,8 @@ def test_golden_idempotent(tmp_path: Path) -> None: def test_db_populated(tmp_path: Path) -> None: """After analyze(), the SQLite store contains the expected file rows.""" - from codeindex.store import Store - from codeindex.index import db_path_for + from blastradius.store import Store + from blastradius.index import db_path_for repo = tmp_path / "repo" shutil.copytree(FIXTURE_SRC, repo) @@ -97,7 +97,7 @@ def test_db_populated(tmp_path: Path) -> None: data = _run_analyze(repo) db_path = db_path_for(repo) - assert db_path.exists(), ".codeindex/index.db not created" + assert db_path.exists(), ".blastradius/index.db not created" store = Store(db_path) status = store.status() @@ -147,8 +147,8 @@ def test_incremental_detection(tmp_path: Path, capsys: pytest.CaptureFixture) -> def test_db_status_counts(tmp_path: Path) -> None: """store.status() counts match the number of nodes analyze() produced.""" - from codeindex.store import Store - from codeindex.index import db_path_for + from blastradius.store import Store + from blastradius.index import db_path_for repo = tmp_path / "repo" shutil.copytree(FIXTURE_SRC, repo) @@ -172,8 +172,8 @@ def test_db_status_counts(tmp_path: Path) -> None: def test_soft_delete_on_removal(tmp_path: Path) -> None: """Removing a file marks its DB row inactive, not deleted.""" - from codeindex.store import Store - from codeindex.index import db_path_for + from blastradius.store import Store + from blastradius.index import db_path_for repo = tmp_path / "repo" shutil.copytree(FIXTURE_SRC, repo) diff --git a/tests/test_phase2.py b/tests/test_phase2.py index 212cb51..e6157ba 100644 --- a/tests/test_phase2.py +++ b/tests/test_phase2.py @@ -46,7 +46,7 @@ def _commit_all(repo: Path, message: str) -> str: def _run_analyze(repo: Path) -> None: import importlib - import codeindex.index as idx_mod + import blastradius.index as idx_mod importlib.reload(idx_mod) idx_mod.build(str(repo)) @@ -86,8 +86,8 @@ def _make_temporal_repo(tmp_path: Path) -> tuple[Path, str, str, str]: def test_as_of_impact_differs_from_head(tmp_path: Path) -> None: """impact B --as-of C2 shows A as dependent; impact B at HEAD does not.""" - from codeindex.store import Store - from codeindex.index import db_path_for, git_reachable, git_resolve + from blastradius.store import Store + from blastradius.index import db_path_for, git_reachable, git_resolve repo, c1, c2, c3 = _make_temporal_repo(tmp_path) db_path = db_path_for(repo) @@ -119,8 +119,8 @@ def test_as_of_impact_differs_from_head(tmp_path: Path) -> None: def test_changed_since_edges(tmp_path: Path) -> None: """changed_since(reachable_C1) shows added edge at C2 and its removal.""" - from codeindex.store import Store - from codeindex.index import db_path_for, git_reachable + from blastradius.store import Store + from blastradius.index import db_path_for, git_reachable repo, c1, c2, c3 = _make_temporal_repo(tmp_path) db_path = db_path_for(repo) @@ -141,10 +141,10 @@ def test_changed_since_edges(tmp_path: Path) -> None: # ── Test 3: history backfill populates commits table ───────────────────────── def test_history_backfill_commits(tmp_path: Path) -> None: - """codeindex history populates the commits table.""" - from codeindex.store import Store - from codeindex.index import db_path_for - from codeindex.temporal import backfill + """blastradius history populates the commits table.""" + from blastradius.store import Store + from blastradius.index import db_path_for + from blastradius.temporal import backfill repo = tmp_path / "hist_repo" shutil.copytree(FIXTURE_SRC, repo) @@ -175,9 +175,9 @@ def test_history_backfill_commits(tmp_path: Path) -> None: def test_history_backfill_first_seen(tmp_path: Path) -> None: """After backfill, files have first_seen_commit set.""" - from codeindex.store import Store - from codeindex.index import db_path_for - from codeindex.temporal import backfill + from blastradius.store import Store + from blastradius.index import db_path_for + from blastradius.temporal import backfill repo = tmp_path / "seen_repo" shutil.copytree(FIXTURE_SRC, repo) @@ -206,9 +206,9 @@ def test_history_backfill_first_seen(tmp_path: Path) -> None: def test_history_no_working_tree_change(tmp_path: Path) -> None: """git status is clean before and after backfill (no checkout side-effects).""" - from codeindex.store import Store - from codeindex.index import db_path_for - from codeindex.temporal import backfill + from blastradius.store import Store + from blastradius.index import db_path_for + from blastradius.temporal import backfill repo = tmp_path / "clean_repo" shutil.copytree(FIXTURE_SRC, repo) @@ -231,7 +231,7 @@ def test_history_no_working_tree_change(tmp_path: Path) -> None: f"Backfill modified working-tree file: {f}" ) - # Git status should show clean (only .codeindex/ and generated files differ) + # Git status should show clean (only .blastradius/ and generated files differ) status = subprocess.run( ["git", "status", "--porcelain", "--", "*.py"], cwd=repo, capture_output=True, text=True, @@ -243,8 +243,8 @@ def test_history_no_working_tree_change(tmp_path: Path) -> None: def test_changed_since_added_file(tmp_path: Path) -> None: """changed_since(C1) shows a file added at C2.""" - from codeindex.store import Store - from codeindex.index import db_path_for, git_reachable + from blastradius.store import Store + from blastradius.index import db_path_for, git_reachable repo = tmp_path / "addfile_repo" shutil.copytree(FIXTURE_SRC, repo) diff --git a/tests/test_phase3.py b/tests/test_phase3.py index 72eced7..c55f4f3 100644 --- a/tests/test_phase3.py +++ b/tests/test_phase3.py @@ -65,7 +65,7 @@ def _commit_all(repo: Path, message: str) -> str: def _run_analyze(repo: Path) -> None: - import codeindex.index as idx_mod + import blastradius.index as idx_mod importlib.reload(idx_mod) idx_mod.build(str(repo)) @@ -92,8 +92,8 @@ def _make_repo_with_symbols(tmp_path: Path) -> Path: def test_fts_search_returns_results(tmp_path: Path) -> None: """fts_search finds symbols by name keyword without any embedding.""" - from codeindex.index import db_path_for - from codeindex.store import Store + from blastradius.index import db_path_for + from blastradius.store import Store repo = _make_repo_with_symbols(tmp_path) store = Store(db_path_for(repo)) @@ -123,9 +123,9 @@ def test_fts_search_returns_results(tmp_path: Path) -> None: def test_hybrid_search_stub_provider(tmp_path: Path) -> None: """hybrid_search returns results using stub embeddings + FTS, no live endpoint.""" - from codeindex.index import db_path_for - from codeindex.store import Store - from codeindex.semantic.search import hybrid_search + from blastradius.index import db_path_for + from blastradius.store import Store + from blastradius.semantic.search import hybrid_search repo = _make_repo_with_symbols(tmp_path) store = Store(db_path_for(repo)) @@ -169,9 +169,9 @@ def test_hybrid_search_stub_provider(tmp_path: Path) -> None: def test_search_degrades_without_sqlite_vec(tmp_path: Path, capsys) -> None: """hybrid_search falls back to FTS+graph and emits notice when vec unavailable.""" - from codeindex.index import db_path_for - from codeindex.store import Store - from codeindex.semantic.search import hybrid_search + from blastradius.index import db_path_for + from blastradius.store import Store + from blastradius.semantic.search import hybrid_search repo = _make_repo_with_symbols(tmp_path) store = Store(db_path_for(repo)) @@ -200,10 +200,10 @@ def test_search_degrades_without_sqlite_vec(tmp_path: Path, capsys) -> None: def test_search_degrades_endpoint_unreachable(tmp_path: Path) -> None: """hybrid_search falls back to FTS+graph when embedding endpoint errors.""" - from codeindex.index import db_path_for - from codeindex.store import Store - from codeindex.semantic.search import hybrid_search - from codeindex.semantic.provider import OpenAIEmbeddingProvider + from blastradius.index import db_path_for + from blastradius.store import Store + from blastradius.semantic.search import hybrid_search + from blastradius.semantic.provider import OpenAIEmbeddingProvider repo = _make_repo_with_symbols(tmp_path) store = Store(db_path_for(repo)) @@ -233,8 +233,8 @@ def test_search_degrades_endpoint_unreachable(tmp_path: Path) -> None: def test_graph_expand(tmp_path: Path) -> None: """graph_expand returns symbols from files that import or are imported by given symbols.""" - from codeindex.index import db_path_for - from codeindex.store import Store + from blastradius.index import db_path_for + from blastradius.store import Store repo = _make_repo_with_symbols(tmp_path) store = Store(db_path_for(repo)) @@ -260,8 +260,8 @@ def test_graph_expand(tmp_path: Path) -> None: def test_get_symbol_metadata(tmp_path: Path) -> None: """get_symbol returns name, file, kind, and other expected fields.""" - from codeindex.index import db_path_for - from codeindex.store import Store + from blastradius.index import db_path_for + from blastradius.store import Store repo = _make_repo_with_symbols(tmp_path) store = Store(db_path_for(repo)) @@ -285,9 +285,9 @@ def test_get_symbol_metadata(tmp_path: Path) -> None: def test_schema_version_is_current(tmp_path: Path) -> None: """Opening a fresh store sets schema_version to the current version.""" - from codeindex.store import Store, SCHEMA_VERSION + from blastradius.store import Store, SCHEMA_VERSION - db_path = tmp_path / ".codeindex" / "index.db" + db_path = tmp_path / ".blastradius" / "index.db" store = Store(db_path) version = store.get_meta("schema_version") store.close() diff --git a/uv.lock b/uv.lock index a845016..100f434 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 1 +revision = 3 requires-python = ">=3.9" resolution-markers = [ "python_full_version >= '3.10'", @@ -7,8 +7,8 @@ resolution-markers = [ ] [[package]] -name = "codeindex" -version = "0.3.5" +name = "blastradius" +version = "0.3.6" source = { editable = "." } [package.optional-dependencies] @@ -43,9 +43,9 @@ provides-extras = ["watch", "yaml", "toml", "dev", "semantic"] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] @@ -53,11 +53,11 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371 } +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740 }, + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, ] [[package]] @@ -67,9 +67,9 @@ source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.10'", ] -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] [[package]] @@ -79,36 +79,36 @@ source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.10'", ] -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503 } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484 }, + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] [[package]] name = "packaging" version = "26.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134 } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195 }, + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, ] [[package]] name = "pluggy" version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] [[package]] name = "pygments" version = "2.20.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991 } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151 }, + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] [[package]] @@ -119,17 +119,17 @@ resolution-markers = [ "python_full_version < '3.10'", ] dependencies = [ - { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, - { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "packaging", marker = "python_full_version < '3.10'" }, - { name = "pluggy", marker = "python_full_version < '3.10'" }, - { name = "pygments", marker = "python_full_version < '3.10'" }, - { name = "tomli", marker = "python_full_version < '3.10'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618 } +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750 }, + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, ] [[package]] @@ -140,90 +140,90 @@ resolution-markers = [ "python_full_version >= '3.10'", ] dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, - { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "pluggy", marker = "python_full_version >= '3.10'" }, - { name = "pygments", marker = "python_full_version >= '3.10'" }, - { name = "tomli", marker = "python_full_version == '3.10.*'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165 } +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249 }, + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, ] [[package]] name = "pyyaml" version = "6.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960 } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227 }, - { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019 }, - { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646 }, - { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793 }, - { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293 }, - { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872 }, - { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828 }, - { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415 }, - { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561 }, - { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826 }, - { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577 }, - { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556 }, - { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114 }, - { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638 }, - { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463 }, - { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986 }, - { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543 }, - { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763 }, - { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063 }, - { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973 }, - { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116 }, - { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011 }, - { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870 }, - { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089 }, - { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181 }, - { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658 }, - { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003 }, - { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344 }, - { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669 }, - { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252 }, - { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081 }, - { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159 }, - { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626 }, - { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613 }, - { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115 }, - { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427 }, - { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090 }, - { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246 }, - { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814 }, - { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809 }, - { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454 }, - { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355 }, - { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175 }, - { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228 }, - { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194 }, - { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429 }, - { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912 }, - { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108 }, - { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641 }, - { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901 }, - { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132 }, - { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261 }, - { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272 }, - { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923 }, - { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062 }, - { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341 }, - { url = "https://files.pythonhosted.org/packages/9f/62/67fc8e68a75f738c9200422bf65693fb79a4cd0dc5b23310e5202e978090/pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da", size = 184450 }, - { url = "https://files.pythonhosted.org/packages/ae/92/861f152ce87c452b11b9d0977952259aa7df792d71c1053365cc7b09cc08/pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917", size = 174319 }, - { url = "https://files.pythonhosted.org/packages/d0/cd/f0cfc8c74f8a030017a2b9c771b7f47e5dd702c3e28e5b2071374bda2948/pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9", size = 737631 }, - { url = "https://files.pythonhosted.org/packages/ef/b2/18f2bd28cd2055a79a46c9b0895c0b3d987ce40ee471cecf58a1a0199805/pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5", size = 836795 }, - { url = "https://files.pythonhosted.org/packages/73/b9/793686b2d54b531203c160ef12bec60228a0109c79bae6c1277961026770/pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a", size = 750767 }, - { url = "https://files.pythonhosted.org/packages/a9/86/a137b39a611def2ed78b0e66ce2fe13ee701a07c07aebe55c340ed2a050e/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926", size = 727982 }, - { url = "https://files.pythonhosted.org/packages/dd/62/71c27c94f457cf4418ef8ccc71735324c549f7e3ea9d34aba50874563561/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7", size = 755677 }, - { url = "https://files.pythonhosted.org/packages/29/3d/6f5e0d58bd924fb0d06c3a6bad00effbdae2de5adb5cda5648006ffbd8d3/pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0", size = 142592 }, - { url = "https://files.pythonhosted.org/packages/f0/0c/25113e0b5e103d7f1490c0e947e303fe4a696c10b501dea7a9f49d4e876c/pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007", size = 158777 }, + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, + { url = "https://files.pythonhosted.org/packages/9f/62/67fc8e68a75f738c9200422bf65693fb79a4cd0dc5b23310e5202e978090/pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da", size = 184450, upload-time = "2025-09-25T21:33:00.618Z" }, + { url = "https://files.pythonhosted.org/packages/ae/92/861f152ce87c452b11b9d0977952259aa7df792d71c1053365cc7b09cc08/pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917", size = 174319, upload-time = "2025-09-25T21:33:02.086Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cd/f0cfc8c74f8a030017a2b9c771b7f47e5dd702c3e28e5b2071374bda2948/pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9", size = 737631, upload-time = "2025-09-25T21:33:03.25Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b2/18f2bd28cd2055a79a46c9b0895c0b3d987ce40ee471cecf58a1a0199805/pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5", size = 836795, upload-time = "2025-09-25T21:33:05.014Z" }, + { url = "https://files.pythonhosted.org/packages/73/b9/793686b2d54b531203c160ef12bec60228a0109c79bae6c1277961026770/pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a", size = 750767, upload-time = "2025-09-25T21:33:06.398Z" }, + { url = "https://files.pythonhosted.org/packages/a9/86/a137b39a611def2ed78b0e66ce2fe13ee701a07c07aebe55c340ed2a050e/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926", size = 727982, upload-time = "2025-09-25T21:33:08.708Z" }, + { url = "https://files.pythonhosted.org/packages/dd/62/71c27c94f457cf4418ef8ccc71735324c549f7e3ea9d34aba50874563561/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7", size = 755677, upload-time = "2025-09-25T21:33:09.876Z" }, + { url = "https://files.pythonhosted.org/packages/29/3d/6f5e0d58bd924fb0d06c3a6bad00effbdae2de5adb5cda5648006ffbd8d3/pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0", size = 142592, upload-time = "2025-09-25T21:33:10.983Z" }, + { url = "https://files.pythonhosted.org/packages/f0/0c/25113e0b5e103d7f1490c0e947e303fe4a696c10b501dea7a9f49d4e876c/pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007", size = 158777, upload-time = "2025-09-25T21:33:15.55Z" }, ] [[package]] @@ -231,109 +231,109 @@ name = "sqlite-vec" version = "0.1.9" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/85/9fad0045d8e7c8df3e0fa5a56c630e8e15ad6e5ca2e6106fceb666aa6638/sqlite_vec-0.1.9-py3-none-macosx_10_6_x86_64.whl", hash = "sha256:1b62a7f0a060d9475575d4e599bbf94a13d85af896bc1ce86ee80d1b5b48e5fb", size = 131171 }, - { url = "https://files.pythonhosted.org/packages/a4/3d/3677e0cd2f92e5ebc43cd29fbf565b75582bff1ccfa0b8327c7508e1084f/sqlite_vec-0.1.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1d52e30513bae4cc9778ddbf6145610434081be4c3afe57cd877893bad9f6b6c", size = 165434 }, - { url = "https://files.pythonhosted.org/packages/00/d4/f2b936d3bdc38eadcbd2a87875815db36430fab0363182ba5d12cd8e0b51/sqlite_vec-0.1.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e921e592f24a5f9a18f590b6ddd530eb637e2d474e3b1972f9bbeb773aa3cb9", size = 160076 }, - { url = "https://files.pythonhosted.org/packages/6f/ad/6afd073b0f817b3e03f9e37ad626ae341805891f23c74b5292818f49ac63/sqlite_vec-0.1.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux1_x86_64.whl", hash = "sha256:1515727990b49e79bcaf75fdee2ffc7d461f8b66905013231251f1c8938e7786", size = 163388 }, - { url = "https://files.pythonhosted.org/packages/42/89/81b2907cda14e566b9bf215e2ad82fc9b349edf07d2010756ffdb902f328/sqlite_vec-0.1.9-py3-none-win_amd64.whl", hash = "sha256:4a28dc12fa4b53d7b1dced22da2488fade444e96b5d16fd2d698cd670675cf32", size = 292804 }, + { url = "https://files.pythonhosted.org/packages/68/85/9fad0045d8e7c8df3e0fa5a56c630e8e15ad6e5ca2e6106fceb666aa6638/sqlite_vec-0.1.9-py3-none-macosx_10_6_x86_64.whl", hash = "sha256:1b62a7f0a060d9475575d4e599bbf94a13d85af896bc1ce86ee80d1b5b48e5fb", size = 131171, upload-time = "2026-03-31T08:02:31.717Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3d/3677e0cd2f92e5ebc43cd29fbf565b75582bff1ccfa0b8327c7508e1084f/sqlite_vec-0.1.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1d52e30513bae4cc9778ddbf6145610434081be4c3afe57cd877893bad9f6b6c", size = 165434, upload-time = "2026-03-31T08:02:32.712Z" }, + { url = "https://files.pythonhosted.org/packages/00/d4/f2b936d3bdc38eadcbd2a87875815db36430fab0363182ba5d12cd8e0b51/sqlite_vec-0.1.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e921e592f24a5f9a18f590b6ddd530eb637e2d474e3b1972f9bbeb773aa3cb9", size = 160076, upload-time = "2026-03-31T08:02:33.796Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ad/6afd073b0f817b3e03f9e37ad626ae341805891f23c74b5292818f49ac63/sqlite_vec-0.1.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux1_x86_64.whl", hash = "sha256:1515727990b49e79bcaf75fdee2ffc7d461f8b66905013231251f1c8938e7786", size = 163388, upload-time = "2026-03-31T08:02:34.888Z" }, + { url = "https://files.pythonhosted.org/packages/42/89/81b2907cda14e566b9bf215e2ad82fc9b349edf07d2010756ffdb902f328/sqlite_vec-0.1.9-py3-none-win_amd64.whl", hash = "sha256:4a28dc12fa4b53d7b1dced22da2488fade444e96b5d16fd2d698cd670675cf32", size = 292804, upload-time = "2026-03-31T08:02:36.035Z" }, ] [[package]] name = "tomli" version = "2.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543 } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704 }, - { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454 }, - { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561 }, - { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824 }, - { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227 }, - { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859 }, - { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204 }, - { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084 }, - { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285 }, - { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924 }, - { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018 }, - { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948 }, - { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341 }, - { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159 }, - { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290 }, - { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141 }, - { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847 }, - { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088 }, - { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866 }, - { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887 }, - { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704 }, - { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628 }, - { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180 }, - { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674 }, - { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976 }, - { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755 }, - { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265 }, - { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726 }, - { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859 }, - { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713 }, - { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084 }, - { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973 }, - { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223 }, - { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973 }, - { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082 }, - { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490 }, - { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263 }, - { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736 }, - { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717 }, - { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461 }, - { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855 }, - { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144 }, - { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683 }, - { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196 }, - { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393 }, - { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583 }, + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, ] [[package]] name = "typing-extensions" version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391 } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614 }, + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] [[package]] name = "watchdog" version = "6.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220 } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390 }, - { url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389 }, - { url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020 }, - { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393 }, - { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392 }, - { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019 }, - { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471 }, - { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449 }, - { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054 }, - { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480 }, - { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451 }, - { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057 }, - { url = "https://files.pythonhosted.org/packages/05/52/7223011bb760fce8ddc53416beb65b83a3ea6d7d13738dde75eeb2c89679/watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8", size = 96390 }, - { url = "https://files.pythonhosted.org/packages/9c/62/d2b21bc4e706d3a9d467561f487c2938cbd881c69f3808c43ac1ec242391/watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a", size = 88386 }, - { url = "https://files.pythonhosted.org/packages/ea/22/1c90b20eda9f4132e4603a26296108728a8bfe9584b006bd05dd94548853/watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c", size = 89017 }, - { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902 }, - { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380 }, - { url = "https://files.pythonhosted.org/packages/5b/79/69f2b0e8d3f2afd462029031baafb1b75d11bb62703f0e1022b2e54d49ee/watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa", size = 87903 }, - { url = "https://files.pythonhosted.org/packages/e2/2b/dc048dd71c2e5f0f7ebc04dd7912981ec45793a03c0dc462438e0591ba5d/watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e", size = 88381 }, - { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079 }, - { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078 }, - { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076 }, - { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077 }, - { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078 }, - { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077 }, - { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078 }, - { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065 }, - { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070 }, - { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067 }, + { url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390, upload-time = "2024-11-01T14:06:24.793Z" }, + { url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389, upload-time = "2024-11-01T14:06:27.112Z" }, + { url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020, upload-time = "2024-11-01T14:06:29.876Z" }, + { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" }, + { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" }, + { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" }, + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, + { url = "https://files.pythonhosted.org/packages/05/52/7223011bb760fce8ddc53416beb65b83a3ea6d7d13738dde75eeb2c89679/watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8", size = 96390, upload-time = "2024-11-01T14:06:49.325Z" }, + { url = "https://files.pythonhosted.org/packages/9c/62/d2b21bc4e706d3a9d467561f487c2938cbd881c69f3808c43ac1ec242391/watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a", size = 88386, upload-time = "2024-11-01T14:06:50.536Z" }, + { url = "https://files.pythonhosted.org/packages/ea/22/1c90b20eda9f4132e4603a26296108728a8bfe9584b006bd05dd94548853/watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c", size = 89017, upload-time = "2024-11-01T14:06:51.717Z" }, + { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902, upload-time = "2024-11-01T14:06:53.119Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380, upload-time = "2024-11-01T14:06:55.19Z" }, + { url = "https://files.pythonhosted.org/packages/5b/79/69f2b0e8d3f2afd462029031baafb1b75d11bb62703f0e1022b2e54d49ee/watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa", size = 87903, upload-time = "2024-11-01T14:06:57.052Z" }, + { url = "https://files.pythonhosted.org/packages/e2/2b/dc048dd71c2e5f0f7ebc04dd7912981ec45793a03c0dc462438e0591ba5d/watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e", size = 88381, upload-time = "2024-11-01T14:06:58.193Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, ] diff --git a/viz/explorer.html b/viz/explorer.html index 6dc87f5..050dcd5 100644 --- a/viz/explorer.html +++ b/viz/explorer.html @@ -3,7 +3,7 @@ -codeindex +blastradius + + +
+
...
+
...
+
...
+
+ + +``` + +## Header + +Repo name, date, and a compact legend: solid box = module, dashed line = seam, red arrow = leakage, thick dark box = deep module. No introduction paragraph — straight into the candidates. + +## Candidate card + +The diagrams carry the weight. Prose is sparse, plain, and uses the glossary terms (from the `/codebase-design` skill) without ceremony. + +Each candidate is one `
`: + +- **Title** — short, names the deepening (e.g. "Collapse the Order intake pipeline"). +- **Badge row** — recommendation strength (`Strong` = emerald, `Worth exploring` = amber, `Speculative` = slate), plus a tag for the dependency category (`in-process`, `local-substitutable`, `ports & adapters`, `mock`). +- **Files** — monospaced list, `font-mono text-sm`. +- **Before / After diagram** — the centrepiece. Two columns, side by side. See patterns below. +- **Problem** — one sentence. What hurts. +- **Solution** — one sentence. What changes. +- **Wins** — bullets, ≤6 words each. e.g. "Tests hit one interface", "Pricing logic stops leaking", "Delete 4 shallow wrappers". +- **ADR callout** (if applicable) — one line in an amber-tinted box. + +No paragraphs of explanation. If the diagram needs a paragraph to be understood, redraw the diagram. + +## Diagram patterns + +Pick the pattern that fits the candidate. Mix them. Don't make every diagram look the same — variety is part of the point. + +### Mermaid graph (the workhorse for dependencies / call flow) + +Use a Mermaid `flowchart` or `graph` when the point is "X calls Y calls Z, and look at the mess." Wrap it in a Tailwind-styled card so it doesn't feel parachuted in. Style with classDef to colour leakage edges red and the deep module dark. Sequence diagrams work well for "before: 6 round-trips; after: 1." + +```html +
+
+    flowchart LR
+      A[OrderHandler] --> B[OrderValidator]
+      B --> C[OrderRepo]
+      C -.leak.-> D[PricingClient]
+      classDef leak stroke:#dc2626,stroke-width:2px;
+      class C,D leak
+  
+
+``` + +### Hand-built boxes-and-arrows (when Mermaid's layout fights you) + +Modules as `
`s with borders and labels. Arrows as inline SVG `` or `` elements positioned absolutely over a relative container. Reach for this when you want the "after" diagram to feel like one thick-bordered deep module with greyed-out internals — Mermaid won't render that with the right weight. + +### Cross-section (good for layered shallowness) + +Stack horizontal bands (`h-12 border-l-4`) to show layers a call passes through. Before: 6 thin layers each doing nothing. After: 1 thick band labelled with the consolidated responsibility. + +### Mass diagram (good for "interface as wide as implementation") + +Two rectangles per module — one for interface surface area, one for implementation. Before: interface rectangle is nearly as tall as the implementation rectangle (shallow). After: interface rectangle is short, implementation rectangle is tall (deep). + +### Call-graph collapse + +Before: a tree of function calls rendered as nested boxes. After: the same tree collapsed into one box, with the now-internal calls shown faded inside it. + +## Style guidance + +- Lean editorial, not corporate-dashboard. Generous whitespace. Serif optional for headings (`font-serif` works well with stone/slate). +- Colour sparingly: one accent (emerald or indigo) plus red for leakage and amber for warnings. +- Keep diagrams ~320px tall so before/after sits comfortably side by side without scrolling. +- Use `text-xs uppercase tracking-wider` for module labels inside diagrams — they should read as schematic, not as UI. +- The only scripts are the Tailwind CDN and the Mermaid ESM import. The report is otherwise static — no app code, no interactivity beyond Mermaid's own rendering. + +## Top recommendation section + +One larger card. Candidate name, one sentence on why, anchor link to its card. That's it. + +## Tone + +Plain English, concise — but the architectural nouns and verbs come straight from the `/codebase-design` skill. Concision is not an excuse to drift. + +**Use exactly:** module, interface, implementation, depth, deep, shallow, seam, adapter, leverage, locality. + +**Never substitute:** component, service, unit (for module) · API, signature (for interface) · boundary (for seam) · layer, wrapper (for module, when you mean module). + +**Phrasings that fit the style:** + +- "Order intake module is shallow — interface nearly matches the implementation." +- "Pricing leaks across the seam." +- "Deepen: one interface, one place to test." +- "Two adapters justify the seam: HTTP in prod, in-memory in tests." + +**Wins bullets** name the gain in glossary terms: *"locality: bugs concentrate in one module"*, *"leverage: one interface, N call sites"*, *"interface shrinks; implementation absorbs the wrappers"*. Don't write *"easier to maintain"* or *"cleaner code"* — those terms aren't in the glossary and don't earn their place. + +No hedging, no throat-clearing, no "it's worth noting that…". If a sentence could be a bullet, make it a bullet. If a bullet could be cut, cut it. If a term isn't in the `/codebase-design` glossary, reach for one that is before inventing a new one. diff --git a/.agents/skills/improve-codebase-architecture/SKILL.md b/.agents/skills/improve-codebase-architecture/SKILL.md new file mode 100644 index 0000000..b56969e --- /dev/null +++ b/.agents/skills/improve-codebase-architecture/SKILL.md @@ -0,0 +1,71 @@ +--- +name: improve-codebase-architecture +description: Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick. +disable-model-invocation: true +--- + +# Improve Codebase Architecture + +Surface architectural friction and propose **deepening opportunities** — refactors that turn shallow modules into deep ones. The aim is testability and AI-navigability. + +This command is _informed_ by the project's domain model and built on a shared design vocabulary: + +- Run the `/codebase-design` skill for the architecture vocabulary (**module**, **interface**, **depth**, **seam**, **adapter**, **leverage**, **locality**) and its principles (the deletion test, "the interface is the test surface", "one adapter = hypothetical seam, two = real"). Use these terms exactly in every suggestion — don't drift into "component," "service," "API," or "boundary." +- The domain language in `CONTEXT.md` gives names to good seams; ADRs in `docs/adr/` record decisions this command should not re-litigate. + +## Process + +### 1. Explore + +**Scope before you scan — YAGNI.** Deepening a module pays off by making future changes to it easier, so put extra weight on the parts of the codebase that have recently changed. Decide *where* to look before you look: + +- If the user named a direction — a module, a subsystem, a pain point — take it, and skip the inference below. +- Otherwise, walk back a good stretch of the commit history (`git log --oneline`) to find the codebase's hot spots — the files and areas that keep coming up — and let those paths pull your attention first. If the changes are scattered with no clear hot spot, widen the net. + +Read the project's domain glossary (`CONTEXT.md`) and any ADRs in the area you're touching first. + +Then use the Agent tool with `subagent_type=Explore` to walk the codebase. Don't follow rigid heuristics — explore organically and note where you experience friction: + +- Where does understanding one concept require bouncing between many small modules? +- Where are modules **shallow** — interface nearly as complex as the implementation? +- Where have pure functions been extracted just for testability, but the real bugs hide in how they're called (no **locality**)? +- Where do tightly-coupled modules leak across their seams? +- Which parts of the codebase are untested, or hard to test through their current interface? + +Apply the **deletion test** to anything you suspect is shallow: would deleting it concentrate complexity, or just move it? A "yes, concentrates" is the signal you want. + +### 2. Present candidates as an HTML report + +Write a self-contained HTML file to the OS temp directory so nothing lands in the repo. Resolve the temp dir from `$TMPDIR`, falling back to `/tmp` (or `%TEMP%` on Windows), and write to `/architecture-review-.html` so each run gets a fresh file. Open it for the user — `xdg-open ` on Linux, `open ` on macOS, `start ` on Windows — and tell them the absolute path. + +The report uses **Tailwind via CDN** for layout and styling, and **Mermaid via CDN** for diagrams where a graph/flow/sequence reliably communicates the structure. Mix Mermaid with hand-crafted CSS/SVG visuals — use Mermaid when relationships are graph-shaped (call graphs, dependencies, sequences), and hand-built divs/SVG when you want something more editorial (mass diagrams, cross-sections, collapse animations). Each candidate gets a **before/after visualisation**. Be visual. + +For each candidate, render a card with: + +- **Files** — which files/modules are involved +- **Problem** — why the current architecture is causing friction +- **Solution** — plain English description of what would change +- **Benefits** — explained in terms of locality and leverage, and how tests would improve +- **Before / After diagram** — side-by-side, custom-drawn, illustrating the shallowness and the deepening +- **Recommendation strength** — one of `Strong`, `Worth exploring`, `Speculative`, rendered as a badge + +End the report with a **Top recommendation** section: which candidate you'd tackle first and why. + +**Use CONTEXT.md vocabulary for the domain, and the `/codebase-design` vocabulary for the architecture.** If `CONTEXT.md` defines "Order," talk about "the Order intake module" — not "the FooBarHandler," and not "the Order service." + +**ADR conflicts**: if a candidate contradicts an existing ADR, only surface it when the friction is real enough to warrant revisiting the ADR. Mark it clearly in the card (e.g. a warning callout: _"contradicts ADR-0007 — but worth reopening because…"_). Don't list every theoretical refactor an ADR forbids. + +See [HTML-REPORT.md](HTML-REPORT.md) for the full HTML scaffold, diagram patterns, and styling guidance. + +Do NOT propose interfaces yet. After the file is written, ask the user: "Which of these would you like to explore?" + +### 3. Grilling loop + +Once the user picks a candidate, run the `/grilling` skill to walk the decision tree with them — constraints, dependencies, the shape of the deepened module, what sits behind the seam, what tests survive. + +Side effects happen inline as decisions crystallize — run the `/domain-modeling` skill to keep the domain model current as you go: + +- **Naming a deepened module after a concept not in `CONTEXT.md`?** Add the term to `CONTEXT.md`. Create the file lazily if it doesn't exist. +- **Sharpening a fuzzy term during the conversation?** Update `CONTEXT.md` right there. +- **User rejects the candidate with a load-bearing reason?** Offer an ADR, framed as: _"Want me to record this as an ADR so future architecture reviews don't re-suggest it?"_ Only offer when the reason would actually be needed by a future explorer to avoid re-suggesting the same thing — skip ephemeral reasons ("not worth it right now") and self-evident ones. +- **Want to explore alternative interfaces for the deepened module?** Run the `/codebase-design` skill and use its design-it-twice parallel sub-agent pattern. diff --git a/.agents/skills/improve-codebase-architecture/agents/openai.yaml b/.agents/skills/improve-codebase-architecture/agents/openai.yaml new file mode 100644 index 0000000..706fdca --- /dev/null +++ b/.agents/skills/improve-codebase-architecture/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "Improve Codebase Architecture" + short_description: "Find and grill architecture improvements" +policy: + allow_implicit_invocation: false diff --git a/.agents/skills/prototype/LOGIC.md b/.agents/skills/prototype/LOGIC.md new file mode 100644 index 0000000..fe9a2c2 --- /dev/null +++ b/.agents/skills/prototype/LOGIC.md @@ -0,0 +1,79 @@ +# Logic Prototype + +A tiny interactive terminal app that lets the user drive a state model by hand. Use this when the question is about **business logic, state transitions, or data shape** — the kind of thing that looks reasonable on paper but only feels wrong once you push it through real cases. + +## When this is the right shape + +- "I'm not sure if this state machine handles the edge case where X then Y." +- "Does this data model actually let me represent the case where..." +- "I want to feel out what the API should look like before writing it." +- Anything where the user wants to **press buttons and watch state change**. + +If the question is "what should this look like" — wrong branch. Use [UI.md](UI.md). + +## Process + +### 1. State the question + +Before writing code, write down what state model and what question you're prototyping. One paragraph, in the prototype's README or a comment at the top of the file. A logic prototype that answers the wrong question is pure waste — make the question explicit so it can be checked later, whether the user is watching now or returning to it AFK. + +### 2. Pick the language + +Use whatever the host project uses. If the project has no obvious runtime (e.g. a docs repo), ask. + +Match the project's existing conventions for tooling — don't add a new package manager or runtime just for the prototype. + +### 3. Isolate the logic in a portable module + +Put the actual logic — the bit that's answering the question — behind a small, pure interface that could be lifted out and dropped into the real codebase later. The TUI around it is throwaway; the logic module shouldn't be. + +The right shape depends on the question: + +- **A pure reducer** — `(state, action) => state`. Good when actions are discrete events and state is a single value. +- **A state machine** — explicit states and transitions. Good when "which actions are even legal right now" is part of the question. +- **A small set of pure functions** over a plain data type. Good when there's no implicit current state — just transformations. +- **A class or module with a clear method surface** when the logic genuinely owns ongoing internal state. + +Pick whichever shape best fits the question being asked, *not* whichever is easiest to wire to a TUI. Keep it pure: no I/O, no terminal code, no `console.log` for control flow. The TUI imports it and calls into it; nothing flows the other direction. + +This is what makes the prototype useful past its own lifetime: when the question's been answered, the validated reducer / machine / function set can be lifted into the real module on its own. + +### 4. Build the smallest TUI that exposes the state + +Build it as a **lightweight TUI** — on every tick, clear the screen (`console.clear()` / `print("\033[2J\033[H")` / equivalent) and re-render the whole frame. The user should always see one stable view, not an ever-growing scrollback. + +Each frame has two parts, in this order: + +1. **Current state**, pretty-printed and diff-friendly (one field per line, or formatted JSON). Use **bold** for field names or section headers and **dim** for less important context (timestamps, IDs, derived values). Native ANSI escape codes are fine — `\x1b[1m` bold, `\x1b[2m` dim, `\x1b[0m` reset. No need to pull in a styling library unless one is already in the project. +2. **Keyboard shortcuts**, listed at the bottom: `[a] add user [d] delete user [t] tick clock [q] quit`. Bold the key, dim the description, or vice-versa — whatever reads cleanly. + +Behaviour: + +1. **Initialise state** — a single in-memory object/struct. Render the first frame on start. +2. **Read one keystroke (or one line)** at a time, dispatch to a handler that mutates state. +3. **Re-render** the full frame after every action — don't append, replace. +4. **Loop until quit.** + +The whole frame should fit on one screen. + +### 5. Make it runnable in one command + +Add a script to the project's existing task runner (`package.json` scripts, `Makefile`, `justfile`, `pyproject.toml`). The user should run `pnpm run ` or equivalent — never need to remember a path. + +If the host project has no task runner, just put the command at the top of the prototype's README. + +### 6. Hand it over + +Give the user the run command. They'll drive it themselves; the interesting moments are when they say "wait, that shouldn't be possible" or "huh, I assumed X would be different" — those are the bugs in the _idea_, which is the whole point. If they want new actions added, add them. Prototypes evolve. + +### 7. Capture the answer and the prototype + +Once the prototype has answered its question, capture the answer, then capture the prototype the way the [SKILL](SKILL.md) describes. The logic-specific mapping: the validated reducer / machine / function set lifts into the real module (the decision, absorbed); the TUI shell rides along to the throwaway branch that keeps the prototype as a primary source. + +## Anti-patterns + +- **Don't add tests.** A prototype that needs tests is no longer a prototype. +- **Don't wire it to the real database.** Use an in-memory store unless the question is specifically about persistence. +- **Don't generalise.** No "what if we wanted to support X later." The prototype answers one question. +- **Don't blur the logic and the TUI together.** If the reducer / state machine references `console.log`, prompts, or terminal escape codes, it's no longer portable. Keep the TUI as a thin shell over a pure module. +- **Don't ship the TUI shell into production.** The shell is optimised for being driven by hand from a terminal. The logic module behind it is the bit worth keeping. diff --git a/.agents/skills/prototype/SKILL.md b/.agents/skills/prototype/SKILL.md new file mode 100644 index 0000000..e75d533 --- /dev/null +++ b/.agents/skills/prototype/SKILL.md @@ -0,0 +1,26 @@ +--- +name: prototype +description: Build a throwaway prototype to answer a design question. Use when the user wants to sanity-check whether a state model or logic feels right, or explore what a UI should look like. +--- + +# Prototype + +A prototype is **throwaway code that answers a question**. The question decides the shape. + +## Pick a branch + +Identify which question is being answered — from the user's prompt, the surrounding code, or by asking if the user is around: + +- **"Does this logic / state model feel right?"** → [LOGIC.md](LOGIC.md). Build a tiny interactive terminal app that pushes the state machine through cases that are hard to reason about on paper. +- **"What should this look like?"** → [UI.md](UI.md). Generate several radically different UI variations on a single route, switchable via a URL search param and a floating bottom bar. + +The two branches produce very different artifacts — getting this wrong wastes the whole prototype. If the question is genuinely ambiguous and the user isn't reachable, default to whichever branch better matches the surrounding code (a backend module → logic; a page or component → UI) and state the assumption at the top of the prototype. + +## Rules that apply to both + +1. **Throwaway from day one, and clearly marked as such.** Locate the prototype code close to where it will actually be used (next to the module or page it's prototyping for) so context is obvious — but name it so a casual reader can see it's a prototype, not production. For throwaway UI routes, obey whatever routing convention the project already uses; don't invent a new top-level structure. +2. **One command to run.** Whatever the project's existing task runner supports — `pnpm `, `python `, `bun `, etc. The user must be able to start it without thinking. +3. **No persistence by default.** State lives in memory. Persistence is the thing the prototype is _checking_, not something it should depend on. If the question explicitly involves a database, hit a scratch DB or a local file with a clear "PROTOTYPE — wipe me" name. +4. **Skip the polish.** No tests, no error handling beyond what makes the prototype _runnable_, no abstractions. The point is to learn something fast. +5. **Surface the state.** After every action (logic) or on every variant switch (UI), print or render the full relevant state so the user can see what changed. +6. **Capture it when done.** Fold any validated decision into the real code, then capture the prototype itself as a **primary source**: commit it to a throwaway branch, out of main, and leave a context pointer to that branch on the implementation issue. Capture the answer too — the verdict and the question it settled — in the issue or a commit. The main branch keeps only the validated decision. diff --git a/.agents/skills/prototype/UI.md b/.agents/skills/prototype/UI.md new file mode 100644 index 0000000..76c0f60 --- /dev/null +++ b/.agents/skills/prototype/UI.md @@ -0,0 +1,112 @@ +# UI Prototype + +Generate **several radically different UI variations** on a single route, switchable from a floating bottom bar. The user flips between variants in the browser, picks one (or steals bits from each), then throws the rest away. + +If the question is about logic/state rather than what something looks like — wrong branch. Use [LOGIC.md](LOGIC.md). + +## When this is the right shape + +- "What should this page look like?" +- "I want to see a few options for this dashboard before committing." +- "Try a different layout for the settings screen." +- Any time the user would otherwise spend a day picking between three vague mockups in their head. + +## Two sub-shapes — strongly prefer sub-shape A + +A UI prototype is much easier to judge when it's **butting up against the rest of the app** — real header, real sidebar, real data, real density. A throwaway route on its own is a vacuum: every variant looks fine in isolation. Default to sub-shape A whenever there's a plausible existing page to host the variants. Only reach for sub-shape B if the prototype genuinely has no nearby home. + +### Sub-shape A — adjustment to an existing page (preferred) + +The route already exists. Variants are rendered **on the same route**, gated by a `?variant=` URL search param. The existing data fetching, params, and auth all stay — only the rendering swaps. This is the default; pick it unless there's a specific reason not to. + +If the prototype is for something that doesn't yet have a page but *would naturally live inside one* (a new section of the dashboard, a new card on the settings screen, a new step in an existing flow) — that's still sub-shape A. Mount the variants inside the host page. + +### Sub-shape B — a new page (last resort) + +Only use this when the thing being prototyped genuinely has no existing page to live inside — e.g. an entirely new top-level surface, or a flow that can't be embedded anywhere sensible. + +Create a **throwaway route** following whatever routing convention the project already uses — don't invent a new top-level structure. Name it so it's obviously a prototype (e.g. include the word `prototype` in the path or filename). Same `?variant=` pattern. + +Before committing to sub-shape B, sanity-check: is there really no existing page this could be embedded in? An empty route hides design problems that a populated one would expose. + +In both sub-shapes the floating bottom bar is identical. + +## Process + +### 1. State the question and pick N + +Default to **3 variants**. More than 5 stops being radically different and starts being noise — cap there. + +Write down the plan in one line, in the prototype's location or a top-of-file comment: + +> "Three variants of the settings page, switchable via `?variant=`, on the existing `/settings` route." + +This works whether the user is here to push back or not. + +### 2. Generate radically different variants + +Draft each variant. Hold each one to: + +- The page's purpose and the data it has access to. +- The project's component library / styling system (TailwindCSS, shadcn, MUI, plain CSS, whatever). +- A clear exported component name, e.g. `VariantA`, `VariantB`, `VariantC`. + +Variants must be **structurally different** — different layout, different information hierarchy, different primary affordance, not just different colours. Three slightly-tweaked card grids isn't a UI prototype, it's wallpaper. If two drafts come out too similar, redo one with explicit "do not use a card grid" guidance. + +### 3. Wire them together + +Create a single switcher component on the route: + +```tsx +// pseudo-code — adapt to the project's framework +const variant = searchParams.get('variant') ?? 'A'; +return ( + <> + {variant === 'A' && } + {variant === 'B' && } + {variant === 'C' && } + + +); +``` + +For sub-shape A (existing page): keep all the existing data fetching above the switcher; only the rendered subtree changes per variant. + +For sub-shape B (new page): the throwaway route under `/prototype/` mounts the same switcher. + +### 4. Build the floating switcher + +A small fixed-position bar at the bottom-centre of the screen with three pieces: + +- **Left arrow** — cycles to the previous variant (wraps around). +- **Variant label** — shows the current variant key and, if the variant exports a name, that name too. e.g. `B — Sidebar layout`. +- **Right arrow** — cycles forward (wraps around). + +Behaviour: + +- Clicking an arrow updates the URL search param (use the framework's router — `router.replace` on Next, `navigate` on React Router, etc) so the variant is shareable and reload-stable. +- Keyboard: `←` and `→` arrow keys also cycle. Don't intercept arrow keys when an ``, `