diff --git a/.python-version b/.python-version
index e4fba21..24ee5b1 100644
--- a/.python-version
+++ b/.python-version
@@ -1 +1 @@
-3.12
+3.13
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..b8b1cdb
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,15 @@
+{
+ "python.languageServer": "None",
+ "windsurfPyright.analysis.typeCheckingMode": "basedpyright",
+ "editor.defaultFormatter": "Codeium.windsurfPyright",
+ "windsurfPyright.analysis.autoSearchPaths": true,
+ "windsurfPyright.analysis.useLibraryCodeForTypes": true,
+ "python.terminal.shellIntegration.enabled": true,
+ "windsurfPyright.analysis.inlayHints.callArgumentNames": false,
+ "windsurfPyright.analysis.inlayHints.functionReturnTypes": false,
+ "windsurfPyright.analysis.inlayHints.genericTypes": false,
+ "windsurfPyright.analysis.inlayHints.variableTypes": false,
+ "[python]": {
+ "editor.defaultFormatter": "charliermarsh.ruff"
+ }
+}
diff --git a/.windsurf/rules/python-guidelines.md b/.windsurf/rules/python-guidelines.md
new file mode 100644
index 0000000..94d6f78
--- /dev/null
+++ b/.windsurf/rules/python-guidelines.md
@@ -0,0 +1,59 @@
+---
+trigger: glob
+globs: **/*.py, src/**/*.py, tests/**/*.py
+---
+
+# Python Best Practices
+
+## Project Structure
+
+- Use src-layout with `src/your_package_name/`
+- Place tests in `tests/` directory parallel to `src/`
+- Keep configuration in `config/`
+- Store requirements in `pyproject.toml`
+- Place static files in `static/` directory
+- Use `templates/` for Jinja2 templates
+
+## Code Style
+
+- Follow Black code formatting
+- Use isort for import sorting
+- Follow PEP 8 naming conventions:
+ - snake_case for functions and variables
+ - PascalCase for classes
+ - UPPER_CASE for constants
+- Maximum line length of 88 characters (Black default)
+- Use absolute imports over relative imports
+
+## Type Hints
+
+- Use type hints for all function parameters and returns
+- Import types from `typing` module
+- Use `Optional[Type]` instead of `Type | None`
+- Define custom types in `types.py`
+- Use `Protocol` for duck typing
+
+## Documentation
+
+- Use Google-style docstrings
+- Document all public APIs
+- Use proper inline comments
+- Generate API documentation
+- Document environment setup
+
+## Development Workflow
+
+- Use virtual environments (venv)
+- Implement pre-commit hooks
+- Use proper Git workflow
+- Follow semantic versioning
+- Use proper CI/CD practices
+- Implement proper logging
+
+## Dependencies
+
+- Pin dependency versions
+- Separate dev dependencies
+- Use proper package versions
+- Regularly update dependencies
+- Check for security vulnerabilities
diff --git a/.windsurfrules b/.windsurfrules
new file mode 100644
index 0000000..490806e
--- /dev/null
+++ b/.windsurfrules
@@ -0,0 +1,92 @@
+# Robofactor Quick-Start
+
+## 1. Write Pure Functions Whenever Possible
+
+- **No side effects** → wrap I/O in `IO[T]` or `IOResult[T, E]`
+- **No mutation** → return new values
+- **No exceptions** → return `Result[T, E]` or `Maybe[T]`
+
+Push all side effects to CLI entrypoint.
+
+## 2. Type System (PEP 695)
+
+```python
+type UserId = int
+type Result[T, E] = Success[T] | Failure[E]
+
+@dataclass(frozen=True, slots=True)
+class User: ...
+```
+
+## 3. Function Rules
+
+- ≤ 20 lines, ≤ 4 params
+- Keyword-only after `*`
+- Use `flow` to compose:
+
+```python
+from returns.pipeline import flow
+from returns.pointfree import bind
+
+result = flow(
+ data,
+ validate,
+ bind(process),
+ bind(save),
+)
+```
+
+## 4. CLI (Typer)
+
+```python
+def cmd(
+ file: Annotated[Path, typer.Argument()],
+ out: Annotated[str, typer.Option("-o")] = "out.txt",
+) -> None:
+ ...
+```
+
+- Defaults live in the signature, **not** in `typer.Option`.
+- Use `rich` for output, `typer.Exit(code)` to quit.
+
+## 5. Error Handling
+
+```python
+def parse_int(s: str) -> Result[int, ValueError]:
+ try:
+ return Success(int(s))
+ except ValueError as e:
+ return Failure(e)
+
+match parse_int("42"):
+ case Success(n): ...
+ case Failure(e): ...
+```
+
+## 6. Testing
+
+- **pytest + hypothesis**
+- One assertion per test
+- Property tests for pure functions
+
+## 7. Tooling
+
+```bash
+uv run basedpyright --pythonversion 3.13
+uv run ruff check
+uv run ruff format
+```
+
+## 8. Forbidden
+
+- `Any`, `cast`, `# type: ignore`, `Optional`, `print`, `sys.exit`, mutable globals, `list`/`dict` in signatures.
+
+## 9. Project Skeleton
+
+```bash
+src/
+ robofactor/ # project source
+tests/
+ test_*.py # property tests
+pyproject.toml # uv + ruff + basedpyright
+```
diff --git a/Makefile b/Makefile
index 7930d6e..bc253b6 100644
--- a/Makefile
+++ b/Makefile
@@ -1,18 +1,16 @@
-.PHONY: help install install-dev clean test test-unit test-integration lint format type-check check build docs serve-docs readme
+.PHONY: help install install-dev clean test lint format type-check check readme
# Default target
help:
@echo "Available commands:"
@echo " install Install the package in production mode"
- @echo " install-dev Install the package in development mode"
+ @echo " install-dev Install the package in development mode with all groups"
@echo " clean Remove build artifacts and caches"
- @echo " test Run all tests"
- @echo " test-unit Run unit tests only"
- @echo " test-integration Run integration tests only"
- @echo " lint Run linting checks"
- @echo " format Format code with black and isort"
- @echo " type-check Run mypy type checking"
- @echo " check Run all checks (lint, type-check, test)"
+ @echo " test Run all tests with pytest"
+ @echo " lint Run ruff linting checks with auto-fix"
+ @echo " format Format code with ruff"
+ @echo " type-check Run basedpyright type checking"
+ @echo " check Run all checks (type-check, lint, test)"
@echo " readme Generate README.md using DSPy"
# Installation targets
@@ -22,19 +20,22 @@ install:
install-dev:
uv sync --all-groups
+# Cleaning
+clean:
+ rm -rf build dist .eggs *.egg-info
+ rm -rf .pytest_cache .ruff_cache .mypy_cache
+ rm -rf htmlcov .coverage coverage.xml
+ find . -type d -name __pycache__ -exec rm -rf {} +
+ find . -type f -name "*.pyc" -delete
+ find . -type f -name "*.pyo" -delete
+
# Testing
test:
- uv run pytest
-
-test-unit:
- uv run pytest tests/unit
-
-test-integration:
- uv run pytest tests/integration
-
-test-coverage:
- uv run pytest --cov-report=html
- @echo "Coverage report generated in htmlcov/index.html"
+ @if [ -n "$$(find tests -name '*.py' 2>/dev/null)" ]; then \
+ uv run pytest; \
+ else \
+ echo "No tests found in tests directory"; \
+ fi
# Code quality
lint:
@@ -42,14 +43,13 @@ lint:
format:
uv run ruff format src tests
- uv run isort src tests
type-check:
- uv run mypy src
+ uv run basedpyright --pythonversion 3.13 src
# Combined checks
-check: lint type-check test
+check: type-check lint test
# Documentation
readme:
- uv run scripts/generate_readme.py
+ uv run python scripts/generate_readme.py
diff --git a/README.md b/README.md
index 7f01bca..dedfbf6 100644
--- a/README.md
+++ b/README.md
@@ -1,185 +1,215 @@
# Robofactor
-The robot who refactors: /[^_^]\
+> The robot who refactors: /[^_^]\
-[](https://pypi.org/project/robofactor)
+[](https://pypi.org/project/robofactor/)
[](https://github.com/ethan-wickstrom/robofactor/actions)
-[](https://github.com/ethan-wickstrom/robofactor)
-[](https://pypi.org/project/robofactor)
+[](https://github.com/ethan-wickstrom/robofactor/blob/main/LICENSE)
-## Table of Contents
+**Robofactor** is a DSPy-powered tool designed to analyze, plan, and refactor Python code. It leverages large language models to understand your code and suggest improvements, which are then programmatically verified for correctness and quality before being applied.
-- [Overview](#overview)
-- [Key Features](#key-features)
-- [Installation](#installation)
-- [Usage](#usage)
-- [How It Works](#how-it-works)
-- [Development](#development)
-- [Contributing](#contributing)
+Robofactor is a command-line tool powered by the [DSPy](https://github.com/stanford-futuredata/dspy) framework, designed to automatically analyze, refactor, and evaluate Python code. By leveraging the structured prompting capabilities of DSPy, it can understand your code, propose improvements, and verify the results. The ultimate goal is to serve as an AI-powered assistant that helps improve the quality, readability, and maintainability of your Python projects.
----
+## ✨ Features
-## Overview
+- 🤖 **AI-Powered Refactoring**: Leverages `dspy-ai` to analyze, plan, and refactor your Python code, improving readability, style, and structure.
+- 🐶 **Self-Refactoring Mode**: Use the `--dog-food` flag to turn Robofactor on itself, continuously improving its own codebase.
+- 📝 **In-Place File Writing**: Automatically write the improved code back to the source file with the `--write` option.
+- 🔧 **Configurable AI Models**: Easily switch between different LLMs for refactoring tasks (`--task-llm`) and prompt generation (`--prompt-llm`).
+- 📊 **Experiment Tracing**: Integrates seamlessly with MLflow to trace refactoring runs, monitor performance, and compare results.
+- 🧠 **DSPy Model Optimization**: Force a re-optimization of the underlying DSPy model with the `--optimize` flag to fine-tune the refactoring logic.
-Robofactor is a DSPy-powered tool to analyze, plan, and refactor Python code. It leverages a modern stack to programmatically assess and improve code quality through a structured, multi-step process.
+## 🚀 Installation
-The core technologies driving Robofactor include:
+Follow these steps to get Robofactor set up on your local machine.
-* **DSPy (`dspy-ai`):** The project is built on the DSPy framework, which provides a structured way to program with language models. It is used to generate refactoring plans and implement code changes.
-* **Railway-Oriented Pipelines (`returns`):** The evaluation process is constructed as a robust pipeline using the `returns` library. This allows for a series of checks (syntax, quality, functional correctness) where any failure gracefully halts the process and returns a descriptive error.
-* **Code Quality Analysis (`flake8`):** Code quality is programmatically measured using `flake8`, providing objective metrics to evaluate the effectiveness of the refactoring.
-* **Rich CLI (`rich`):** All terminal output, from the refactoring process to the final evaluation results, is formatted for clarity and readability using the `rich` library.
+### Prerequisites
-## Key Features
+- Python 3.12 or higher
+- [uv](https://github.com/astral-sh/uv) package manager. If you don't have it, you can install it via pip:
-* **AI-Powered Refactoring**: Leverages a `CodeRefactor` module built with DSPy (`dspy_modules.py`) to intelligently analyze and generate refactoring suggestions for Python code snippets.
-* **Comprehensive Evaluation Pipeline**: Ensures the quality and correctness of refactored code through a multi-stage process (`evaluation.py`). This pipeline includes syntax validation (`check_syntax`), quality scoring using `flake8` and AST analysis (`check_code_quality`), and functional correctness checks against provided test cases (`check_functional_correctness`).
-* **Advanced Code Analysis**: Performs deep static analysis of Python code by parsing it into an Abstract Syntax Tree (AST). The `function_extraction.py` module is dedicated to extracting detailed information about functions, decorators, and parameters directly from the source code structure.
-* **DSPy Model Optimization**: Features the ability to compile and optimize the underlying DSPy program for improved performance and accuracy. This can be triggered using the `--optimize` flag in the CLI (`main.py`).
-* **Interactive CLI**: Provides a user-friendly command-line interface built with `typer`. It uses `rich` to deliver clear, well-formatted, and colorized output for refactoring plans and evaluation results (`main.py`, `ui.py`).
-* **MLflow Integration**: Comes with built-in support for experiment tracing using MLflow. Users can configure the MLflow tracking URI and experiment name via CLI arguments (`--mlflow-uri`, `--mlflow-experiment`) to log and monitor refactoring runs (`main.py`).
+ ```bash
+ pip install uv
+ ```
-## Installation
+### Step-by-Step Guide
-Before you begin, ensure you have Python 3.10 or newer installed on your system. This project uses `uv` for fast and efficient dependency management.
+1. **Clone the repository:**
-### Standard Installation
+ ```bash
+ git clone https://github.com/ethan-wickstrom/robofactor.git
+ cd robofactor
+ ```
-To install Robofactor for regular use, clone the repository and run the following command from the project root:
+2. **Install dependencies:**
-```bash
-make install
-```
+ This project uses `uv` to manage dependencies and virtual environments.
+
+ - For a **standard installation** (to use the tool):
+
+ ```bash
+ uv sync --no-dev
+ ```
+
+ - For a **development installation** (includes testing and linting tools):
+
+ ```bash
+ uv sync --all-groups
+ ```
+
+## 🚀 Usage
-This command uses `uv` to install the package and its required dependencies.
+To refactor a Python file, run Robofactor from your command line and provide the path to the file:
-### Development Installation
+```bash
+robofactor path/to/your/file.py
+```
-If you plan to contribute to the project, you will need to install the development dependencies, which include tools for testing, linting, and type-checking. Use the following command:
+To have Robofactor refactor its own source code (a process often called "dogfooding"), use the `--dog-food` flag:
```bash
-make install-dev
+robofactor --dog-food
```
-This will install all dependencies, including the development-specific ones listed in `pyproject.toml`.
+By default, Robofactor prints the refactored code to the console without modifying the original file. To write the changes back to the source file, include the `--write` flag:
-## Usage
+```bash
+robofactor path/to/your/file.py --write
+```
-Robofactor is a command-line tool designed to analyze and refactor a single Python file.
+For a complete list of all available commands and options, see the help text below.
-To refactor a Python file, run the tool with the path to your script. By default, it performs a dry run, printing the proposed changes to the console without modifying the original file.
+
+Full CLI Options
```bash
-robofactor path/to/your/file.py
+ Usage: robofactor [OPTIONS] [PATH]
+
+ A DSPy-powered tool to analyze, plan, and refactor Python code.
+
+╭─ Arguments ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
+│ path [PATH] Path to the Python file to refactor. [default: None] │
+╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
+╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
+│ --dog-food Self-refactor the script you are running. │
+│ --write Write the refactored code back to the file. │
+│ --optimize Force re-optimization of the DSPy model. │
+│ --task-llm TEXT Model for the main refactoring task. [default: gemini/gemini-2.5-flash-lite-preview-06-17] │
+│ --prompt-llm TEXT Model for generating prompts during optimization. [default: gemini/gemini-2.5-pro] │
+│ --tracing --no-tracing Enable MLflow tracing. [default: tracing] │
+│ --mlflow-uri TEXT MLflow tracking server URI. [default: http://127.0.0.1:5000] │
+│ --mlflow-experiment TEXT MLflow experiment name. [default: robofactor] │
+│ --install-completion Install completion for the current shell. │
+│ --show-completion Show completion for the current shell, to copy it or customize the installation. │
+│ --help Show this message and exit. │
+╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
-### Example Workflow
+
-1. **Analyze the Code (Dry Run)**
+## ⚙️ How It Works
- Run Robofactor on a script to see the proposed refactoring. The tool will display the original code, the refactoring plan, the refactored code, and an evaluation of the changes.
+Robofactor operates through a systematic, three-stage pipeline to ensure that code is not only refactored but also improved in a safe and verifiable way.
- ```bash
- robofactor src/my_app/utils.py
- ```
+### 1. Code Parsing & Analysis
-2. **Apply the Changes**
+The process begins by deeply understanding the target Python file. Instead of treating the code as plain text, Robofactor uses Python's built-in `ast` (Abstract Syntax Tree) module to parse the source code into a structured tree.
- If you are satisfied with the proposed changes, you can write them back to the original file using the `--write` flag.
+- **Module:** `src/robofactor/function_extraction.py`
+- **Process:** This module traverses the AST to identify individual functions, their signatures (parameters, decorators, return types), docstrings, and body content. This granular understanding allows the AI to focus its efforts on a specific, well-defined piece of code.
- ```bash
- robofactor --write src/my_app/utils.py
- ```
+### 2. AI-Powered Refactoring with DSPy
-### Command-Line Options
+Once a function is isolated, it's handed over to the AI core for refactoring. Robofactor leverages `dspy-ai`, a framework for programming with language models, to create a robust and optimizable refactoring program.
-Here are some of the key arguments and options available. The descriptions are based on the output of `robofactor --help`.
+- **Module:** `src/robofactor/dspy_modules.py`
+- **Process:** The parsed function code is fed into a compiled DSPy program. This program instructs a Large Language Model (LLM) to rewrite the code with specific goals: improving readability, adding missing type hints, generating comprehensive docstrings, and adhering to Python best practices.
-| Argument / Option | Description |
-| --- | --- |
-| `PATH` | The path to the Python file you want to refactor. |
-| `--write` | Write the refactored code back to the original file. |
-| `--optimize` | Force re-optimization of the underlying DSPy model. |
-| `--dog-food` | A special mode to make Robofactor refactor its own source code. |
-| `--task-llm ` | Specify the language model for the main refactoring task. |
-| `--tracing / --no-tracing` | Enable or disable MLflow tracing for experiment tracking. |
-| `--mlflow-uri ` | Set the MLflow tracking server URI (default: `http://127.0.0.1:5000`). |
-| `--mlflow-experiment ` | Set the MLflow experiment name (default: `robofactor`). |
+### 3. Rigorous Evaluation
-For a complete list of all available options, run:
+The AI's suggested refactoring is never trusted blindly. Before any changes are accepted, the new code is subjected to a strict, multi-faceted evaluation pipeline. This pipeline is built using a railway-oriented approach with the `returns` library, ensuring that if any single check fails, the entire process halts safely.
-```bash
-robofactor --help
-```
+- **Module:** `src/robofactor/evaluation.py`
+- **Process:** The evaluation consists of several automated checks:
+ 1. **Syntax Check**: The refactored code is parsed again to ensure it is valid Python syntax.
+ 2. **Code Quality Analysis**: The code is linted using `flake8` to check for style guide violations, logical errors, and code smells.
+ 3. **Functional Correctness**: The original function's test cases are executed against the refactored code in a sandboxed environment. This critical step verifies that the refactoring did not alter the function's behavior or introduce regressions.
+
+Only if the refactored code passes all three checks is the process considered a success.
-## How It Works
+## 🔧 Configuration
-Robofactor follows a structured, multi-stage process to analyze, refactor, and evaluate Python code. The architecture is designed to be robust and transparent, leveraging modern tools for each step.
+You can configure Robofactor's behavior using command-line options, particularly for setting the language models and connecting to an MLflow instance for experiment tracing.
-1. **Code Parsing & Extraction**
- The process begins by parsing the target Python file. Using Python's built-in `ast` (Abstract Syntax Tree) module, the tool traverses the code's structure. As detailed in `src/robofactor/function_extraction.py`, it identifies every function and extracts comprehensive metadata, including its name, parameters, decorators, and docstring. This creates a structured representation of the code to be refactored.
+### Language Models (LLMs)
-2. **LLM-Powered Refactoring with DSPy**
- The extracted function code is then passed to a `dspy.Module`, specifically the `CodeRefactor` class found in `src/robofactor/dspy_modules.py`. This module contains a sophisticated prompt that instructs a Large Language Model (LLM) to analyze the provided code snippet, identify areas for improvement, and generate a refactored version. The LLM's goal is to enhance code quality, readability, and performance while preserving its original functionality.
+Robofactor uses two distinct language models: one for the primary refactoring task and another, typically more powerful, model for the one-time optimization process that generates effective prompts.
-3. **Programmatic Evaluation Pipeline**
- Once the LLM returns the refactored code, it undergoes a rigorous, automated evaluation pipeline defined in `src/robofactor/evaluation.py`. This pipeline, built using the `returns` library for robust error handling (railway-oriented programming), consists of several checks:
- * **Syntax Check**: Verifies that the generated code is valid Python.
- * **Quality Check**: Uses `flake8` to score the code against PEP 8 standards and other common issues.
- * **Functional Correctness**: Executes the refactored code against a set of predefined test cases to ensure it still produces the correct output.
- If any step fails, the pipeline short-circuits and reports the failure.
+- `--task-llm`: Specifies the model used for the core refactoring task.
+ - **Default**: `gemini/gemini-2.5-flash-lite-preview-06-17`
+- `--prompt-llm`: Specifies the model used during the DSPy optimization step (`--optimize`) to generate high-quality prompts.
+ - **Default**: `gemini/gemini-2.5-pro`
-4. **Rich Terminal Display**
- Finally, the results of the refactoring and evaluation are presented to the user in the terminal. The `src/robofactor/ui.py` module uses the `rich` library to create clear, well-formatted tables and panels that display the original code, the refactored code, the LLM's reasoning, and the detailed evaluation scores.
+**Example:**
-## Development
+```bash
+# Use OpenAI models for both tasks
+robofactor --task-llm "openai/gpt-4o-mini" --prompt-llm "openai/gpt-4o" path/to/your/file.py
+```
-To contribute to Robofactor, you'll need to set up a local development environment. This project uses `uv` for fast dependency management and a `Makefile` to provide convenient shortcuts for common tasks.
+### MLflow Tracing
-First, clone the repository:
+To monitor and debug the DSPy program's execution, Robofactor integrates with MLflow. Tracing is enabled by default.
+
+- `--no-tracing`: Use this flag to disable MLflow integration entirely.
+- `--mlflow-uri`: Sets the URI for your MLflow tracking server.
+ - **Default**: `http://127.0.0.1:5000`
+- `--mlflow-experiment`: Specifies the name of the MLflow experiment where runs will be logged.
+ - **Default**: `robofactor`
+
+**Example:**
```bash
-git clone https://github.com/ethan-wickstrom/robofactor.git
-cd robofactor
+# Run with a custom MLflow server and experiment name
+robofactor --mlflow-uri "http://your-mlflow-server:5001" --mlflow-experiment "refactor-audits" path/to/your/file.py
+
+# Run without any MLflow tracing
+robofactor --no-tracing path/to/your/file.py
```
-### Setup
+## 🛠️ Technology Stack
-To install all dependencies, including development tools like `ruff`, `mypy`, and `pytest`, run the following command. This will create a virtual environment and install all required packages.
+Robofactor is built on a modern stack of Python libraries, leveraging the power of LLMs, robust CLI frameworks, and functional programming principles.
+
+- **[DSPy-AI](https://github.com/stanford-futuredata/dspy)**: The core AI programming model used to create, optimize, and execute the refactoring logic with language models.
+- **[Typer](https://typer.tiangolo.com/)**: Powers the clean, user-friendly command-line interface.
+- **[Rich](https://github.com/Textualize/rich)**: Provides beautiful and informative terminal output, including progress spinners, tables, and syntax-highlighted code.
+- **[MLflow](https://mlflow.org/)**: Tracks and visualizes the refactoring process as experiments, enabling detailed analysis of the LLM's behavior.
+- **[Flake8](https://flake8.pycqa.org/en/latest/) & `ast`**: Used for static analysis of Python code, checking for syntax errors, code quality issues, and extracting function metadata.
+- **[Returns](https://returns.readthedocs.io/en/latest/)**: Implements a robust, railway-oriented programming pipeline for evaluating refactored code, ensuring each step is handled safely and declaratively.
+
+## 🧑💻 Development
+
+Contributions are welcome! To set up the development environment, first clone the repository. This project uses `uv` for dependency management. Install all dependencies, including development tools, with:
```bash
-make install-dev
+uv sync --all-groups
```
-### Common Development Tasks
-
-The `Makefile` includes several targets to streamline the development workflow:
-
-* **Run all checks:** To ensure code quality before committing, run all linters, type-checkers, and tests at once.
- ```bash
- make check
- ```
-* **Run tests:** Execute the test suite using pytest.
- ```bash
- make test
- ```
-* **Linting:** Check for code style issues and automatically apply fixes using Ruff.
- ```bash
- make lint
- ```
-* **Formatting:** Format the code using Ruff Formatter and isort.
- ```bash
- make format
- ```
-* **Type-checking:** Perform static type analysis with mypy.
- ```bash
- make type-check
- ```
-
-## Contributing
-
-Contributions are welcome! If you find a bug, have a feature request, or want to contribute to the code, please open an issue on our GitHub repository.
-
-- **Issues:** [https://github.com/ethan-wickstrom/robofactor/issues](https://github.com/ethan-wickstrom/robofactor/issues)
-
-Please check the existing issues to see if your suggestion has already been discussed.
+### Available Commands
+
+The project includes several helper commands to streamline development, which can be executed with `uv run `:
+
+- **`lint`**: Run linting checks.
+- **`format`**: Format code with black and isort.
+- **`type-check`**: Run mypy type checking.
+- **`test`**: Run all tests.
+- **`test-unit`**: Run unit tests only.
+- **`test-integration`**: Run integration tests only.
+- **`test-coverage`**: Run tests and generate an HTML coverage report.
+- **`check`**: Run all checks (lint, type-check, test).
+- **`readme`**: Generate README.md using DSPy.
+
+## 📜 License
+
+This project is licensed under the Apache Version 2.0 License. See the `LICENSE` file for more details.
diff --git a/docs/issues-in-typer.md b/docs/issues-in-typer.md
new file mode 100644
index 0000000..28f268c
--- /dev/null
+++ b/docs/issues-in-typer.md
@@ -0,0 +1,14 @@
+# Typer 0.16.0 Quick Fix
+
+**Problem:** `TypeError: Name 'X' defined twice` when using `Annotated` with `typer.Option`.
+**Fix:** Never put the default inside `Option()` when you also give the parameter a default.
+
+```python
+# ❌ Breaks
+mode: Annotated[DiffMode, typer.Option(DiffMode.both, "-m", "--mode")] = DiffMode.both
+
+# ✅ Works
+mode: Annotated[DiffMode, typer.Option("-m", "--mode")] = DiffMode.both
+```
+
+That’s it—remove the first positional argument from `Option()` and rely on the parameter default.
diff --git a/docs/modern_python_patterns.md b/docs/modern_python_patterns.md
new file mode 100644
index 0000000..4dc3c95
--- /dev/null
+++ b/docs/modern_python_patterns.md
@@ -0,0 +1,42 @@
+# Modern Python 3.13 Cheat-Sheet
+
+## 1. Type Aliases (PEP 695)
+
+```python
+type UserId = int
+type Result[T, E] = Success[T] | Failure[E]
+```
+
+Use them anywhere you repeat a complex type.
+
+## 2. Safe Null Handling (Maybe)
+
+Install: `pip install returns`
+
+```python
+from returns.maybe import Maybe, Some, Nothing
+
+def safe_get(d, k) -> Maybe[V]:
+ return Some(d[k]) if k in d else Nothing
+
+match safe_get(data, "key"):
+ case Some(v): use(v)
+ case Nothing: handle_missing()
+```
+
+Chain safely with `.bind()` instead of `if x is not None`.
+
+## 3. Type Guards (PEP 647)
+
+```python
+from typing import TypeGuard
+
+def is_email(s: str) -> TypeGuard[Email]:
+ return "@" in s and "." in s.split("@")[1]
+
+if is_email(raw):
+ # raw is now Email
+ send_mail(raw)
+```
+
+No casts, no runtime surprises. Never use Any, cast, or # type: ignore for type fixes.
diff --git a/pyproject.toml b/pyproject.toml
index 183d7a1..9345f5c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -16,7 +16,6 @@ classifiers = [
"Topic :: Software Development :: Libraries :: Application Frameworks",
]
dependencies = [
- "pyright>=1.1.402",
"typer>=0.16.0",
"rich>=13.7.1",
"pyflakes>=3.3.2",
@@ -25,6 +24,9 @@ dependencies = [
"mlflow>=3.1.0",
"toml>=0.10.2",
"returns>=0.25.0",
+ "dspy>=3.0.0b1",
+ "networkx>=3.4.2",
+ "grep-ast>=0.9.0",
]
[project.urls]
@@ -34,7 +36,7 @@ Issues = "https://github.com/ethan-wickstrom/robofactor/issues"
Documentation = "https://github.com/ethan-wickstrom/robofactor#readme"
[project.scripts]
-robofactor = "robofactor.main:app"
+robofactor = "robofactor.app.main:app"
[build-system]
requires = ["hatchling"]
@@ -44,11 +46,11 @@ build-backend = "hatchling.build"
packages = ["src/robofactor"]
[dependency-groups]
-dev = ["isort>=6.0.1", "mypy>=1.16.1", "ruff>=0.11.13", "toml>=0.10.2"]
+dev = ["basedpyright>=1.29.4", "ruff>=0.11.13", "toml>=0.10.2"]
[tool.ruff]
line-length = 100
-target-version = "py312"
+target-version = "py313"
[tool.ruff.lint]
select = [
@@ -65,28 +67,22 @@ ignore = [
"E501", # line too long (handled by formatter)
]
-[tool.ruff.lint.per-file-ignores]
-"tests/*" = ["S101"] # allow assert in tests
+[tool.basedpyright]
+include = ["src"]
+exclude = [
+ "**/node_modules",
+ "**/__pycache__",
+ "src/experimental",
+ "src/typestubs",
+]
+reportUnknownMemberType = false
+reportUnknownVariableType = false
+reportUnknownArgumentType = false
+reportMissingTypeStubs = false
-[tool.mypy]
-python_version = "3.12"
-plugins = ["returns.contrib.mypy.returns_plugin"]
-warn_return_any = true
-warn_unused_configs = true
-disallow_untyped_defs = true
-disallow_incomplete_defs = true
-check_untyped_defs = true
-disallow_untyped_decorators = false
-no_implicit_optional = true
-warn_redundant_casts = true
-warn_unused_ignores = true
-warn_no_return = true
-warn_unreachable = true
-strict_equality = true
+[tool.basedpyright.defineConstant]
+DEBUG = true
-[[tool.mypy.overrides]]
-module = "dspy.*"
-ignore_missing_imports = true
[tool.pytest.ini_options]
minversion = "8.0"
@@ -116,15 +112,5 @@ exclude_lines = [
"if __name__ == .__main__.:",
]
-[tool.isort]
-profile = "black"
-line_length = 100
-multi_line_output = 3
-include_trailing_comma = true
-
-[tool.pyright]
-venvPath = "."
-venv = ".venv"
-
[tool.uv.sources]
dspy = { git = "https://github.com/stanfordnlp/dspy.git" }
diff --git a/scripts/create_diffs.py b/scripts/create_diffs.py
new file mode 100644
index 0000000..a8a8608
--- /dev/null
+++ b/scripts/create_diffs.py
@@ -0,0 +1,162 @@
+#!/usr/bin/env python3
+"""Show staged/unstaged git diffs in smart Markdown format."""
+
+import subprocess
+from collections.abc import Sequence
+from dataclasses import dataclass
+from enum import Enum
+from typing import Annotated
+
+import typer
+from returns.result import Result, Success, Failure
+
+app = typer.Typer(help="Show staged/unstaged git diffs in Markdown")
+
+DiffOptions = Sequence[str]
+DiffText = str
+
+
+class DiffMode(str, Enum):
+ staged = "staged"
+ unstaged = "unstaged"
+ both = "both"
+
+
+class DiffType(str, Enum):
+ stat = "stat"
+ patch = "patch"
+
+
+@dataclass(frozen=True)
+class GitError:
+ command: tuple[str, ...]
+ return_code: int
+ stderr: str
+
+
+def build_diff_opts(is_stat: bool, context: int, word_diff: bool) -> DiffOptions:
+ """Return git-diff options for stat-only or patch with context."""
+ if is_stat:
+ return ("--stat",)
+ base: tuple[str, ...] = ("--minimal", f"-U{context}", "--color=never")
+ return base + (("--word-diff",) if word_diff else ())
+
+
+def run_git_diff(opts: DiffOptions) -> Result[DiffText, GitError]:
+ """
+ Runs `git` with the given options.
+ - return code >1 ⇒ Failure(GitError)
+ - return code ==1 ⇒ Success("") (no changes)
+ - return code ==0 ⇒ Success(stdout)
+ """
+ command = tuple(["git", *opts])
+ proc = subprocess.run(
+ command,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ text=True,
+ )
+ rc = proc.returncode
+ if rc > 1:
+ return Failure(GitError(command=command, return_code=rc, stderr=proc.stderr))
+ if rc == 1:
+ return Success("") # no changes
+ return Success(proc.stdout)
+
+
+def format_section(title: str, diff_text: str) -> str:
+ """Render a Markdown section for a diff under the given title."""
+ header = f"### {title}\n\n"
+ if not diff_text.strip():
+ return header + "_No changes_\n\n"
+ fence = "```diff" if diff_text.startswith(("diff --", "@@")) else "```text"
+ body = f"{fence}\n{diff_text.rstrip()}\n```\n\n"
+ return header + body
+
+
+@app.command()
+def main(
+ which: Annotated[
+ DiffMode,
+ typer.Option(
+ "-m",
+ "--mode",
+ help="Which diffs to show: 'staged', 'unstaged', or 'both' (default: both)",
+ ),
+ ] = DiffMode.both,
+ stat: Annotated[
+ bool,
+ typer.Option("--stat", help="Show only stat for all sections"),
+ ] = False,
+ word_diff: Annotated[
+ bool,
+ typer.Option("--word-diff", help="Enable word-level patch diff"),
+ ] = False,
+ context: Annotated[
+ int,
+ typer.Option("-c", "--context", help="Context lines (ignored if --stat)"),
+ ] = 3,
+ staged_type: Annotated[
+ DiffType | None,
+ typer.Option("--staged-type", help="Override mode for staged changes"),
+ ] = None,
+ unstaged_type: Annotated[
+ DiffType | None,
+ typer.Option("--unstaged-type", help="Override mode for unstaged changes"),
+ ] = None,
+) -> None:
+ """
+ Show staged/unstaged git diffs formatted as Markdown.
+ Errors are printed to stderr and exit with the git return code.
+ """
+
+ def section_is_stat(section_mode: DiffMode) -> bool:
+ if stat:
+ return True
+ override = staged_type if section_mode is DiffMode.staged else unstaged_type
+ return override is not None and override is DiffType.stat
+
+ staged_opts: DiffOptions = ("diff", "--cached") + tuple(
+ build_diff_opts(section_is_stat(DiffMode.staged), context, word_diff)
+ )
+ unstaged_opts: DiffOptions = ("diff",) + tuple(
+ build_diff_opts(section_is_stat(DiffMode.unstaged), context, word_diff)
+ )
+
+ # Staged
+ staged_diff = ""
+ if which is not DiffMode.unstaged:
+ res = run_git_diff(staged_opts)
+ if isinstance(res, Failure):
+ err = res.failure()
+ typer.secho(
+ f"Error running git {' '.join(err.command)}:\n{err.stderr}",
+ err=True,
+ fg="red",
+ )
+ raise typer.Exit(err.return_code)
+ staged_diff = res.unwrap()
+
+ # Unstaged
+ unstaged_diff = ""
+ if which is not DiffMode.staged:
+ res = run_git_diff(unstaged_opts)
+ if isinstance(res, Failure):
+ err = res.failure()
+ typer.secho(
+ f"Error running git {' '.join(err.command)}:\n{err.stderr}",
+ err=True,
+ fg="red",
+ )
+ raise typer.Exit(err.return_code)
+ unstaged_diff = res.unwrap()
+
+ # Print results
+ if which in (DiffMode.both, DiffMode.staged) and staged_diff:
+ print(format_section("Staged", staged_diff))
+ if which in (DiffMode.both, DiffMode.unstaged) and unstaged_diff:
+ print(format_section("Unstaged", unstaged_diff))
+
+
+if __name__ == "__main__":
+ app()
diff --git a/scripts/generate_readme.py b/scripts/generate_readme.py
index 4402b84..ca27592 100644
--- a/scripts/generate_readme.py
+++ b/scripts/generate_readme.py
@@ -1,23 +1,31 @@
+#!/usr/bin/env python3
+"""
+Intelligent README generator for the robofactor project.
+
+This module uses DSPy with Pydantic integration to analyze the project structure
+and generate a comprehensive README based on extracted information rather than assumptions.
+"""
+
from __future__ import annotations
-import enum
-import json
+import logging
import sys
-from dataclasses import asdict, dataclass, is_dataclass
from pathlib import Path
-from typing import Any
+from typing import Protocol
import dspy
import toml
import typer
+from pydantic import BaseModel, Field
from rich.console import Console
from rich.progress import Progress, SpinnerColumn, TextColumn
+# Add project root to path for imports
try:
project_root = Path(__file__).parent.parent.resolve()
sys.path.insert(0, str(project_root / "src"))
- from robofactor.function_extraction import FunctionInfo, parse_python_source
- from robofactor.main import app as cli_app
+ from robofactor.parsing.ast_parser import parse_python_source
+ from robofactor.app.main import app as cli_app
from robofactor.utils import suppress_pydantic_warnings
except ImportError as e:
print(
@@ -27,112 +35,238 @@
)
sys.exit(1)
-# --- Constants ---
-PYPROJECT_TOML_FILENAME = "pyproject.toml"
-MAKEFILE_FILENAME = "Makefile"
-PYPROJECT_PROJECT_KEY = "project"
-PYPROJECT_NAME_KEY = "name"
-PYPROJECT_DESC_KEY = "description"
+# Configure logging
+logging.basicConfig(
+ level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
+)
+logger = logging.getLogger(__name__)
+
+# --- Data Models (Pydantic) ---
-# --- Data Structures for Generation Pipeline ---
-@dataclass(frozen=True)
-class FileAnalysis:
- """Immutable representation of a source file's content and structure."""
+class FunctionMetadata(BaseModel):
+ """Metadata about a function extracted from source code."""
+
+ name: str
+ file_path: str
+ docstring: str | None
+ is_async: bool
+ decorators: list[str]
+ parameters: list[str]
+
+
+class SourceFileAnalysis(BaseModel):
+ """Analysis of a single source file."""
relative_path: str
- structure: tuple[FunctionInfo, ...]
+ functions: list[FunctionMetadata]
+ imports: list[str] = Field(default_factory=list)
+ classes: list[str] = Field(default_factory=list)
+
+class ProjectMetadata(BaseModel):
+ """Basic project metadata from pyproject.toml."""
+
+ name: str
+ description: str
+ version: str | None = None
+ authors: list[str] = Field(default_factory=list)
+ dependencies: list[str] = Field(default_factory=list)
+ dev_dependencies: list[str] = Field(default_factory=list)
+ homepage: str | None = None
+ repository: str | None = None
+
+
+class DevelopmentEnvironment(BaseModel):
+ """Extracted development environment information."""
+
+ package_manager: str = Field(
+ description="The package manager used (e.g., uv, pip, poetry)"
+ )
+ install_command: str = Field(description="Command to install the package")
+ dev_install_command: str = Field(
+ description="Command to install with dev dependencies"
+ )
+ available_commands: dict[str, str] = Field(
+ default_factory=dict,
+ description="Available make/task commands and their descriptions",
+ )
+ python_version: str | None = None
-@dataclass(frozen=True)
-class ReadmeSection:
- """Represents a proposed section for the README."""
+
+class ProjectFeatures(BaseModel):
+ """High-level features extracted from the project."""
+
+ core_technologies: list[str] = Field(description="Main technologies/libraries used")
+ cli_capabilities: list[str] = Field(
+ description="CLI commands and options available"
+ )
+ key_modules: dict[str, str] = Field(
+ description="Key modules and their purposes", default_factory=dict
+ )
+ testing_framework: str | None = None
+ code_quality_tools: list[str] = Field(default_factory=list)
+
+
+class ExtractedContext(BaseModel):
+ """Complete extracted context for README generation."""
+
+ metadata: ProjectMetadata
+ environment: DevelopmentEnvironment
+ features: ProjectFeatures
+ source_analyses: list[SourceFileAnalysis]
+ cli_help_text: str
+
+
+class ReadmeSection(BaseModel):
+ """A section in the README outline."""
title: str
description: str
+ priority: int = Field(default=5, ge=1, le=10)
-@dataclass(frozen=True)
-class GeneratedSection:
- """Represents a fully generated section with its Markdown content."""
+class GeneratedSection(BaseModel):
+ """A generated README section with content."""
title: str
content: str
-@dataclass(frozen=True)
-class ProjectContext:
- """Immutable snapshot of the entire project's state for generation."""
+# --- Service Interfaces (Dependency Injection) ---
- project_name: str
- project_description: str
- source_analyses: tuple[FileAnalysis, ...]
- config_files: dict[str, str]
- cli_help_text: str
+class FileReaderProtocol(Protocol):
+ """Protocol for file reading operations."""
-# --- Project Analysis Logic ---
-class ProjectAnalyzer:
- """Handles all file system I/O and static analysis of the project."""
+ def read_file(self, path: Path) -> str: ...
+ def file_exists(self, path: Path) -> bool: ...
- def __init__(self, root: Path, console: Console):
- """Initializes the analyzer."""
- self.root = root
- self.console = console
- self.source_dir = root / "src" / "robofactor"
- def _read_file(self, path: Path) -> str:
- """Reads a file, raising a FileNotFoundError on failure."""
+class CLIRunnerProtocol(Protocol):
+ """Protocol for running CLI commands."""
+
+ def get_help_text(self) -> str: ...
+
+
+# --- Concrete Service Implementations ---
+
+
+class FileReader:
+ """Handles file system operations."""
+
+ def read_file(self, path: Path) -> str:
+ """Read a file's contents."""
try:
return path.read_text(encoding="utf-8")
except FileNotFoundError:
- self.console.print(f"[bold red]Error: File not found at {path}[/]")
+ logger.error(f"File not found: {path}")
raise
except Exception as e:
- self.console.print(f"[bold red]Error: Failed to read {path}: {e}[/]")
+ logger.error(f"Failed to read {path}: {e}")
raise
- def _analyze_source_file(self, path: Path) -> FileAnalysis:
- """Parses a Python file to extract its structure."""
- content = self._read_file(path)
- try:
- # The `parse_python_source` function returns a `Result` container.
- # We `unwrap()` it to get the value or propagate the exception on failure.
- structure_result = parse_python_source(content, module_name=path.name)
- structure_iterator = structure_result.unwrap()
- return FileAnalysis(
- relative_path=str(path.relative_to(self.root)),
- structure=tuple(structure_iterator),
- )
- except Exception as e:
- self.console.print(f"[bold red]Error: Failed to parse AST for {path}: {e}[/]")
- raise
+ def file_exists(self, path: Path) -> bool:
+ """Check if a file exists."""
+ return path.exists()
- def get_cli_help_text(self) -> str:
- """Captures the --help output from the project's Typer CLI."""
- self.console.print("[dim]Capturing CLI help text...[/dim]")
+
+class CLIRunner:
+ """Handles CLI command execution."""
+
+ def get_help_text(self) -> str:
+ """Get the CLI help text."""
try:
from typer.testing import CliRunner
runner = CliRunner()
- cli_runner_result = runner.invoke(cli_app, ["--help"], catch_exceptions=False)
+ result = runner.invoke(cli_app, ["--help"], catch_exceptions=False)
+
+ if result.exit_code != 0:
+ error_msg = f"CLI failed with exit code {result.exit_code}"
+ logger.error(error_msg)
+ raise RuntimeError(error_msg)
+
+ return result.stdout
+ except Exception as e:
+ logger.error(f"Failed to get CLI help text: {e}")
+ raise
- if cli_runner_result.exit_code != 0:
- error_message = f"CLI command failed with exit code {cli_runner_result.exit_code}:\n{cli_runner_result.stderr or cli_runner_result.stdout}"
- raise RuntimeError(error_message)
- return cli_runner_result.stdout
+# --- Project Analyzer ---
+
+class ProjectAnalyzer:
+ """Analyzes project structure and extracts information."""
+
+ def __init__(
+ self,
+ root: Path,
+ file_reader: FileReaderProtocol,
+ cli_runner: CLIRunnerProtocol,
+ console: Console | None = None,
+ ):
+ self.root = root
+ self.file_reader = file_reader
+ self.cli_runner = cli_runner
+ self.console = console or Console()
+ self.source_dir = root / "src" / "robofactor"
+
+ def analyze_source_file(self, path: Path) -> SourceFileAnalysis:
+ """Analyze a Python source file."""
+ content = self.file_reader.read_file(path)
+
+ try:
+ result = parse_python_source(content, module_name=path.name)
+ functions = list(result.unwrap())
+
+ # Convert FunctionInfo to our simplified FunctionMetadata
+ func_metadata = [
+ FunctionMetadata(
+ name=f.name,
+ file_path=str(path.relative_to(self.root)),
+ docstring=f.docstring,
+ is_async=f.is_async,
+ decorators=[d.name for d in f.decorators],
+ parameters=[p.name for p in f.parameters],
+ )
+ for f in functions
+ ]
+
+ return SourceFileAnalysis(
+ relative_path=str(path.relative_to(self.root)), functions=func_metadata
+ )
except Exception as e:
- self.console.print(f"[bold red]Error: Failed to get CLI help text: {e}[/]")
+ logger.error(f"Failed to parse {path}: {e}")
raise
- def analyze(self) -> ProjectContext:
- """Performs a full analysis of the project."""
- self.console.print(f"[dim]Analyzing project at: {self.root}[/dim]")
+ def extract_project_metadata(self) -> ProjectMetadata:
+ """Extract metadata from pyproject.toml."""
+ pyproject_path = self.root / "pyproject.toml"
+ content = self.file_reader.read_file(pyproject_path)
+ data = toml.loads(content)
+
+ project = data.get("project", {})
+ deps = project.get("dependencies", [])
+ dev_deps = data.get("dependency-groups", {}).get("dev", [])
+ urls = project.get("urls", {})
+
+ return ProjectMetadata(
+ name=project.get("name", "Unknown"),
+ description=project.get("description", ""),
+ version=project.get("version"),
+ authors=[a.get("name", "") for a in project.get("authors", [])],
+ dependencies=deps,
+ dev_dependencies=dev_deps,
+ homepage=urls.get("Homepage"),
+ repository=urls.get("Repository"),
+ )
+ def analyze_all_source_files(self) -> list[SourceFileAnalysis]:
+ """Analyze all Python source files."""
py_files = [p for p in self.source_dir.glob("*.py") if p.name != "__init__.py"]
- analyses: list[FileAnalysis] = []
+ analyses = []
+
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
@@ -140,204 +274,197 @@ def analyze(self) -> ProjectContext:
transient=True,
) as progress:
task = progress.add_task("Analyzing source files...", total=len(py_files))
+
for file_path in py_files:
progress.update(task, description=f"Parsing {file_path.name}")
- analyses.append(self._analyze_source_file(file_path))
+ analyses.append(self.analyze_source_file(file_path))
progress.advance(task)
- config_files: dict[str, str] = {}
- required_configs = (PYPROJECT_TOML_FILENAME, MAKEFILE_FILENAME)
- for filename in required_configs:
- try:
- config_files[filename] = self._read_file(self.root / filename)
- except FileNotFoundError:
- self.console.print(f"[yellow]Warning: Config file '{filename}' not found. Skipping.[/yellow]")
-
- pyproject_data = toml.loads(config_files.get(PYPROJECT_TOML_FILENAME, ""))
- project_name = pyproject_data.get(PYPROJECT_PROJECT_KEY, {}).get(
- PYPROJECT_NAME_KEY, "Unknown Project"
- )
- project_desc = pyproject_data.get(PYPROJECT_PROJECT_KEY, {}).get(
- PYPROJECT_DESC_KEY, "No description found."
- )
- cli_help_text = self.get_cli_help_text()
-
- return ProjectContext(
- project_name=project_name,
- project_description=project_desc,
- source_analyses=tuple(analyses),
- config_files=config_files,
- cli_help_text=cli_help_text,
- )
+ return analyses
+ def get_cli_help(self) -> str:
+ """Get CLI help text."""
+ self.console.print("[dim]Capturing CLI help text...[/dim]")
+ return self.cli_runner.get_help_text()
-# --- JSON Serialization ---
-def _custom_json_encoder(obj: Any) -> Any:
- """A custom encoder to handle dataclasses and other special types."""
- if isinstance(obj, enum.Enum):
- return obj.value
- if is_dataclass(obj) and not isinstance(obj, type):
- return asdict(obj)
- if isinstance(obj, Path):
- return str(obj)
- raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
+# --- DSPy Signatures with Pydantic ---
-def to_json_string(data: Any) -> str:
- """Converts a Python object (including dataclasses) to a JSON string."""
- return json.dumps(data, default=_custom_json_encoder, indent=2)
+class ExtractPackageManager(dspy.Signature):
+ """Extract the package manager and installation commands from project files."""
-# --- DSPy Signatures for README Generation ---
-class GenerateReadmeOutline(dspy.Signature):
- """
- Generate a logical and comprehensive outline for a project's README.md file.
-
- IMPORTANT: You MUST prioritize information from the provided `project_context`
- over your own knowledge. The context contains the ground truth for this project,
- including file contents and configurations.
- """
-
- project_context: str = dspy.InputField(
- desc=(
- "A JSON object containing the project's ground truth. It includes: "
- "'project_name', 'project_description', 'source_analyses' (AST parsing of source files), "
- "'cli_help_text' (output of --help), and 'config_files'. The 'config_files' key holds the "
- "full content of important files like 'pyproject.toml' and 'Makefile'. "
- "Use 'Makefile' for installation and development commands. "
- "Use 'pyproject.toml' for dependencies and project metadata."
- )
+ makefile_content: str = dspy.InputField(desc="Content of the Makefile")
+ pyproject_content: str = dspy.InputField(desc="Content of pyproject.toml")
+ package_manager: str = dspy.OutputField(
+ desc="The package manager used (e.g., 'uv', 'pip', 'poetry')"
)
- outline: list[dict] = dspy.OutputField(
- desc=(
- "A list of sections for the README. Each item should be a dictionary with 'title' and 'description' keys. "
- "The description must specify what content to include in that section, referencing the ground truth from the project_context."
- )
+ install_command: str = dspy.OutputField(
+ desc="The exact command to install the package"
+ )
+ dev_install_command: str = dspy.OutputField(
+ desc="The exact command to install with dev dependencies"
)
-class GenerateSectionContent(dspy.Signature):
- """
- Generate the Markdown content for a single section of the README.
-
- IMPORTANT: You MUST prioritize information from the provided `project_context`
- over your own knowledge. Adhere strictly to the file contents provided in the context.
- For example, if the Makefile specifies using 'uv', you must use 'uv' in the installation instructions.
- """
-
- project_context: str = dspy.InputField(
- desc=(
- "A JSON object containing all analyzed information about the project. This is the ground truth. "
- "It includes 'project_name', 'project_description', 'source_analyses', 'cli_help_text', and "
- "'config_files' (containing the content of 'pyproject.toml' and 'Makefile')."
- )
- )
- section_title: str = dspy.InputField(desc="The title of the section to generate.")
- section_description: str = dspy.InputField(
- desc="A description of the content that should be in this section, as determined by the outline."
- )
- section_content: str = dspy.OutputField(
- desc="The fully-formed Markdown content for this specific section, grounded in the provided context."
+class ExtractDevelopmentCommands(dspy.Signature):
+ """Extract available development commands from Makefile."""
+
+ makefile_content: str = dspy.InputField(desc="Content of the Makefile")
+ commands: dict[str, str] = dspy.OutputField(
+ desc="Dictionary mapping command names to their descriptions"
)
-class AssembleReadme(dspy.Signature):
- """
- Assemble the final README.md from a list of generated sections.
+class ExtractProjectFeatures(dspy.Signature):
+ """Extract key features and technologies from the project."""
- Ensure the final output is clean, well-formatted, and includes a table of contents.
- """
+ metadata: ProjectMetadata = dspy.InputField()
+ source_analyses: list[SourceFileAnalysis] = dspy.InputField()
+ cli_help_text: str = dspy.InputField()
+ features: ProjectFeatures = dspy.OutputField()
- project_name: str = dspy.InputField(desc="The name of the project.")
- project_description: str = dspy.InputField(desc="A one-line description of the project.")
- generated_sections: str = dspy.InputField(
- desc="A JSON string of a list of generated sections, each with a 'title' and 'content'key."
+
+class GenerateReadmeOutline(dspy.Signature):
+ """Generate a README outline based on extracted context."""
+
+ context: ExtractedContext = dspy.InputField()
+ sections: list[ReadmeSection] = dspy.OutputField(
+ desc="List of sections for the README, ordered by priority"
)
+
+
+class GenerateSectionContent(dspy.Signature):
+ """Generate content for a specific README section."""
+
+ context: ExtractedContext = dspy.InputField()
+ section: ReadmeSection = dspy.InputField()
+ content: str = dspy.OutputField(desc="Markdown content for this section")
+
+
+class AssembleReadme(dspy.Signature):
+ """Assemble the final README from generated sections."""
+
+ project_name: str = dspy.InputField()
+ project_description: str = dspy.InputField()
+ sections: list[GeneratedSection] = dspy.InputField()
readme_content: str = dspy.OutputField(
- desc=(
- "The complete, final README.md content. It must include a title, the project description, "
- "a table of contents, and all the provided sections formatted professionally with Markdown."
- )
+ desc="Complete README.md content with proper formatting"
)
-# --- The Main DSPy Module ---
+# --- DSPy Modules ---
+
+
+class ContextExtractor(dspy.Module):
+ """Extracts specific context from project files."""
+
+ def __init__(self):
+ super().__init__()
+ self.package_extractor = dspy.ChainOfThought(ExtractPackageManager)
+ self.commands_extractor = dspy.ChainOfThought(ExtractDevelopmentCommands)
+ self.features_extractor = dspy.ChainOfThought(ExtractProjectFeatures)
+
+ def forward(
+ self,
+ metadata: ProjectMetadata,
+ source_analyses: list[SourceFileAnalysis],
+ makefile_content: str,
+ pyproject_content: str,
+ cli_help_text: str,
+ python_version: str | None = None,
+ ) -> ExtractedContext:
+ """Extract all context from project files."""
+
+ # Extract package manager and install commands
+ pkg_result = self.package_extractor(
+ makefile_content=makefile_content, pyproject_content=pyproject_content
+ )
+
+ # Extract development commands
+ cmd_result = self.commands_extractor(makefile_content=makefile_content)
+
+ # Create development environment
+ environment = DevelopmentEnvironment(
+ package_manager=pkg_result.package_manager,
+ install_command=pkg_result.install_command,
+ dev_install_command=pkg_result.dev_install_command,
+ available_commands=cmd_result.commands,
+ python_version=python_version,
+ )
+
+ # Extract project features
+ features_result = self.features_extractor(
+ metadata=metadata,
+ source_analyses=source_analyses,
+ cli_help_text=cli_help_text,
+ )
+
+ return ExtractedContext(
+ metadata=metadata,
+ environment=environment,
+ features=features_result.features,
+ source_analyses=source_analyses,
+ cli_help_text=cli_help_text,
+ )
+
+
class ReadmeGenerator(dspy.Module):
- """A DSPy module that orchestrates the entire README generation process."""
+ """Generates README content from extracted context."""
def __init__(self):
- """Initializes the sub-modules for each step of the generation pipeline."""
super().__init__()
self.outline_generator = dspy.ChainOfThought(GenerateReadmeOutline)
self.section_generator = dspy.ChainOfThought(GenerateSectionContent)
self.assembler = dspy.ChainOfThought(AssembleReadme)
- def forward(self, project_context: ProjectContext) -> dspy.Prediction:
- """
- Executes the two-stage README generation pipeline.
+ def forward(self, context: ExtractedContext) -> dspy.Prediction:
+ """Generate complete README from context."""
- Args:
- project_context: The analyzed state of the project.
+ # Generate outline
+ outline_result = self.outline_generator(context=context)
+ sections = sorted(outline_result.sections, key=lambda s: s.priority)
- Returns:
- A dspy.Prediction object containing the final readme_content and
- intermediate artifacts for inspection.
- """
- context_json = to_json_string(project_context)
-
- # Stage 1: Generate the README outline
- outline_prediction = self.outline_generator(project_context=context_json)
- readme_outline = [
- ReadmeSection(title=s["title"], description=s["description"])
- for s in outline_prediction.outline
- ]
-
- # Stage 2: Generate content for each section in the outline
+ # Generate content for each section
generated_sections = []
- for section in readme_outline:
- section_content_prediction = self.section_generator(
- project_context=context_json,
- section_title=section.title,
- section_description=section.description,
- )
+ for section in sections:
+ section_result = self.section_generator(context=context, section=section)
generated_sections.append(
- GeneratedSection(
- title=section.title,
- content=section_content_prediction.section_content,
- )
+ GeneratedSection(title=section.title, content=section_result.content)
)
- # Stage 3: Assemble the final README
- final_prediction = self.assembler(
- project_name=project_context.project_name,
- project_description=project_context.project_description,
- generated_sections=to_json_string(generated_sections),
+ # Assemble final README
+ final_result = self.assembler(
+ project_name=context.metadata.name,
+ project_description=context.metadata.description,
+ sections=generated_sections,
)
return dspy.Prediction(
- outline=readme_outline,
+ outline=sections,
generated_sections=generated_sections,
- readme_content=final_prediction.readme_content,
+ readme_content=final_result.readme_content,
)
-# --- CLI Application ---
+# --- Main Application ---
+
app = typer.Typer(
- help="An intelligent, context-aware README generator for the robofactor project.",
+ help="Intelligent README generator for the robofactor project",
add_completion=False,
no_args_is_help=True,
pretty_exceptions_show_locals=False,
)
-def configure_dspy(model_name: str, console: Console) -> None:
- """Configures the DSPy framework with the specified language model."""
- console.print(f"[dim]Configuring LLM: [bold]{model_name}[/bold]...[/dim]")
+def configure_dspy(model_name: str) -> None:
+ """Configure DSPy with the specified model."""
+ logger.info(f"Configuring DSPy with model: {model_name}")
try:
llm = dspy.LM(model_name, max_tokens=64000)
dspy.configure(lm=llm)
except Exception as e:
- console.print(f"[bold red]Error: Failed to configure DSPy with model '{model_name}': {e}[/]")
+ logger.error(f"Failed to configure DSPy: {e}")
raise typer.Exit(code=1)
@@ -348,7 +475,6 @@ def generate(
"--output",
"-o",
help="Path to write the generated README.md file.",
- show_default=True,
writable=True,
),
model: str = typer.Option(
@@ -356,39 +482,99 @@ def generate(
"--model",
"-m",
help="Language model to use for generation.",
- show_default=True,
+ ),
+ verbose: bool = typer.Option(
+ False,
+ "--verbose",
+ "-v",
+ help="Enable verbose logging.",
),
) -> None:
- """
- Analyzes the project and generates a comprehensive README.md.
- """
+ """Analyze the project and generate a comprehensive README."""
suppress_pydantic_warnings()
+
+ if verbose:
+ logging.getLogger().setLevel(logging.DEBUG)
+
console = Console()
console.print("\n[bold cyan]═══ Robofactor README Generator ═══[/bold cyan]\n")
try:
- configure_dspy(model, console)
+ # Configure DSPy
+ configure_dspy(model)
+
+ # Initialize services
+ file_reader = FileReader()
+ cli_runner = CLIRunner()
+
+ # Analyze project
+ console.print("[dim]Analyzing project structure...[/dim]")
+ analyzer = ProjectAnalyzer(project_root, file_reader, cli_runner, console)
+
+ # Extract metadata
+ metadata = analyzer.extract_project_metadata()
+ logger.info(f"Extracted metadata for project: {metadata.name}")
+
+ # Analyze source files
+ source_analyses = analyzer.analyze_all_source_files()
+ logger.info(f"Analyzed {len(source_analyses)} source files")
+
+ # Get CLI help
+ cli_help_text = analyzer.get_cli_help()
+
+ # Read additional files
+ makefile_content = file_reader.read_file(project_root / "Makefile")
+ pyproject_content = file_reader.read_file(project_root / "pyproject.toml")
+
+ # Read Python version if available
+ python_version = None
+ python_version_file = project_root / ".python-version"
+ if file_reader.file_exists(python_version_file):
+ python_version = file_reader.read_file(python_version_file).strip()
+
+ # Extract context
+ console.print("[bold blue]Extracting project context...[/bold blue]")
+ context_extractor = ContextExtractor()
+ context = context_extractor(
+ metadata=metadata,
+ source_analyses=source_analyses,
+ makefile_content=makefile_content,
+ pyproject_content=pyproject_content,
+ cli_help_text=cli_help_text,
+ python_version=python_version,
+ )
- analyzer = ProjectAnalyzer(project_root, console)
- project_context = analyzer.analyze()
+ logger.info(
+ f"Extracted context - Package manager: {context.environment.package_manager}"
+ )
+ logger.info(
+ f"Available commands: {list(context.environment.available_commands.keys())}"
+ )
- console.print("[bold blue]Starting README generation pipeline...[/bold blue]")
+ # Generate README
+ console.print("[bold green]Generating README content...[/bold green]")
readme_generator = ReadmeGenerator()
- with console.status("[bold green]Synthesizing README with DSPy...[/]", spinner="dots"):
- prediction = readme_generator(project_context=project_context)
- console.print("[green]✓ Generation pipeline complete.[/green]")
+ with console.status(
+ "[bold green]Synthesizing README with DSPy...[/]", spinner="dots"
+ ):
+ result = readme_generator(context=context)
+
+ console.print("[green]✓ Generation complete.[/green]")
+
+ # Write output
console.print(f"[dim]Writing output to [bold]{output}[/bold]...[/dim]")
- output.write_text(prediction.readme_content, encoding="utf-8")
+ output.write_text(result.readme_content, encoding="utf-8")
+
+ console.print(
+ f"\n[bold green]✅ README successfully generated at: {output}[/bold green]"
+ )
except Exception as e:
- console.print(f"\n[bold red]❌ An unexpected error occurred:[/bold red]\n{e}")
+ logger.error(f"Generation failed: {e}", exc_info=True)
+ console.print(f"\n[bold red]❌ An error occurred:[/bold red]\n{e}")
raise typer.Exit(code=1)
- console.print(
- f"\n[bold green]✅ README successfully generated at: {output}[/bold green]"
- )
-
if __name__ == "__main__":
app()
diff --git a/src/robofactor/app/__init__.py b/src/robofactor/app/__init__.py
new file mode 100644
index 0000000..bf79120
--- /dev/null
+++ b/src/robofactor/app/__init__.py
@@ -0,0 +1,3 @@
+"""
+The app package contains the main entry point and user interface components.
+"""
diff --git a/src/robofactor/app/config.py b/src/robofactor/app/config.py
new file mode 100644
index 0000000..758533d
--- /dev/null
+++ b/src/robofactor/app/config.py
@@ -0,0 +1,54 @@
+"""
+Configuration for the Robofactor tool.
+"""
+
+from pathlib import Path
+from typing import Final
+
+# --- File Paths ---
+ROOT_DIR: Final[Path] = Path(__file__).parent.parent
+OPTIMIZER_FILENAME: Final[Path] = ROOT_DIR / "optimized" / "program.pkl"
+OPTIMIZER_METADATA: Final[Path] = ROOT_DIR / "optimized" / "metadata.json"
+TRAINING_DATA_FILE: Final[Path] = ROOT_DIR / "training" / "training_data.json"
+
+# --- DSPy Model Configuration ---
+DEFAULT_TASK_LLM: Final[str] = "gemini/gemini-2.5-flash-lite-preview-06-17"
+DEFAULT_PROMPT_LLM: Final[str] = "gemini/gemini-2.5-pro"
+TASK_LLM_MAX_TOKENS: Final[int] = 64000
+PROMPT_LLM_MAX_TOKENS: Final[int] = 64000
+
+# --- Refinement Configuration ---
+REFINEMENT_THRESHOLD: Final[float] = 0.9
+REFINEMENT_COUNT: Final[int] = 3
+
+# --- Analysis & Linting Configuration ---
+FLAKE8_COMPLEXITY_CODE: Final[str] = "C901"
+FLAKE8_MAX_COMPLEXITY: Final[int] = 10
+LINTING_PENALTY_PER_ISSUE: Final[float] = 0.1
+
+# --- UI Configuration ---
+RICH_SYNTAX_THEME: Final[str] = "monokai"
+
+# --- MLflow Configuration ---
+DEFAULT_MLFLOW_TRACKING_URI: Final[str] = "http://127.0.0.1:5000"
+DEFAULT_MLFLOW_EXPERIMENT_NAME: Final[str] = "robofactor"
+
+# --- SSoT Enforcement ---
+__all__ = [
+ "DEFAULT_MLFLOW_EXPERIMENT_NAME",
+ "DEFAULT_MLFLOW_TRACKING_URI",
+ "DEFAULT_PROMPT_LLM",
+ "DEFAULT_TASK_LLM",
+ "FLAKE8_COMPLEXITY_CODE",
+ "FLAKE8_MAX_COMPLEXITY",
+ "LINTING_PENALTY_PER_ISSUE",
+ "OPTIMIZER_FILENAME",
+ "OPTIMIZER_METADATA",
+ "PROMPT_LLM_MAX_TOKENS",
+ "REFINEMENT_COUNT",
+ "REFINEMENT_THRESHOLD",
+ "RICH_SYNTAX_THEME",
+ "ROOT_DIR",
+ "TASK_LLM_MAX_TOKENS",
+ "TRAINING_DATA_FILE",
+]
diff --git a/src/robofactor/main.py b/src/robofactor/app/main.py
similarity index 77%
rename from src/robofactor/main.py
rename to src/robofactor/app/main.py
index fd98233..89f8a6c 100644
--- a/src/robofactor/main.py
+++ b/src/robofactor/app/main.py
@@ -1,8 +1,10 @@
"""
Main entry point for the command-line interface (CLI) of the refactoring tool.
"""
+
+from collections.abc import Callable
from pathlib import Path
-from typing import Annotated
+from typing import Annotated, cast
import dspy
import mlflow
@@ -13,10 +15,15 @@
from rich.rule import Rule
from rich.syntax import Syntax
-from . import config, ui, utils
-from .analysis import extract_python_code
-from .dspy_modules import CodeRefactor, RefactoringEvaluator, load_training_data
-from .evaluation import TestCase, evaluate_refactored_code
+from robofactor import utils
+from robofactor.app import config, ui
+from robofactor.evaluation import evaluate_refactored_code
+from robofactor.json.types import JSONObject
+from robofactor.parsing.analysis import extract_python_code
+from robofactor.parsing.models import TestCase
+from robofactor.refactoring.evaluator import RefactoringEvaluator
+from robofactor.refactoring.module import CodeRefactor
+from robofactor.training.training_loader import load_training_data
app = typer.Typer()
@@ -28,19 +35,25 @@ def _setup_environment(tracing: bool, mlflow_uri: str, mlflow_experiment: str) -
if tracing:
console.print(f"[bold yellow]MLflow tracing enabled. URI: {mlflow_uri}[/bold yellow]")
mlflow.set_tracking_uri(mlflow_uri)
- mlflow.set_experiment(mlflow_experiment)
- mlflow.dspy.autolog(log_compiles=True, log_traces=True)
+ _ = mlflow.set_experiment(mlflow_experiment)
+ _ = mlflow.autolog()
return console
def _load_or_compile_model(
- optimizer_path: Path, optimize: bool, console: Console, prompt_llm: dspy.LM, task_llm: dspy.LM
+ optimizer_path: Path,
+ optimize: bool,
+ console: Console,
+ prompt_llm: dspy.LM,
+ task_llm: dspy.LM,
) -> dspy.Module:
"""Loads an optimized DSPy model or compiles a new one."""
refactorer = CodeRefactor()
self_correcting_refactorer = dspy.Refine(
module=refactorer,
- reward_fn=RefactoringEvaluator(),
+ reward_fn=cast(
+ Callable[[JSONObject, dspy.Prediction], float], RefactoringEvaluator().forward
+ ),
threshold=config.REFINEMENT_THRESHOLD,
N=config.REFINEMENT_COUNT,
)
@@ -57,7 +70,9 @@ def _load_or_compile_model(
num_threads=8,
)
teleprompter.compile(
- refactorer, trainset=load_training_data(), requires_permission_to_run=False
+ refactorer,
+ trainset=load_training_data(),
+ requires_permission_to_run=False,
)
console.print(f"Optimization complete. Saving to {optimizer_path}...")
self_correcting_refactorer.save(str(optimizer_path), save_program=True)
@@ -87,7 +102,7 @@ def _run_refactoring_on_file(
refactor_example = dspy.Example(code_snippet=source_code, test_cases=[]).with_inputs(
"code_snippet"
)
- prediction = refactorer(**refactor_example.inputs())
+ prediction = cast(dspy.Prediction, refactorer(**refactor_example.inputs()))
ui.display_refactoring_process(console, prediction)
refactored_code = extract_python_code(prediction.refactored_code)
@@ -103,7 +118,7 @@ def _run_refactoring_on_file(
console.print(
f"[yellow]Writing refactored code back to {script_path.name}...[/yellow]"
)
- script_path.write_text(refactored_code, encoding="utf-8")
+ _ = script_path.write_text(refactored_code, encoding="utf-8")
console.print(f"[green]Refactoring of {script_path.name} complete.[/green]")
case Failure(error_message):
console.print(
@@ -116,27 +131,20 @@ def _run_refactoring_on_file(
console.print(
"[bold yellow]Skipping write-back due to evaluation failure.[/bold yellow]"
)
+ case _:
+ # Fallback case for static type checkers.
+ pass
@app.command()
def main(
path: Annotated[
- Path | None,
- typer.Argument(
- help="Path to the Python file to refactor.",
- exists=True,
- file_okay=True,
- dir_okay=False,
- readable=True,
- resolve_path=True,
- ),
+ Path | None, typer.Argument(help="Path to Python file to refactor.", exists=True)
] = None,
self_refactor: bool = typer.Option(
False, "--dog-food", help="Self-refactor the script you are running."
),
- write: bool = typer.Option(
- False, "--write", help="Write the refactored code back to the file."
- ),
+ write: bool = typer.Option(False, "--write", help="Write refactored code back to the file."),
optimize: bool = typer.Option(
False, "--optimize", help="Force re-optimization of the DSPy model."
),
@@ -167,15 +175,15 @@ def main(
config.OPTIMIZER_FILENAME, optimize, console, prompt_llm, task_llm
)
- target_path: Path | None = None
+ file_path: Path | None = None
if self_refactor:
- target_path = Path(__file__)
+ file_path = Path(__file__)
console.print(Rule("[bold magenta]Self-Refactoring Mode[/bold magenta]"))
elif path:
- target_path = path
+ file_path = path
- if target_path:
- _run_refactoring_on_file(console, refactorer, target_path, write)
+ if file_path:
+ _run_refactoring_on_file(console, refactorer, file_path, write)
else:
console.print(
"[bold red]Error:[/bold red] Please provide a path to a file or use --dog-food."
diff --git a/src/robofactor/ui.py b/src/robofactor/app/ui.py
similarity index 89%
rename from src/robofactor/ui.py
rename to src/robofactor/app/ui.py
index a28986b..824f95e 100644
--- a/src/robofactor/ui.py
+++ b/src/robofactor/app/ui.py
@@ -13,8 +13,9 @@
from rich.table import Table
from rich.text import Text
-from . import analysis, config
-from .evaluation import EvaluationResult
+from ..evaluation import EvaluationResult
+from ..parsing import analysis
+from . import config
def display_refactoring_process(console: Console, prediction: dspy.Prediction) -> None:
@@ -22,11 +23,11 @@ def display_refactoring_process(console: Console, prediction: dspy.Prediction) -
console.print(Panel(prediction.analysis, title="[bold cyan]Analysis[/bold cyan]", expand=False))
plan_text = Text()
- plan_text.append("Summary: ", style="bold")
- plan_text.append(prediction.refactoring_summary)
- plan_text.append("\n\n")
+ _ = plan_text.append("Summary: ", style="bold")
+ _ = plan_text.append(prediction.refactoring_summary)
+ _ = plan_text.append("\n\n")
for i, step in enumerate(prediction.plan_steps, 1):
- plan_text.append(f"{i}. {step}\n")
+ _ = plan_text.append(f"{i}. {step}\n")
console.print(Panel(plan_text, title="[bold cyan]Refactoring Plan[/bold cyan]"))
console.print(
diff --git a/src/robofactor/common/__init__.py b/src/robofactor/common/__init__.py
new file mode 100644
index 0000000..7422c1d
--- /dev/null
+++ b/src/robofactor/common/__init__.py
@@ -0,0 +1,16 @@
+"""
+Common utilities and shared functionality for Robofactor.
+This package provides single sources of truth for cross-cutting concerns.
+"""
+
+from .ast_utils import ast_node_to_source
+from .code_extraction import extract_python_code
+from .json_validation import is_json_list, is_json_object, is_training_item
+
+__all__ = [
+ "ast_node_to_source",
+ "extract_python_code",
+ "is_json_list",
+ "is_json_object",
+ "is_training_item",
+]
diff --git a/src/robofactor/common/ast_utils.py b/src/robofactor/common/ast_utils.py
new file mode 100644
index 0000000..e49396a
--- /dev/null
+++ b/src/robofactor/common/ast_utils.py
@@ -0,0 +1,23 @@
+"""
+Single source of truth for AST-related utilities.
+"""
+
+import ast
+
+
+def ast_node_to_source(node: ast.AST) -> str:
+ """
+ Convert an AST node back to its source code representation.
+
+ Args:
+ node: The AST node to convert.
+
+ Returns:
+ The source code string for the node, or a repr for fallback.
+ """
+ try:
+ return ast.unparse(node)
+ except Exception:
+ # Fallback for nodes that ast.unparse might not handle gracefully.
+ # This ensures that even complex or unusual AST structures can be represented.
+ return repr(node)
diff --git a/src/robofactor/common/code_extraction.py b/src/robofactor/common/code_extraction.py
new file mode 100644
index 0000000..5ed5db0
--- /dev/null
+++ b/src/robofactor/common/code_extraction.py
@@ -0,0 +1,25 @@
+"""
+Single source of truth for extracting Python code from text/markdown.
+"""
+
+import re
+from typing import Final
+
+_CODE_BLOCK_PATTERN: Final[re.Pattern[str]] = re.compile(r"```python\n(.*?)\n```", re.DOTALL)
+
+
+def extract_python_code(text: str) -> str:
+ """
+ Extracts Python code from a markdown block.
+
+ If a python markdown block (```python...```) is found, its content is
+ returned. Otherwise, the original text is returned.
+
+ Args:
+ text: The string to search for a Python code block.
+
+ Returns:
+ The extracted Python code, or the original text if no block is found.
+ """
+ match = _CODE_BLOCK_PATTERN.search(text)
+ return match.group(1).strip() if match else text
diff --git a/src/robofactor/common/json_validation.py b/src/robofactor/common/json_validation.py
new file mode 100644
index 0000000..a1e4ead
--- /dev/null
+++ b/src/robofactor/common/json_validation.py
@@ -0,0 +1,38 @@
+"""
+Single source of truth for JSON validation utilities.
+"""
+
+from collections.abc import Sequence
+from typing import TypeGuard
+
+from ..json.types import JSON, JSONObject
+
+
+def is_json_object(x: JSON) -> TypeGuard[JSONObject]:
+ """Type guard for JSON object validation."""
+ return isinstance(x, dict)
+
+
+def is_json_list(x: JSON) -> TypeGuard[Sequence[JSON]]:
+ """Type guard for JSON list validation."""
+ return isinstance(x, Sequence) and not isinstance(x, str)
+
+
+def is_training_item(x: JSON) -> TypeGuard[JSONObject]:
+ """
+ Type guard for training data validation.
+
+ Validates that the input is a JSON object with required training data structure.
+ """
+ return (
+ is_json_object(x)
+ and "code_snippet" in x
+ and isinstance(x["code_snippet"], str)
+ and (
+ "test_cases" not in x
+ or (
+ isinstance(x["test_cases"], Sequence)
+ and all(is_json_object(tc) for tc in x["test_cases"])
+ )
+ )
+ )
diff --git a/src/robofactor/config.py b/src/robofactor/config.py
deleted file mode 100644
index 962c5f8..0000000
--- a/src/robofactor/config.py
+++ /dev/null
@@ -1,33 +0,0 @@
-"""
-Centralized configuration for the refactoring tool.
-
-This module consolidates all constants, magic numbers, and default settings
-to simplify management and modification.
-"""
-
-from pathlib import Path
-
-# --- File Paths ---
-OPTIMIZER_FILENAME: Path = Path("optimized/")
-
-# --- DSPy Model Configuration ---
-DEFAULT_TASK_LLM: str = "gemini/gemini-2.5-flash-lite-preview-06-17"
-DEFAULT_PROMPT_LLM: str = "gemini/gemini-2.5-pro"
-TASK_LLM_MAX_TOKENS: int = 64000
-PROMPT_LLM_MAX_TOKENS: int = 64000
-
-# --- Refinement Configuration ---
-REFINEMENT_THRESHOLD: float = 0.9
-REFINEMENT_COUNT: int = 3
-
-# --- Analysis Configuration ---
-FLAKE8_COMPLEXITY_CODE: str = "C901"
-FLAKE8_MAX_COMPLEXITY: int = 10
-LINTING_PENALTY_PER_ISSUE: float = 0.1
-
-# --- UI Configuration ---
-RICH_SYNTAX_THEME: str = "monokai"
-
-# --- MLflow Configuration ---
-DEFAULT_MLFLOW_TRACKING_URI: str = "http://127.0.0.1:5000"
-DEFAULT_MLFLOW_EXPERIMENT_NAME: str = "robofactor"
diff --git a/src/robofactor/dspy_modules.py b/src/robofactor/dspy_modules.py
deleted file mode 100644
index 7f8b4b8..0000000
--- a/src/robofactor/dspy_modules.py
+++ /dev/null
@@ -1,197 +0,0 @@
-"""
-Refactored DSPy modules and Pydantic models for Python code refactoring agent.
-Improved with type safety, error handling, and separation of concerns.
-"""
-
-import json
-import logging
-from pathlib import Path
-
-import dspy
-from pydantic import BaseModel, Field, field_validator, model_validator
-from returns.result import Result, Success
-
-from . import analysis, evaluation
-from .evaluation import EvaluationResult, TestCase
-
-# --- Constants ---
-FAILURE_SCORE = 0.0
-TRAINING_DATA_FILE = "training_data.json"
-logger = logging.getLogger(__name__)
-
-# --- Pydantic Models ---
-class AnalysisOutput(BaseModel):
- """Structured analysis of Python code functionality and improvement opportunities."""
- analysis: str = Field(description="Concise summary of functionality, complexity, and dependencies")
- refactoring_opportunities: list[str] = Field(
- description="Actionable bullet points for refactoring"
- )
-
-class PlanOutput(BaseModel):
- """Step-by-step refactoring execution plan."""
- refactoring_summary: str = Field(description="High-level refactoring objective")
- plan_steps: list[str] = Field(description="Sequential actions to achieve refactoring")
-
-class ImplementationOutput(BaseModel):
- """Final refactored code with change explanations."""
- refactored_code: str = Field(
- description="PEP8-compliant Python code with type hints and docstrings"
- )
- implementation_explanation: str = Field(
- description="Rationale for implemented changes"
- )
-
- @field_validator("refactored_code")
- @classmethod
- def extract_from_markdown(cls, v: str) -> str:
- return analysis.extract_python_code(v)
-
-class EvaluationOutput(BaseModel):
- """Holistic assessment of refactoring quality."""
- final_score: float = Field(
- description="Weighted quality score (0.0-1.0)",
- ge=0.0,
- le=1.0
- )
- final_suggestion: str = Field(
- description="Improvement recommendations or approval"
- )
-
- @model_validator(mode="after")
- def validate_score_precision(self) -> "EvaluationOutput":
- if isinstance(self.final_score, float):
- self.final_score = round(self.final_score, 2)
- return self
-
-# --- DSPy Signatures ---
-class CodeAnalysis(dspy.Signature):
- """Analyze Python code for functionality and improvement areas."""
- code_snippet: str = dspy.InputField(desc="Python code to analyze")
- analysis: AnalysisOutput = dspy.OutputField()
-
-class RefactoringPlan(dspy.Signature):
- """Create refactoring plan based on code analysis."""
- code_snippet: str = dspy.InputField(desc="Original Python code")
- analysis: str = dspy.InputField(desc="Code analysis summary")
- plan: PlanOutput = dspy.OutputField()
-
-class RefactoredCode(dspy.Signature):
- """Generate refactored code from execution plan."""
- original_code: str = dspy.InputField(desc="Unmodified source code")
- refactoring_summary: str = dspy.InputField(desc="Refactoring objective")
- plan_steps: list[str] = dspy.InputField(desc="Step-by-step refactoring actions")
- implementation: ImplementationOutput = dspy.OutputField()
-
-class FinalEvaluation(dspy.Signature):
- """Assess refactored code quality with quantitative metrics."""
- code_snippet: str = dspy.InputField(desc="Refactored Python code")
- quality_scores: str = dspy.InputField(desc="JSON quality metrics")
- functional_score: float = dspy.InputField(desc="Test pass rate (0.0-1.0)")
- evaluation: EvaluationOutput = dspy.OutputField()
-
-# --- DSPy Modules ---
-class CodeRefactor(dspy.Module):
- """Orchestrates code analysis, planning, and refactoring."""
- def __init__(self):
- super().__init__()
- self.analyzer = dspy.Predict(CodeAnalysis)
- self.planner = dspy.Predict(RefactoringPlan)
- self.implementer = dspy.Predict(RefactoredCode)
-
- def forward(self, code_snippet: str) -> dspy.Prediction:
- analysis_result = self.analyzer(code_snippet=code_snippet)
- plan_result = self.planner(
- code_snippet=code_snippet,
- analysis=analysis_result.analysis.analysis
- )
- impl_result = self.implementer(
- original_code=code_snippet,
- refactoring_summary=plan_result.plan.refactoring_summary,
- plan_steps=plan_result.plan.plan_steps,
- )
-
- return dspy.Prediction(
- analysis=analysis_result.analysis.analysis,
- refactoring_opportunities=analysis_result.analysis.refactoring_opportunities,
- refactoring_summary=plan_result.plan.refactoring_summary,
- plan_steps=plan_result.plan.plan_steps,
- refactored_code=impl_result.implementation.refactored_code,
- implementation_explanation=impl_result.implementation.implementation_explanation,
- )
-
-class RefactoringEvaluator(dspy.Module):
- """Evaluates refactored code through automated checks and LLM assessment."""
- def __init__(self):
- super().__init__()
- self.evaluator = dspy.Predict(FinalEvaluation)
-
- def _handle_evaluation_success(
- self,
- eval_data: EvaluationResult,
- refactored_code: str
- ) -> float:
- """Process successful programmatic evaluation."""
- functional_score = (
- eval_data.functional_check.passed_tests / eval_data.functional_check.total_tests
- if eval_data.functional_check.total_tests > 0
- else 1.0
- )
-
- try:
- llm_evaluation = self.evaluator(
- code_snippet=refactored_code,
- quality_scores=eval_data.quality_scores.model_dump_json(),
- functional_score=functional_score,
- )
- return llm_evaluation.evaluation.final_score
- except Exception as e:
- logger.error(f"LLM evaluation failed: {e}", exc_info=True)
- return FAILURE_SCORE
-
- def forward(
- self,
- original_example: dspy.Example,
- prediction: dspy.Prediction
- ) -> float:
- refactored_code = getattr(prediction, "refactored_code", "")
- if not refactored_code:
- logger.warning("Evaluation aborted: Missing refactored code")
- return FAILURE_SCORE
-
- code_to_evaluate = analysis.extract_python_code(refactored_code)
- if not code_to_evaluate:
- logger.warning("Evaluation aborted: Empty code extraction")
- return FAILURE_SCORE
-
- test_cases = getattr(original_example, "test_cases", [])
- eval_result: Result[EvaluationResult, str] = (
- evaluation.evaluate_refactored_code(code_to_evaluate, test_cases)
- )
-
- if isinstance(eval_result, Success):
- return self._handle_evaluation_success(eval_result.unwrap(), code_to_evaluate)
- else:
- logger.warning(f"Programmatic evaluation failed: {eval_result.failure()}")
- return FAILURE_SCORE
-
-# --- Data Loading ---
-def load_training_data() -> list[dspy.Example]:
- """Load training examples from external JSON file."""
- data_path = Path(__file__).parent / TRAINING_DATA_FILE
- try:
- with data_path.open("r", encoding="utf-8") as f:
- return [
- dspy.Example(
- code_snippet=item["code_snippet"],
- test_cases=[TestCase(**tc) for tc in item.get("test_cases", [])]
- ).with_inputs("code_snippet")
- for item in json.load(f)
- ]
- except FileNotFoundError:
- logger.error(f"Training data file not found: {data_path}")
- except json.JSONDecodeError as e:
- logger.error(f"Invalid JSON in training data: {e}")
- except KeyError as e:
- logger.error(f"Missing required key in training data: {e}")
-
- return []
diff --git a/src/robofactor/evaluation/__init__.py b/src/robofactor/evaluation/__init__.py
new file mode 100644
index 0000000..a3f7793
--- /dev/null
+++ b/src/robofactor/evaluation/__init__.py
@@ -0,0 +1,11 @@
+"""
+The evaluation package is responsible for assessing the quality and correctness
+of refactored code. It includes a multi-stage pipeline that performs syntax,
+quality, and functional checks to ensure that AI-generated code is safe,
+reliable, and adheres to best practices.
+"""
+
+from . import checkers, pipeline
+from .pipeline import EvaluationResult, evaluate_refactored_code
+
+__all__ = ["EvaluationResult", "checkers", "evaluate_refactored_code", "pipeline"]
diff --git a/src/robofactor/analysis.py b/src/robofactor/evaluation/checkers.py
similarity index 69%
rename from src/robofactor/analysis.py
rename to src/robofactor/evaluation/checkers.py
index 0414d64..ecc098f 100644
--- a/src/robofactor/analysis.py
+++ b/src/robofactor/evaluation/checkers.py
@@ -1,39 +1,38 @@
"""
-Utility functions for static and dynamic code analysis.
+Provides functions for checking the quality and correctness of Python code.
-This module includes functions for syntax validation, quality scoring (linting,
-complexity, typing, docstrings), and functional correctness checking. These
-functions are designed to be pure or to have their side effects managed by
-callers, often using decorators like `@safe` from the `returns` library.
+This module contains checkers for syntax validity, code quality (linting,
+complexity, docstrings, typing), and functional correctness against test cases.
+These functions are designed to be composed into an evaluation pipeline.
"""
import ast
import json
import os
-import re
import subprocess
import tempfile
import textwrap
+from collections.abc import Sequence
from pathlib import Path
import dspy
-from . import config
-from .evaluation import CodeQualityScores, TestCase
-
-
-def extract_python_code(text: str) -> str:
- """Extracts Python code from a markdown block, returns original text if no block is found."""
- match = re.search(r"```python\n(.*?)\n```", text, re.DOTALL)
- return match.group(1).strip() if match else text
+from robofactor.app import config
+from robofactor.parsing.models import CodeQualityScores, TestCase
def check_syntax(code: str) -> tuple[bool, str | None, str | None]:
"""
Checks for valid Python syntax and a top-level function definition.
- Returns a tuple indicating validity, the function name, and an error message.
- This format is consumed by a wrapper that converts it into a `Result` monad.
+ Args:
+ code: The Python source code to check.
+
+ Returns:
+ A tuple containing:
+ - A boolean indicating if the syntax is valid.
+ - The name of the top-level function if found, otherwise None.
+ - An error message if the syntax is invalid, otherwise None.
"""
try:
tree = ast.parse(code)
@@ -74,14 +73,24 @@ def check_code_quality(code: str, func_name: str | None = None) -> CodeQualitySc
This function performs I/O by creating a temporary file and running a
subprocess. It is designed to be wrapped by a decorator like `@safe`
- or `@impure_safe` to handle potential exceptions.
+ to handle potential exceptions.
+
+ Args:
+ code: The Python code to analyze.
+ func_name: The specific function name to target for AST-based checks.
+
+ Returns:
+ A CodeQualityScores object with the analysis results.
+
+ Raises:
+ subprocess.CalledProcessError: If the flake8 command fails.
+ SyntaxError: If the code cannot be parsed into an AST.
"""
with tempfile.NamedTemporaryFile("w", suffix=".py", delete=False, encoding="utf-8") as tmp:
- tmp.write(code)
+ _ = tmp.write(code)
tmp_path = Path(tmp.name)
try:
- # Exceptions from subprocess.run will be caught by the @safe wrapper in the caller.
result = subprocess.run(
[
"flake8",
@@ -90,7 +99,7 @@ def check_code_quality(code: str, func_name: str | None = None) -> CodeQualitySc
],
capture_output=True,
text=True,
- check=False, # We manually check output, not exit code.
+ check=False,
)
all_issues = result.stdout.strip().splitlines() if result.stdout else []
@@ -104,7 +113,6 @@ def check_code_quality(code: str, func_name: str | None = None) -> CodeQualitySc
complexity_score = 1.0 if not complexity_warnings else 0.0
linting_score = max(0.0, 1.0 - (config.LINTING_PENALTY_PER_ISSUE * len(linting_issues)))
- # A SyntaxError here will be caught by the @safe wrapper in the caller.
tree = ast.parse(code)
docstring_score, typing_score = _get_ast_based_scores(tree, func_name)
@@ -116,7 +124,6 @@ def check_code_quality(code: str, func_name: str | None = None) -> CodeQualitySc
linting_issues=linting_issues,
)
finally:
- # Ensure the temporary file is always cleaned up.
if tmp_path.exists():
os.unlink(tmp_path)
@@ -131,9 +138,6 @@ def _build_execution_script(func_name: str, test_case: TestCase) -> str:
import json
import sys
- # This script assumes the function '{func_name}' has been defined in the
- # execution context by the dspy.PythonInterpreter.
-
args = json.loads('''{args_json}''')
kwargs = json.loads('''{kwargs_json}''')
@@ -143,34 +147,35 @@ def _build_execution_script(func_name: str, test_case: TestCase) -> str:
)
-def check_functional_correctness(code: str, func_name: str, test_cases: list[TestCase]) -> int:
+def check_functional_correctness(code: str, func_name: str, test_cases: Sequence[TestCase]) -> int:
"""
Executes test cases against code in a sandboxed Python interpreter.
- This function can raise exceptions if the provided code is invalid or if
- the test execution fails unexpectedly. It is designed to be wrapped by a
+ Args:
+ code: The Python source code containing the function.
+ func_name: The name of the function to test.
+ test_cases: A sequence of TestCase objects to run.
- decorator like `@safe` to handle these failures gracefully.
+ Returns:
+ The number of test cases that passed.
+
+ Raises:
+ Exception: If the PythonInterpreter fails during setup or execution.
"""
if not test_cases:
return 0
passed_count = 0
- # A failure in the interpreter setup will be caught by the @safe wrapper.
with dspy.PythonInterpreter() as interp:
- interp.execute(code) # Define the function in the interpreter's scope.
+ interp.execute(code)
for test in test_cases:
- # Handle failures for individual test cases gracefully to allow others to run.
try:
exec_script = _build_execution_script(func_name, test)
actual_output_json = interp.execute(exec_script)
actual_output = json.loads(actual_output_json)
-
- # Normalize expected output to ensure consistent comparison.
normalized_expected_output = json.loads(json.dumps(test.expected_output))
if actual_output == normalized_expected_output:
passed_count += 1
except Exception:
- # If a single test case fails to execute or assert, continue to the next.
continue
return passed_count
diff --git a/src/robofactor/evaluation.py b/src/robofactor/evaluation/pipeline.py
similarity index 68%
rename from src/robofactor/evaluation.py
rename to src/robofactor/evaluation/pipeline.py
index eb109c6..69132fd 100644
--- a/src/robofactor/evaluation.py
+++ b/src/robofactor/evaluation/pipeline.py
@@ -1,38 +1,12 @@
-"""
-Data models and core logic for evaluating refactored code.
-
-This module defines the structures for test cases, quality scores, and
-evaluation results, and contains the pure function for performing the evaluation.
-It leverages the 'returns' library for robust, type-safe error handling using
-a railway-oriented programming approach.
-"""
-
from __future__ import annotations
-from typing import Any, NamedTuple
+from collections.abc import Sequence
+from typing import NamedTuple
-from pydantic import BaseModel, Field
from returns.result import Failure, Result, Success, safe
-from . import analysis
-
-
-class TestCase(BaseModel):
- """A single, executable test case for a function."""
-
- args: list[Any] = Field(default_factory=list)
- kwargs: dict[str, Any] = Field(default_factory=dict)
- expected_output: Any
-
-
-class CodeQualityScores(BaseModel):
- """Holds various code quality metrics."""
-
- linting_score: float
- complexity_score: float
- typing_score: float
- docstring_score: float
- linting_issues: list[str] = Field(default_factory=list)
+from ..parsing.models import CodeQualityScores, TestCase
+from . import checkers
class FunctionalCheckResult(NamedTuple):
@@ -59,7 +33,7 @@ def _check_syntax(code: str) -> Result[str, str]:
output into a `Result` monad, which is more suitable for functional
pipelines.
"""
- is_valid, func_name, err = analysis.check_syntax(code)
+ is_valid, func_name, err = checkers.check_syntax(code)
if not is_valid or not func_name:
return Failure(f"Syntax Check Failed: {err or 'No function found.'}")
return Success(func_name)
@@ -73,12 +47,12 @@ def _check_quality(code: str, func_name: str) -> CodeQualityScores:
The `@safe` decorator automatically wraps this function's execution in a
`Result` container, capturing any exceptions as a `Failure`.
"""
- return analysis.check_code_quality(code, func_name)
+ return checkers.check_code_quality(code, func_name)
@safe
def _check_functional_correctness(
- code: str, func_name: str, tests: list[TestCase]
+ code: str, func_name: str, tests: Sequence[TestCase]
) -> FunctionalCheckResult:
"""
Runs functional tests and returns the pass rate.
@@ -88,13 +62,11 @@ def _check_functional_correctness(
if not tests:
return FunctionalCheckResult(passed_tests=0, total_tests=0)
- passed_tests = analysis.check_functional_correctness(code, func_name, tests)
+ passed_tests = checkers.check_functional_correctness(code, func_name, tests)
return FunctionalCheckResult(passed_tests=passed_tests, total_tests=len(tests))
-def evaluate_refactored_code(
- code: str, tests: list[TestCase]
-) -> Result[EvaluationResult, str]:
+def evaluate_refactored_code(code: str, tests: Sequence[TestCase]) -> Result[EvaluationResult, str]:
"""
Performs a full evaluation of the refactored code.
@@ -105,7 +77,7 @@ def evaluate_refactored_code(
Args:
code: The refactored Python code to evaluate.
- tests: A list of test cases to verify functional correctness.
+ tests: A Sequence of test cases to verify functional correctness.
Returns:
A `Result` container:
diff --git a/src/robofactor/json/__init__.py b/src/robofactor/json/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/robofactor/json/types.py b/src/robofactor/json/types.py
new file mode 100644
index 0000000..2f21e4f
--- /dev/null
+++ b/src/robofactor/json/types.py
@@ -0,0 +1,6 @@
+from collections.abc import Mapping, Sequence
+
+type JSONPrimitive = None | bool | int | float | str
+type JSONSequence = Sequence["JSON"]
+type JSONObject = Mapping[str, "JSON"]
+type JSON = JSONPrimitive | JSONSequence | JSONObject
diff --git a/src/robofactor/parsing/__init__.py b/src/robofactor/parsing/__init__.py
new file mode 100644
index 0000000..5dfa8df
--- /dev/null
+++ b/src/robofactor/parsing/__init__.py
@@ -0,0 +1,10 @@
+"""
+The parsing package is responsible for analyzing and extracting information
+from Python source code. It uses Abstract Syntax Trees (AST) to deconstruct
+code into a structured format, making it easier for other parts of the
+application to understand and manipulate.
+"""
+
+from . import analysis, ast_parser, models
+
+__all__ = ["analysis", "ast_parser", "models"]
diff --git a/src/robofactor/parsing/analysis.py b/src/robofactor/parsing/analysis.py
new file mode 100644
index 0000000..bf39abf
--- /dev/null
+++ b/src/robofactor/parsing/analysis.py
@@ -0,0 +1,7 @@
+"""
+Provides utility functions for parsing and extracting code from text.
+"""
+
+from robofactor.common.code_extraction import extract_python_code
+
+__all__ = ["extract_python_code"]
diff --git a/src/robofactor/function_extraction.py b/src/robofactor/parsing/ast_parser.py
similarity index 84%
rename from src/robofactor/function_extraction.py
rename to src/robofactor/parsing/ast_parser.py
index 45aa9e2..8ac6e06 100644
--- a/src/robofactor/function_extraction.py
+++ b/src/robofactor/parsing/ast_parser.py
@@ -1,103 +1,25 @@
import ast
-import enum
-from collections.abc import Iterator
+from collections.abc import Iterator, Sequence
from dataclasses import dataclass
from pathlib import Path
from returns.io import impure_safe
from returns.result import safe
-# Type alias for function definition AST nodes to improve readability.
-FunctionDefNode = ast.FunctionDef | ast.AsyncFunctionDef
-
-
-class ParameterKind(enum.Enum):
- """Enumeration for the different kinds of function parameters."""
-
- POSITIONAL_ONLY = "positional_only"
- POSITIONAL_OR_KEYWORD = "positional_or_keyword"
- VAR_POSITIONAL = "var_positional"
- KEYWORD_ONLY = "keyword_only"
- VAR_KEYWORD = "var_keyword"
-
-
-@dataclass(frozen=True)
-class Parameter:
- """
- Represents a function parameter with its name, kind, and optional details.
+from robofactor.common.ast_utils import ast_node_to_source
- Attributes:
- name: The name of the parameter.
- kind: The kind of the parameter (e.g., positional-only).
- annotation: The type annotation as a string, if present.
- default: The default value as a string, if present.
- """
-
- name: str
- kind: ParameterKind
- annotation: str | None = None
- default: str | None = None
-
-
-@dataclass(frozen=True)
-class Decorator:
- """
- Represents a function decorator.
-
- Attributes:
- name: The name of the decorator.
- args: A tuple of arguments passed to the decorator, as strings.
- """
-
- name: str
- args: tuple[str, ...] = ()
-
-
-@dataclass(frozen=True)
-class FunctionContext:
- """Represents the context where a function is defined (base class)."""
-
- pass
-
-
-@dataclass(frozen=True)
-class ModuleContext(FunctionContext):
- """
- Represents a function defined at the module level.
+from .models import (
+ ClassContext,
+ Decorator,
+ FunctionContext,
+ ModuleContext,
+ NestedContext,
+ Parameter,
+ ParameterKind,
+)
- Attributes:
- module_name: The name of the module.
- """
-
- module_name: str
-
-
-@dataclass(frozen=True)
-class ClassContext(FunctionContext):
- """
- Represents a function defined within a class.
-
- Attributes:
- class_name: The name of the class.
- parent_context: The context in which the class is defined.
- """
-
- class_name: str
- parent_context: FunctionContext
-
-
-@dataclass(frozen=True)
-class NestedContext(FunctionContext):
- """
- Represents a function defined within another function.
-
- Attributes:
- parent_function: The name of the enclosing function.
- parent_context: The context of the enclosing function.
- """
-
- parent_function: str
- parent_context: FunctionContext
+# Type alias for function definition AST nodes to improve readability.
+type FunctionDefNode = ast.FunctionDef | ast.AsyncFunctionDef
@dataclass(frozen=True)
@@ -162,34 +84,16 @@ def from_ast_node(
is_async=isinstance(node, ast.AsyncFunctionDef),
context=context,
docstring=extract_docstring(node),
- return_annotation=ast_node_to_source(node.returns) if node.returns else None,
+ return_annotation=(ast_node_to_source(node.returns) if node.returns else None),
)
-def ast_node_to_source(node: ast.AST) -> str:
- """
- Convert an AST node back to its source code representation.
-
- Args:
- node: The AST node to convert.
-
- Returns:
- The source code string for the node, or a repr for fallback.
- """
- try:
- return ast.unparse(node)
- except Exception:
- # Fallback for nodes that ast.unparse might not handle gracefully.
- # This ensures that even complex or unusual AST structures can be represented.
- return repr(node)
-
-
-def extract_decorators(decorators: list[ast.expr]) -> tuple[Decorator, ...]:
+def extract_decorators(decorators: Sequence[ast.expr]) -> tuple[Decorator, ...]:
"""
- Extract decorator information from an AST decorator list.
+ Extract decorator information from an AST decorator Sequence.
Args:
- decorators: A list of decorator nodes from an AST function definition.
+ decorators: A Sequence of decorator nodes from an AST function definition.
Returns:
A tuple of Decorator objects.
@@ -527,7 +431,7 @@ def format_param(p: Parameter) -> str:
res += f" = {p.default}"
return res
- param_parts: list[str] = []
+ param_parts: Sequence[str] = []
pos_only_ended = False
var_pos_added = False
diff --git a/src/robofactor/parsing/models.py b/src/robofactor/parsing/models.py
new file mode 100644
index 0000000..97ff30d
--- /dev/null
+++ b/src/robofactor/parsing/models.py
@@ -0,0 +1,114 @@
+import enum
+from collections.abc import Sequence
+from dataclasses import dataclass
+
+from pydantic import BaseModel, Field
+
+from ..json.types import JSON
+
+
+class TestCase(BaseModel):
+ """Represents a single test case with positional and keyword args and expected output."""
+
+ args: JSON = Field()
+ kwargs: JSON = Field()
+ expected_output: JSON
+
+
+class CodeQualityScores(BaseModel):
+ """Holds various code quality metrics."""
+
+ linting_score: float
+ complexity_score: float
+ typing_score: float
+ docstring_score: float
+ linting_issues: Sequence[str] = Field(default_factory=Sequence)
+
+
+class ParameterKind(enum.Enum):
+ """Enumeration for the different kinds of function parameters."""
+
+ POSITIONAL_ONLY = "positional_only"
+ POSITIONAL_OR_KEYWORD = "positional_or_keyword"
+ VAR_POSITIONAL = "var_positional"
+ KEYWORD_ONLY = "keyword_only"
+ VAR_KEYWORD = "var_keyword"
+
+
+@dataclass(frozen=True)
+class Parameter:
+ """
+ Represents a function parameter with its name, kind, and optional details.
+
+ Attributes:
+ name: The name of the parameter.
+ kind: The kind of the parameter (e.g., positional-only).
+ annotation: The type annotation as a string, if present.
+ default: The default value as a string, if present.
+ """
+
+ name: str
+ kind: ParameterKind
+ annotation: str | None = None
+ default: str | None = None
+
+
+@dataclass(frozen=True)
+class Decorator:
+ """
+ Represents a function decorator.
+
+ Attributes:
+ name: The name of the decorator.
+ args: A tuple of arguments passed to the decorator, as strings.
+ """
+
+ name: str
+ args: tuple[str, ...] = ()
+
+
+@dataclass(frozen=True)
+class FunctionContext:
+ """Represents the context where a function is defined (base class)."""
+
+ pass
+
+
+@dataclass(frozen=True)
+class ModuleContext(FunctionContext):
+ """
+ Represents a function defined at the module level.
+
+ Attributes:
+ module_name: The name of the module.
+ """
+
+ module_name: str
+
+
+@dataclass(frozen=True)
+class ClassContext(FunctionContext):
+ """
+ Represents a function defined within a class.
+
+ Attributes:
+ class_name: The name of the class.
+ parent_context: The context in which the class is defined.
+ """
+
+ class_name: str
+ parent_context: FunctionContext
+
+
+@dataclass(frozen=True)
+class NestedContext(FunctionContext):
+ """
+ Represents a function defined within another function.
+
+ Attributes:
+ parent_function: The name of the enclosing function.
+ parent_context: The context of the enclosing function.
+ """
+
+ parent_function: str
+ parent_context: FunctionContext
diff --git a/src/robofactor/refactoring/__init__.py b/src/robofactor/refactoring/__init__.py
new file mode 100644
index 0000000..4b0d8ef
--- /dev/null
+++ b/src/robofactor/refactoring/__init__.py
@@ -0,0 +1,9 @@
+"""
+The refactoring package contains the core AI-powered logic for code
+transformation. It defines the DSPy modules, signatures, and evaluators
+that work together to analyze, plan, and execute code improvements.
+"""
+
+from . import evaluator, module, signatures
+
+__all__ = ["evaluator", "module", "signatures"]
diff --git a/src/robofactor/refactoring/evaluator.py b/src/robofactor/refactoring/evaluator.py
new file mode 100644
index 0000000..ff26b57
--- /dev/null
+++ b/src/robofactor/refactoring/evaluator.py
@@ -0,0 +1,90 @@
+"""
+Defines the DSPy module for evaluating refactored code.
+"""
+
+import logging
+
+import dspy
+from returns.result import Result, Success
+
+from ..evaluation import EvaluationResult, evaluate_refactored_code
+from ..parsing import analysis
+from .signatures import FinalEvaluation
+
+# --- Constants ---
+FAILURE_SCORE = 0.0
+logger = logging.getLogger(__name__)
+
+
+class RefactoringEvaluator(dspy.Module):
+ """Evaluates refactored code through automated checks and LLM assessment."""
+
+ def __init__(self) -> None:
+ """Initializes the evaluator module."""
+ super().__init__()
+ self.evaluator: dspy.Module = dspy.Predict(FinalEvaluation)
+
+ def _handle_evaluation_success(
+ self, eval_data: EvaluationResult, refactored_code: str
+ ) -> float:
+ """
+ Process a successful programmatic evaluation by sending results to an LLM.
+
+ Args:
+ eval_data: The structured results from the programmatic checks.
+ refactored_code: The code that was evaluated.
+
+ Returns:
+ The final score from the LLM assessment, or a failure score.
+ """
+ functional_score = (
+ eval_data.functional_check.passed_tests / eval_data.functional_check.total_tests
+ if eval_data.functional_check.total_tests > 0
+ else 1.0
+ )
+
+ try:
+ llm_evaluation = self.evaluator(
+ code_snippet=refactored_code,
+ quality_scores=eval_data.quality_scores.model_dump_json(),
+ functional_score=functional_score,
+ )
+ return llm_evaluation.evaluation.final_score
+ except Exception as e:
+ logger.error(f"LLM evaluation failed: {e}", exc_info=True)
+ return FAILURE_SCORE
+
+ def forward(self, original_example: dspy.Example, prediction: dspy.Prediction) -> float:
+ """
+ Executes the full evaluation pipeline for a refactoring prediction.
+
+ This function serves as the metric for the DSPy teleprompter. It first
+ runs programmatic checks and then uses an LLM for a final assessment.
+
+ Args:
+ original_example: The original data point, containing test cases.
+ prediction: The output from the CodeRefactor module.
+
+ Returns:
+ A final score between 0.0 and 1.0.
+ """
+ refactored_code = getattr(prediction, "refactored_code", "")
+ if not refactored_code:
+ logger.warning("Evaluation aborted: Missing refactored code")
+ return FAILURE_SCORE
+
+ code_to_evaluate = analysis.extract_python_code(refactored_code)
+ if not code_to_evaluate:
+ logger.warning("Evaluation aborted: Empty code extraction")
+ return FAILURE_SCORE
+
+ test_cases = getattr(original_example, "test_cases", [])
+ eval_result: Result[EvaluationResult, str] = evaluate_refactored_code(
+ code_to_evaluate, test_cases
+ )
+
+ if isinstance(eval_result, Success):
+ return self._handle_evaluation_success(eval_result.unwrap(), code_to_evaluate)
+
+ logger.warning(f"Programmatic evaluation failed: {eval_result.failure()}")
+ return FAILURE_SCORE
diff --git a/src/robofactor/refactoring/module.py b/src/robofactor/refactoring/module.py
new file mode 100644
index 0000000..f1cba3f
--- /dev/null
+++ b/src/robofactor/refactoring/module.py
@@ -0,0 +1,48 @@
+"""
+Defines the core DSPy module for the code refactoring pipeline.
+"""
+
+import dspy
+
+from .signatures import CodeAnalysis, RefactoredCode, RefactoringPlan
+
+
+class CodeRefactor(dspy.Module):
+ """Orchestrates code analysis, planning, and refactoring."""
+
+ def __init__(self) -> None:
+ """Initializes the multi-stage refactoring module."""
+ super().__init__()
+ self.analyzer: dspy.Module = dspy.Predict(CodeAnalysis)
+ self.planner: dspy.Module = dspy.Predict(RefactoringPlan)
+ self.implementer: dspy.Module = dspy.Predict(RefactoredCode)
+
+ def forward(self, code_snippet: str) -> dspy.Prediction:
+ """
+ Executes the analysis, planning, and implementation pipeline.
+
+ Args:
+ code_snippet: The Python code to be refactored.
+
+ Returns:
+ A dspy.Prediction object containing the full trace of the
+ refactoring process, from analysis to final code.
+ """
+ analysis_result = self.analyzer(code_snippet=code_snippet)
+ plan_result = self.planner(
+ code_snippet=code_snippet, analysis=analysis_result.analysis.analysis
+ )
+ impl_result = self.implementer(
+ original_code=code_snippet,
+ refactoring_summary=plan_result.plan.refactoring_summary,
+ plan_steps=plan_result.plan.plan_steps,
+ )
+
+ return dspy.Prediction(
+ analysis=analysis_result.analysis.analysis,
+ refactoring_opportunities=analysis_result.analysis.refactoring_opportunities,
+ refactoring_summary=plan_result.plan.refactoring_summary,
+ plan_steps=plan_result.plan.plan_steps,
+ refactored_code=impl_result.implementation.refactored_code,
+ implementation_explanation=impl_result.implementation.implementation_explanation,
+ )
diff --git a/src/robofactor/refactoring/signatures.py b/src/robofactor/refactoring/signatures.py
new file mode 100644
index 0000000..1cedaab
--- /dev/null
+++ b/src/robofactor/refactoring/signatures.py
@@ -0,0 +1,129 @@
+"""
+Defines the Pydantic data models and DSPy Signatures for the refactoring process.
+
+This module specifies the structured inputs and outputs for each step of the
+AI-powered refactoring pipeline, including analysis, planning, implementation,
+and evaluation.
+"""
+
+from collections.abc import Sequence
+
+import dspy
+from pydantic import BaseModel, Field, field_validator, model_validator
+
+from robofactor.common.code_extraction import extract_python_code
+
+
+# --- Pydantic Models ---
+class AnalysisOutput(BaseModel):
+ """Structured analysis of Python code functionality and improvement opportunities."""
+
+ analysis: str = Field(
+ description="Concise summary of functionality, complexity, and dependencies"
+ )
+ refactoring_opportunities: Sequence[str] = Field(
+ description="Actionable bullet points for refactoring"
+ )
+
+
+class PlanOutput(BaseModel):
+ """Step-by-step refactoring execution plan."""
+
+ refactoring_summary: str = Field(description="High-level refactoring objective")
+ plan_steps: Sequence[str] = Field(description="Sequential actions to achieve refactoring")
+
+
+class ImplementationOutput(BaseModel):
+ """Final refactored code with change explanations."""
+
+ refactored_code: str = Field(
+ description="PEP8-compliant Python code with type hints and docstrings"
+ )
+ implementation_explanation: str = Field(description="Rationale for implemented changes")
+
+ @field_validator("refactored_code")
+ @classmethod
+ def extract_from_markdown(cls, v: str) -> str:
+ """Extracts Python code from a markdown code block."""
+ return extract_python_code(v)
+
+
+class EvaluationOutput(BaseModel):
+ """Holistic assessment of refactoring quality."""
+
+ final_score: float = Field(description="Weighted quality score (0.0-1.0)", ge=0.0, le=1.0)
+ final_suggestion: str = Field(description="Improvement recommendations or approval")
+
+ @model_validator(mode="after")
+ def validate_score_precision(self) -> "EvaluationOutput":
+ """Rounds the final score to two decimal places."""
+ if isinstance(self.final_score, float):
+ self.final_score = round(self.final_score, 2)
+ return self
+
+
+# --- DSPy Signatures ---
+class CodeAnalysis(dspy.Signature):
+ """
+ Analyze Python code for functionality and improvement areas.
+
+ **Instruction**: You are an expert code analyst. Your task is to thoroughly
+ examine the provided Python code snippet. Identify its core functionality,
+ dependencies, and complexity. Then, suggest concrete, actionable refactoring
+ opportunities. The analysis should be concise, and the opportunities should be
+ clear and directly implementable.
+ """
+
+ code_snippet: str = dspy.InputField(desc="Python code to analyze")
+ analysis: AnalysisOutput = dspy.OutputField()
+
+
+class RefactoringPlan(dspy.Signature):
+ """
+ Create a refactoring plan based on code analysis.
+
+ **Instruction**: You are a senior software architect. Based on the provided code
+ and its analysis, create a high-level refactoring plan. Define a clear
+ objective for the refactoring and then break it down into a sequence of
+ specific, logical steps. The plan should be easy to follow and lead to a
+ measurably better version of the code.
+ """
+
+ code_snippet: str = dspy.InputField(desc="Original Python code")
+ analysis: str = dspy.InputField(desc="Code analysis summary")
+ plan: PlanOutput = dspy.OutputField()
+
+
+class RefactoredCode(dspy.Signature):
+ """
+ Generate refactored code from an execution plan.
+
+ **Instruction**: You are a world-class Python programmer. Your task is to
+ implement the refactoring plan for the given code. The final code must be
+ 100% PEP8 compliant, include comprehensive docstrings (Google-style),
+ and have full type hints. Provide a clear explanation of the changes you made
+ and why. The refactored code must be enclosed in a single Python markdown block.
+ """
+
+ original_code: str = dspy.InputField(desc="Unmodified source code")
+ refactoring_summary: str = dspy.InputField(desc="Refactoring objective")
+ plan_steps: Sequence[str] = dspy.InputField(desc="Step-by-step refactoring actions")
+ implementation: ImplementationOutput = dspy.OutputField()
+
+
+class FinalEvaluation(dspy.Signature):
+ """
+ Assess refactored code quality with quantitative metrics.
+
+ **Instruction**: You are a quality assurance automation bot. Evaluate the
+ refactored code based on the provided quality and functional scores.
+ Your assessment must result in a final score between 0.0 and 1.0,
+ where 1.0 is a perfect refactoring. Provide a concluding suggestion,
+ either approving the code or recommending specific further improvements.
+ The final score should be a weighted average of the inputs.
+ """
+
+ code_snippet: str = dspy.InputField(desc="Refactored Python code")
+ quality_scores: str = dspy.InputField(desc="JSON quality metrics")
+ functional_score: float = dspy.InputField(desc="Test pass rate (0.0-1.0)")
+ evaluation: EvaluationOutput = dspy.OutputField()
diff --git a/src/robofactor/training/__init__.py b/src/robofactor/training/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/robofactor/training/models.py b/src/robofactor/training/models.py
new file mode 100644
index 0000000..6814bda
--- /dev/null
+++ b/src/robofactor/training/models.py
@@ -0,0 +1,36 @@
+"""Pydantic models for the training data JSON format.
+
+These models ensure type-safe deserialization of the training dataset and
+eliminate the reliance on ``json.loads`` returning ``Any``. They also provide
+single-source-of-truth schemas that other parts of the codebase can reference,
+keeping the system DRY and explicit.
+"""
+
+from collections.abc import Sequence
+
+from pydantic import BaseModel, Field, TypeAdapter
+
+from ..parsing.models import TestCase
+
+
+class TrainingEntry(BaseModel):
+ """A single training data record.
+
+ Attributes
+ ----------
+ code_snippet
+ The Python source code which DSPy will analyse and refactor.
+ test_cases
+ A possibly empty sequence of :class:`robofactor.parsing.models.TestCase`
+ instances that validate the behaviour of ``code_snippet``.
+ """
+
+ code_snippet: str = Field(..., min_length=1)
+ test_cases: Sequence[TestCase] = Field(default_factory=tuple)
+
+
+#: A pre-configured adapter that can validate a *top-level* JSON array of
+#: :class:`TrainingEntry` objects. We declare the adapter at module import
+#: so that the heavy schema compilation happens once and can be reused by
+#: every call to :pyfunc:`robofactor.training.training_loader.load_training_data`.
+TrainingSetAdapter: TypeAdapter[list[TrainingEntry]] = TypeAdapter(list[TrainingEntry])
diff --git a/src/robofactor/training_data.json b/src/robofactor/training/training_data.json
similarity index 100%
rename from src/robofactor/training_data.json
rename to src/robofactor/training/training_data.json
diff --git a/src/robofactor/training/training_loader.py b/src/robofactor/training/training_loader.py
new file mode 100644
index 0000000..3dc8207
--- /dev/null
+++ b/src/robofactor/training/training_loader.py
@@ -0,0 +1,37 @@
+from __future__ import annotations
+
+import logging
+
+import dspy
+from pydantic import ValidationError
+
+from robofactor.app.config import TRAINING_DATA_FILE
+
+from .models import TrainingEntry, TrainingSetAdapter
+
+FAILURE_SCORE = 0.0
+logger = logging.getLogger(__name__)
+
+
+def load_training_data() -> list[dspy.Example]:
+ """Return the validated training set as a list of DSPy ``Example`` objects."""
+
+ try:
+ raw_text: str = TRAINING_DATA_FILE.read_text(encoding="utf-8")
+ except FileNotFoundError:
+ logger.error("Training data file not found: %s", TRAINING_DATA_FILE)
+ return []
+
+ try:
+ entries: list[TrainingEntry] = TrainingSetAdapter.validate_json(raw_text)
+ except ValidationError as exc:
+ logger.error("Invalid JSON in training data: %s", exc)
+ return []
+
+ return [
+ dspy.Example(
+ code_snippet=entry.code_snippet,
+ test_cases=list(entry.test_cases), # Already validated TestCase models
+ ).with_inputs("code_snippet")
+ for entry in entries
+ ]
diff --git a/src/robofactor/utils.py b/src/robofactor/utils.py
index a2d548e..2543157 100644
--- a/src/robofactor/utils.py
+++ b/src/robofactor/utils.py
@@ -1,5 +1,6 @@
import warnings
+
def suppress_pydantic_warnings():
# TODO: Remove this warning suppression once DSPy fixes Pydantic serialization compatibility.
#
@@ -31,4 +32,4 @@ def suppress_pydantic_warnings():
#
# Last checked: 19 June 2025
# DSPy version when added: 2.6.27
- warnings.filterwarnings('ignore', category=UserWarning, module='pydantic.main')
+ warnings.filterwarnings("ignore", category=UserWarning, module="pydantic.main")
diff --git a/uv.lock b/uv.lock
index 8ba797a..d1d267d 100644
--- a/uv.lock
+++ b/uv.lock
@@ -191,6 +191,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148 },
]
+[[package]]
+name = "basedpyright"
+version = "1.29.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "nodejs-wheel-binaries" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/80/fb/bd92196a07e3b4ccee4ff2761a26a05bff77d4da089b67b4b1a547868099/basedpyright-1.29.4.tar.gz", hash = "sha256:2df1976f8591eedf4b4ce8f9d123f43e810cc8cb7cc83c53eec0e2f8044073d0", size = 21961481 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d5/dc/180fe721a2574fb3aad4051adcca196ac2d18adaf75122f5eeb47436cca2/basedpyright-1.29.4-py3-none-any.whl", hash = "sha256:e087513979972f83010639c6c1a1c13dd3b1d24ee45f8ecff747962cc2063d6f", size = 11476859 },
+]
+
[[package]]
name = "blinker"
version = "1.9.0"
@@ -410,39 +422,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/51/db/6d34604be92a163309cbf1e3aeb11ea464012cdc33fe11132ea1eff2f072/databricks_sdk-0.57.0-py3-none-any.whl", hash = "sha256:a253bb4c7e00e43654af8b6e29ac79bee72d310e342ec73e148e4e591b75915f", size = 733790 },
]
-[[package]]
-name = "datasets"
-version = "3.6.0"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "dill" },
- { name = "filelock" },
- { name = "fsspec", extra = ["http"] },
- { name = "huggingface-hub" },
- { name = "multiprocess" },
- { name = "numpy" },
- { name = "packaging" },
- { name = "pandas" },
- { name = "pyarrow" },
- { name = "pyyaml" },
- { name = "requests" },
- { name = "tqdm" },
- { name = "xxhash" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/1a/89/d3d6fef58a488f8569c82fd293ab7cbd4250244d67f425dcae64c63800ea/datasets-3.6.0.tar.gz", hash = "sha256:1b2bf43b19776e2787e181cfd329cb0ca1a358ea014780c3581e0f276375e041", size = 569336 }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/20/34/a08b0ee99715eaba118cbe19a71f7b5e2425c2718ef96007c325944a1152/datasets-3.6.0-py3-none-any.whl", hash = "sha256:25000c4a2c0873a710df127d08a202a06eab7bf42441a6bc278b499c2f72cd1b", size = 491546 },
-]
-
-[[package]]
-name = "dill"
-version = "0.3.8"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/17/4d/ac7ffa80c69ea1df30a8aa11b3578692a5118e7cd1aa157e3ef73b092d15/dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca", size = 184847 }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/c9/7a/cef76fd8438a42f96db64ddaa85280485a9c395e7df3db8158cfec1eee34/dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7", size = 116252 },
-]
-
[[package]]
name = "diskcache"
version = "5.6.3"
@@ -477,15 +456,14 @@ wheels = [
[[package]]
name = "dspy"
-version = "2.6.27"
-source = { registry = "https://pypi.org/simple" }
+version = "3.0.0b1"
+source = { git = "https://github.com/stanfordnlp/dspy.git#734eff216155207dfea89765a46e935ee1482794" }
dependencies = [
{ name = "anyio" },
{ name = "asyncer" },
{ name = "backoff" },
{ name = "cachetools" },
{ name = "cloudpickle" },
- { name = "datasets" },
{ name = "diskcache" },
{ name = "joblib" },
{ name = "json-repair" },
@@ -494,7 +472,6 @@ dependencies = [
{ name = "numpy" },
{ name = "openai" },
{ name = "optuna" },
- { name = "pandas" },
{ name = "pydantic" },
{ name = "regex" },
{ name = "requests" },
@@ -503,10 +480,6 @@ dependencies = [
{ name = "tqdm" },
{ name = "ujson" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/38/8a/f7ff1a6d3b5294678f13d17ecfc596f49a59e494b190e4e30f7dea7df1dc/dspy-2.6.27.tar.gz", hash = "sha256:de1c4f6f6d127e0efed894e1915dac40f5d5623e7f1cf3d749c98d790066477a", size = 234604 }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/5a/bb/8a75d44bc1b54dea0fa0428eb52b13e7ee533b85841d2c53a53dfc360646/dspy-2.6.27-py3-none-any.whl", hash = "sha256:54e55fd6999b6a46e09b0e49e8c4b71be7dd56a881e66f7a60b8d657650c1a74", size = 297296 },
-]
[[package]]
name = "dspy-ai"
@@ -557,16 +530,16 @@ wheels = [
[[package]]
name = "flake8"
-version = "7.2.0"
+version = "7.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mccabe" },
{ name = "pycodestyle" },
{ name = "pyflakes" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/e7/c4/5842fc9fc94584c455543540af62fd9900faade32511fab650e9891ec225/flake8-7.2.0.tar.gz", hash = "sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426", size = 48177 }
+sdist = { url = "https://files.pythonhosted.org/packages/9b/af/fbfe3c4b5a657d79e5c47a2827a362f9e1b763336a52f926126aa6dc7123/flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872", size = 48326 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/83/5c/0627be4c9976d56b1217cb5187b7504e7fd7d3503f8bfd312a04077bd4f7/flake8-7.2.0-py2.py3-none-any.whl", hash = "sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343", size = 57786 },
+ { url = "https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", size = 57922 },
]
[[package]]
@@ -723,16 +696,11 @@ wheels = [
[[package]]
name = "fsspec"
-version = "2025.3.0"
+version = "2025.5.1"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/34/f4/5721faf47b8c499e776bc34c6a8fc17efdf7fdef0b00f398128bc5dcb4ac/fsspec-2025.3.0.tar.gz", hash = "sha256:a935fd1ea872591f2b5148907d103488fc523295e6c64b835cfad8c3eca44972", size = 298491 }
+sdist = { url = "https://files.pythonhosted.org/packages/00/f7/27f15d41f0ed38e8fcc488584b57e902b331da7f7c6dcda53721b15838fc/fsspec-2025.5.1.tar.gz", hash = "sha256:2e55e47a540b91843b755e83ded97c6e897fa0942b11490113f09e9c443c2475", size = 303033 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/56/53/eb690efa8513166adef3e0669afd31e95ffde69fb3c52ec2ac7223ed6018/fsspec-2025.3.0-py3-none-any.whl", hash = "sha256:efb87af3efa9103f94ca91a7f8cb7a4df91af9f74fc106c9c7ea0efd7277c1b3", size = 193615 },
-]
-
-[package.optional-dependencies]
-http = [
- { name = "aiohttp" },
+ { url = "https://files.pythonhosted.org/packages/bb/61/78c7b3851add1481b048b5fdc29067397a1784e2910592bc81bb3f608635/fsspec-2025.5.1-py3-none-any.whl", hash = "sha256:24d3a2e663d5fc735ab256263c4075f374a174c3410c0b25e5bd1970bceaa462", size = 199052 },
]
[[package]]
@@ -860,6 +828,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5c/4f/aab73ecaa6b3086a4c89863d94cf26fa84cbff63f52ce9bc4342b3087a06/greenlet-3.2.3-cp314-cp314-win_amd64.whl", hash = "sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a", size = 301236 },
]
+[[package]]
+name = "grep-ast"
+version = "0.9.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pathspec" },
+ { name = "tree-sitter-language-pack" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/67/82/a87079945a7c15d242cb586ae22e17952132439eaa9c878ec5fbdc61c54d/grep_ast-0.9.0.tar.gz", hash = "sha256:620a242a4493e6721338d1c9a6c234ae651f8774f4924a6dcf90f6865d4b2ee3", size = 14125 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/40/79/29f1373b2ce1eec37c03aefbc17194c2470d8b61ede288e5043231825999/grep_ast-0.9.0-py3-none-any.whl", hash = "sha256:a3973dca99f1abc026a01bbbc70e00a63860c8ff94a56182ff18b089836826d7", size = 13918 },
+]
+
[[package]]
name = "gunicorn"
version = "23.0.0"
@@ -883,17 +864,17 @@ wheels = [
[[package]]
name = "hf-xet"
-version = "1.1.4"
+version = "1.1.5"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/8d/11/b480bb7515db97d5b2b703927a59bbdd3f87e68d47dff5591aada467b4a9/hf_xet-1.1.4.tar.gz", hash = "sha256:875158df90cb13547752532ed73cad9dfaad3b29e203143838f67178418d08a4", size = 492082 }
+sdist = { url = "https://files.pythonhosted.org/packages/ed/d4/7685999e85945ed0d7f0762b686ae7015035390de1161dcea9d5276c134c/hf_xet-1.1.5.tar.gz", hash = "sha256:69ebbcfd9ec44fdc2af73441619eeb06b94ee34511bbcf57cd423820090f5694", size = 495969 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/c4/62/3b41a7439930996530c64955874445012fd9044c82c60b34c5891c34fec6/hf_xet-1.1.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6591ab9f61ea82d261107ed90237e2ece972f6a7577d96f5f071208bbf255d1c", size = 2643151 },
- { url = "https://files.pythonhosted.org/packages/9b/9f/1744fb1d79e0ac147578b193ce29208ebb9f4636e8cdf505638f6f0a6874/hf_xet-1.1.4-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:071b0b4d4698990f746edd666c7cc42555833d22035d88db0df936677fb57d29", size = 2510687 },
- { url = "https://files.pythonhosted.org/packages/d1/a8/49a81d4f81b0d21cc758b6fca3880a85ca0d209e8425c8b3a6ef694881ca/hf_xet-1.1.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b610831e92e41182d4c028653978b844d332d492cdcba1b920d3aca4a0207e", size = 3057631 },
- { url = "https://files.pythonhosted.org/packages/bf/8b/65fa08273789dafbc38d0f0bdd20df56b63ebc6566981bbaa255d9d84a33/hf_xet-1.1.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f6578bcd71393abfd60395279cc160ca808b61f5f9d535b922fcdcd3f77a708d", size = 2949250 },
- { url = "https://files.pythonhosted.org/packages/8b/4b/224340bb1d5c63b6e03e04095b4e42230848454bf4293c45cd7bdaa0c208/hf_xet-1.1.4-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fb2bbfa2aae0e4f0baca988e7ba8d8c1a39a25adf5317461eb7069ad00505b3e", size = 3124670 },
- { url = "https://files.pythonhosted.org/packages/4a/b7/4be010014de6585401c32a04c46b09a4a842d66bd16ed549a401e973b74b/hf_xet-1.1.4-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:73346ba3e2e15ea8909a26b0862b458f15b003e6277935e3fba5bf273508d698", size = 3234131 },
- { url = "https://files.pythonhosted.org/packages/c2/2d/cf148d532f741fbf93f380ff038a33c1309d1e24ea629dc39d11dca08c92/hf_xet-1.1.4-cp37-abi3-win_amd64.whl", hash = "sha256:52e8f8bc2029d8b911493f43cea131ac3fa1f0dc6a13c50b593c4516f02c6fc3", size = 2695589 },
+ { url = "https://files.pythonhosted.org/packages/00/89/a1119eebe2836cb25758e7661d6410d3eae982e2b5e974bcc4d250be9012/hf_xet-1.1.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f52c2fa3635b8c37c7764d8796dfa72706cc4eded19d638331161e82b0792e23", size = 2687929 },
+ { url = "https://files.pythonhosted.org/packages/de/5f/2c78e28f309396e71ec8e4e9304a6483dcbc36172b5cea8f291994163425/hf_xet-1.1.5-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:9fa6e3ee5d61912c4a113e0708eaaef987047616465ac7aa30f7121a48fc1af8", size = 2556338 },
+ { url = "https://files.pythonhosted.org/packages/6d/2f/6cad7b5fe86b7652579346cb7f85156c11761df26435651cbba89376cd2c/hf_xet-1.1.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc874b5c843e642f45fd85cda1ce599e123308ad2901ead23d3510a47ff506d1", size = 3102894 },
+ { url = "https://files.pythonhosted.org/packages/d0/54/0fcf2b619720a26fbb6cc941e89f2472a522cd963a776c089b189559447f/hf_xet-1.1.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dbba1660e5d810bd0ea77c511a99e9242d920790d0e63c0e4673ed36c4022d18", size = 3002134 },
+ { url = "https://files.pythonhosted.org/packages/f3/92/1d351ac6cef7c4ba8c85744d37ffbfac2d53d0a6c04d2cabeba614640a78/hf_xet-1.1.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ab34c4c3104133c495785d5d8bba3b1efc99de52c02e759cf711a91fd39d3a14", size = 3171009 },
+ { url = "https://files.pythonhosted.org/packages/c9/65/4b2ddb0e3e983f2508528eb4501288ae2f84963586fbdfae596836d5e57a/hf_xet-1.1.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:83088ecea236d5113de478acb2339f92c95b4fb0462acaa30621fac02f5a534a", size = 3279245 },
+ { url = "https://files.pythonhosted.org/packages/f0/55/ef77a85ee443ae05a9e9cba1c9f0dd9241eb42da2aeba1dc50f51154c81a/hf_xet-1.1.5-cp37-abi3-win_amd64.whl", hash = "sha256:73e167d9807d166596b4b2f0b585c6d5bd84a26dea32843665a8b58f6edba245", size = 2738931 },
]
[[package]]
@@ -964,15 +945,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656 },
]
-[[package]]
-name = "isort"
-version = "6.0.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/b8/21/1e2a441f74a653a144224d7d21afe8f4169e6c7c20bb13aec3a2dc3815e0/isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450", size = 821955 }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/c1/11/114d0a5f4dabbdcedc1125dee0888514c3c3b16d3e9facad87ed96fad97c/isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615", size = 94186 },
-]
-
[[package]]
name = "itsdangerous"
version = "2.2.0"
@@ -1077,11 +1049,11 @@ wheels = [
[[package]]
name = "json-repair"
-version = "0.47.1"
+version = "0.47.3"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/51/83/46f16a17e7a12e81719da3c17eefceb39c7dc076cf8ae48aef421cf81504/json_repair-0.47.1.tar.gz", hash = "sha256:e232ae4def2cb5ce00900f74ca8c0a05cab148bd2ec7eba85506aef26e750805", size = 33882 }
+sdist = { url = "https://files.pythonhosted.org/packages/42/1b/13f80ab76f81552f201fa1e5147d21b2c2c496927665b88ab37e67c185af/json_repair-0.47.3.tar.gz", hash = "sha256:030d036db0e4f7896cfc422dd47e7022c0942ffe14d8d7cd6b1b1abd40f6636a", size = 33863 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/c3/bb/616395c4e06ce6ffdd4b2b73afc7bbb072391d8b0824294a1a3419a4770b/json_repair-0.47.1-py3-none-any.whl", hash = "sha256:0b9c0b3ae400e83317efdaab91983a2b63faebebda2924dcfdd16080839673c7", size = 22475 },
+ { url = "https://files.pythonhosted.org/packages/0d/20/3beb965d1731ce35e1b1392b4e357fa5ef2ffc14402d28b3c2ef47ff893c/json_repair-0.47.3-py3-none-any.whl", hash = "sha256:a35c909f1b2f4a9bbf5453bc4f60cc173728f394700500ad859feb47914550f5", size = 22449 },
]
[[package]]
@@ -1200,7 +1172,7 @@ wheels = [
[[package]]
name = "litellm"
-version = "1.72.6.post2"
+version = "1.73.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp" },
@@ -1215,7 +1187,10 @@ dependencies = [
{ name = "tiktoken" },
{ name = "tokenizers" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/8f/50/e594d8978362796e44c9643befac6dd27dcc113aa700723697bae849ed72/litellm-1.72.6.post2.tar.gz", hash = "sha256:24dbe0efaeca0712d2e18795a6734d1678af086ed9ea1893721c426fe984398d", size = 8363431 }
+sdist = { url = "https://files.pythonhosted.org/packages/3e/5e/e110a45916b18ac93234e1b76a6ca57e7a3f9d38c4b9c004b68aedcddf41/litellm-1.73.0.tar.gz", hash = "sha256:4fdbb86f349be2038068827517786f6f7e7f761528d8f5d4b941b406d33bb8c3", size = 8553383 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8c/ef/a788e2aca00e1afa97e07a14e85b9a1189498bbd424be7e1aea57cc5a831/litellm-1.73.0-py3-none-any.whl", hash = "sha256:0a0a14c2f2522ffaf6cfbea043108d837047ee2b8dff5fb1dc14a7bd3cea0118", size = 8358192 },
+]
[[package]]
name = "magicattr"
@@ -1533,78 +1508,28 @@ wheels = [
]
[[package]]
-name = "multiprocess"
-version = "0.70.16"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "dill" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/b5/ae/04f39c5d0d0def03247c2893d6f2b83c136bf3320a2154d7b8858f2ba72d/multiprocess-0.70.16.tar.gz", hash = "sha256:161af703d4652a0e1410be6abccecde4a7ddffd19341be0a7011b94aeb171ac1", size = 1772603 }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/ef/76/6e712a2623d146d314f17598df5de7224c85c0060ef63fd95cc15a25b3fa/multiprocess-0.70.16-pp310-pypy310_pp73-macosx_10_13_x86_64.whl", hash = "sha256:476887be10e2f59ff183c006af746cb6f1fd0eadcfd4ef49e605cbe2659920ee", size = 134980 },
- { url = "https://files.pythonhosted.org/packages/0f/ab/1e6e8009e380e22254ff539ebe117861e5bdb3bff1fc977920972237c6c7/multiprocess-0.70.16-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d951bed82c8f73929ac82c61f01a7b5ce8f3e5ef40f5b52553b4f547ce2b08ec", size = 134982 },
- { url = "https://files.pythonhosted.org/packages/bc/f7/7ec7fddc92e50714ea3745631f79bd9c96424cb2702632521028e57d3a36/multiprocess-0.70.16-py310-none-any.whl", hash = "sha256:c4a9944c67bd49f823687463660a2d6daae94c289adff97e0f9d696ba6371d02", size = 134824 },
- { url = "https://files.pythonhosted.org/packages/50/15/b56e50e8debaf439f44befec5b2af11db85f6e0f344c3113ae0be0593a91/multiprocess-0.70.16-py311-none-any.whl", hash = "sha256:af4cabb0dac72abfb1e794fa7855c325fd2b55a10a44628a3c1ad3311c04127a", size = 143519 },
- { url = "https://files.pythonhosted.org/packages/0a/7d/a988f258104dcd2ccf1ed40fdc97e26c4ac351eeaf81d76e266c52d84e2f/multiprocess-0.70.16-py312-none-any.whl", hash = "sha256:fc0544c531920dde3b00c29863377f87e1632601092ea2daca74e4beb40faa2e", size = 146741 },
- { url = "https://files.pythonhosted.org/packages/ea/89/38df130f2c799090c978b366cfdf5b96d08de5b29a4a293df7f7429fa50b/multiprocess-0.70.16-py38-none-any.whl", hash = "sha256:a71d82033454891091a226dfc319d0cfa8019a4e888ef9ca910372a446de4435", size = 132628 },
- { url = "https://files.pythonhosted.org/packages/da/d9/f7f9379981e39b8c2511c9e0326d212accacb82f12fbfdc1aa2ce2a7b2b6/multiprocess-0.70.16-py39-none-any.whl", hash = "sha256:a0bafd3ae1b732eac64be2e72038231c1ba97724b60b09400d68f229fcc2fbf3", size = 133351 },
-]
-
-[[package]]
-name = "mypy"
-version = "1.16.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "mypy-extensions" },
- { name = "pathspec" },
- { name = "tomli", marker = "python_full_version < '3.11'" },
- { name = "typing-extensions" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/81/69/92c7fa98112e4d9eb075a239caa4ef4649ad7d441545ccffbd5e34607cbb/mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab", size = 3324747 }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/8e/12/2bf23a80fcef5edb75de9a1e295d778e0f46ea89eb8b115818b663eff42b/mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a", size = 10958644 },
- { url = "https://files.pythonhosted.org/packages/08/50/bfe47b3b278eacf348291742fd5e6613bbc4b3434b72ce9361896417cfe5/mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72", size = 10087033 },
- { url = "https://files.pythonhosted.org/packages/21/de/40307c12fe25675a0776aaa2cdd2879cf30d99eec91b898de00228dc3ab5/mypy-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea", size = 11875645 },
- { url = "https://files.pythonhosted.org/packages/a6/d8/85bdb59e4a98b7a31495bd8f1a4445d8ffc86cde4ab1f8c11d247c11aedc/mypy-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574", size = 12616986 },
- { url = "https://files.pythonhosted.org/packages/0e/d0/bb25731158fa8f8ee9e068d3e94fcceb4971fedf1424248496292512afe9/mypy-1.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d", size = 12878632 },
- { url = "https://files.pythonhosted.org/packages/2d/11/822a9beb7a2b825c0cb06132ca0a5183f8327a5e23ef89717c9474ba0bc6/mypy-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6", size = 9484391 },
- { url = "https://files.pythonhosted.org/packages/9a/61/ec1245aa1c325cb7a6c0f8570a2eee3bfc40fa90d19b1267f8e50b5c8645/mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc", size = 10890557 },
- { url = "https://files.pythonhosted.org/packages/6b/bb/6eccc0ba0aa0c7a87df24e73f0ad34170514abd8162eb0c75fd7128171fb/mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782", size = 10012921 },
- { url = "https://files.pythonhosted.org/packages/5f/80/b337a12e2006715f99f529e732c5f6a8c143bb58c92bb142d5ab380963a5/mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507", size = 11802887 },
- { url = "https://files.pythonhosted.org/packages/d9/59/f7af072d09793d581a745a25737c7c0a945760036b16aeb620f658a017af/mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca", size = 12531658 },
- { url = "https://files.pythonhosted.org/packages/82/c4/607672f2d6c0254b94a646cfc45ad589dd71b04aa1f3d642b840f7cce06c/mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4", size = 12732486 },
- { url = "https://files.pythonhosted.org/packages/b6/5e/136555ec1d80df877a707cebf9081bd3a9f397dedc1ab9750518d87489ec/mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6", size = 9479482 },
- { url = "https://files.pythonhosted.org/packages/b4/d6/39482e5fcc724c15bf6280ff5806548c7185e0c090712a3736ed4d07e8b7/mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d", size = 11066493 },
- { url = "https://files.pythonhosted.org/packages/e6/e5/26c347890efc6b757f4d5bb83f4a0cf5958b8cf49c938ac99b8b72b420a6/mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9", size = 10081687 },
- { url = "https://files.pythonhosted.org/packages/44/c7/b5cb264c97b86914487d6a24bd8688c0172e37ec0f43e93b9691cae9468b/mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79", size = 11839723 },
- { url = "https://files.pythonhosted.org/packages/15/f8/491997a9b8a554204f834ed4816bda813aefda31cf873bb099deee3c9a99/mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15", size = 12722980 },
- { url = "https://files.pythonhosted.org/packages/df/f0/2bd41e174b5fd93bc9de9a28e4fb673113633b8a7f3a607fa4a73595e468/mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd", size = 12903328 },
- { url = "https://files.pythonhosted.org/packages/61/81/5572108a7bec2c46b8aff7e9b524f371fe6ab5efb534d38d6b37b5490da8/mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b", size = 9562321 },
- { url = "https://files.pythonhosted.org/packages/28/e3/96964af4a75a949e67df4b95318fe2b7427ac8189bbc3ef28f92a1c5bc56/mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438", size = 11063480 },
- { url = "https://files.pythonhosted.org/packages/f5/4d/cd1a42b8e5be278fab7010fb289d9307a63e07153f0ae1510a3d7b703193/mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536", size = 10090538 },
- { url = "https://files.pythonhosted.org/packages/c9/4f/c3c6b4b66374b5f68bab07c8cabd63a049ff69796b844bc759a0ca99bb2a/mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f", size = 11836839 },
- { url = "https://files.pythonhosted.org/packages/b4/7e/81ca3b074021ad9775e5cb97ebe0089c0f13684b066a750b7dc208438403/mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359", size = 12715634 },
- { url = "https://files.pythonhosted.org/packages/e9/95/bdd40c8be346fa4c70edb4081d727a54d0a05382d84966869738cfa8a497/mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be", size = 12895584 },
- { url = "https://files.pythonhosted.org/packages/5a/fd/d486a0827a1c597b3b48b1bdef47228a6e9ee8102ab8c28f944cb83b65dc/mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee", size = 9573886 },
- { url = "https://files.pythonhosted.org/packages/cf/d3/53e684e78e07c1a2bf7105715e5edd09ce951fc3f47cf9ed095ec1b7a037/mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37", size = 2265923 },
-]
-
-[[package]]
-name = "mypy-extensions"
-version = "1.1.0"
+name = "networkx"
+version = "3.4.2"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 }
+sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 },
+ { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263 },
]
[[package]]
-name = "nodeenv"
-version = "1.9.1"
+name = "nodejs-wheel-binaries"
+version = "22.16.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 }
+sdist = { url = "https://files.pythonhosted.org/packages/0f/c6/66f36b7b0d528660dfb4a59cb9b8dd6a3f4c0a3939cd49c404a775ea4a63/nodejs_wheel_binaries-22.16.0.tar.gz", hash = "sha256:d695832f026df3a0cf9a089d222225939de9d1b67f8f0a353b79f015aabbe7e2", size = 8061 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 },
+ { url = "https://files.pythonhosted.org/packages/d7/dc/417a5c5f99e53a5d2b3be122506312731eb90fb9630c248e327e2e38cc6b/nodejs_wheel_binaries-22.16.0-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:986b715a96ed703f8ce0c15712f76fc42895cf09067d72b6ef29e8b334eccf64", size = 50957501 },
+ { url = "https://files.pythonhosted.org/packages/0e/dd/d6ce48209ed15f5d1fccb29eeaa111f962557123eaf4fd03a7316c42734c/nodejs_wheel_binaries-22.16.0-py2.py3-none-macosx_11_0_x86_64.whl", hash = "sha256:4ae3cf22138891cb44c3ee952862a257ce082b098b29024d7175684a9a77b0c0", size = 51891634 },
+ { url = "https://files.pythonhosted.org/packages/80/fa/a07e622fd87717eec3e5cff41575f85ad62717e8698884d28ca809266ca1/nodejs_wheel_binaries-22.16.0-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71f2de4dc0b64ae43e146897ce811f80ac4f9acfbae6ccf814226282bf4ef174", size = 57857862 },
+ { url = "https://files.pythonhosted.org/packages/1f/80/52736f9570a93f8e6b7942981dc9770eca2bc7aa1d200c1d54198374a6ca/nodejs_wheel_binaries-22.16.0-py2.py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbfccbcd558d2f142ccf66d8c3a098022bf4436db9525b5b8d32169ce185d99e", size = 58395868 },
+ { url = "https://files.pythonhosted.org/packages/0f/0e/53616a5ed8fc1fbe9e48bf132862da5a9abf5cc7f8483dab1722ec257187/nodejs_wheel_binaries-22.16.0-py2.py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:447ad796850eb52ca20356ad39b2d296ed8fef3f214921f84a1ccdad49f2eba1", size = 59712469 },
+ { url = "https://files.pythonhosted.org/packages/4a/cd/e2b5083df581fc1d08eb93feb6f8fbd3d56b113cef9b59d8e0fb7d4dd4f3/nodejs_wheel_binaries-22.16.0-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7f526ca6a132b0caf633566a2a78c6985fe92857e7bfdb37380f76205a10b808", size = 60763005 },
+ { url = "https://files.pythonhosted.org/packages/71/8d/57112b49214e8bd636f3cc3386eba6be4d23552ec8a0f6efbe814013caa7/nodejs_wheel_binaries-22.16.0-py2.py3-none-win_amd64.whl", hash = "sha256:2fffb4bf1066fb5f660da20819d754f1b424bca1b234ba0f4fa901c52e3975fb", size = 41313324 },
+ { url = "https://files.pythonhosted.org/packages/91/03/a852711aec73dfb965844592dfe226024c0da28e37d1ee54083342e38f57/nodejs_wheel_binaries-22.16.0-py2.py3-none-win_arm64.whl", hash = "sha256:2728972d336d436d39ee45988978d8b5d963509e06f063e80fe41b203ee80b28", size = 38828154 },
]
[[package]]
@@ -1671,7 +1596,7 @@ wheels = [
[[package]]
name = "openai"
-version = "1.88.0"
+version = "1.91.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
@@ -1683,9 +1608,9 @@ dependencies = [
{ name = "tqdm" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/5a/ea/bbeef604d1fe0f7e9111745bb8a81362973a95713b28855beb9a9832ab12/openai-1.88.0.tar.gz", hash = "sha256:122d35e42998255cf1fc84560f6ee49a844e65c054cd05d3e42fda506b832bb1", size = 470963 }
+sdist = { url = "https://files.pythonhosted.org/packages/0f/e2/a22f2973b729eff3f1f429017bdf717930c5de0fbf9e14017bae330e4e7a/openai-1.91.0.tar.gz", hash = "sha256:d6b07730d2f7c6745d0991997c16f85cddfc90ddcde8d569c862c30716b9fc90", size = 472529 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f4/03/ef68d77a38dd383cbed7fc898857d394d5a8b0520a35f054e7fe05dc3ac1/openai-1.88.0-py3-none-any.whl", hash = "sha256:7edd7826b3b83f5846562a6f310f040c79576278bf8e3687b30ba05bb5dff978", size = 734293 },
+ { url = "https://files.pythonhosted.org/packages/7a/d2/f99bdd6fc737d6b3cf0df895508d621fc9a386b375a1230ee81d46c5436e/openai-1.91.0-py3-none-any.whl", hash = "sha256:207f87aa3bc49365e014fac2f7e291b99929f4fe126c4654143440e0ad446a5f", size = 735837 },
]
[[package]]
@@ -2068,11 +1993,11 @@ wheels = [
[[package]]
name = "pycodestyle"
-version = "2.13.0"
+version = "2.14.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/04/6e/1f4a62078e4d95d82367f24e685aef3a672abfd27d1a868068fed4ed2254/pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae", size = 39312 }
+sdist = { url = "https://files.pythonhosted.org/packages/11/e0/abfd2a0d2efe47670df87f3e3a0e2edda42f055053c85361f19c0e2c1ca8/pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783", size = 39472 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/07/be/b00116df1bfb3e0bb5b45e29d604799f7b91dd861637e4d448b4e09e6a3e/pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9", size = 31424 },
+ { url = "https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d", size = 31594 },
]
[[package]]
@@ -2179,20 +2104,20 @@ wheels = [
[[package]]
name = "pyflakes"
-version = "3.3.2"
+version = "3.4.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/af/cc/1df338bd7ed1fa7c317081dcf29bf2f01266603b301e6858856d346a12b3/pyflakes-3.3.2.tar.gz", hash = "sha256:6dfd61d87b97fba5dcfaaf781171ac16be16453be6d816147989e7f6e6a9576b", size = 64175 }
+sdist = { url = "https://files.pythonhosted.org/packages/45/dc/fd034dc20b4b264b3d015808458391acbf9df40b1e54750ef175d39180b1/pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58", size = 64669 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/15/40/b293a4fa769f3b02ab9e387c707c4cbdc34f073f945de0386107d4e669e6/pyflakes-3.3.2-py2.py3-none-any.whl", hash = "sha256:5039c8339cbb1944045f4ee5466908906180f13cc99cc9949348d10f82a5c32a", size = 63164 },
+ { url = "https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f", size = 63551 },
]
[[package]]
name = "pygments"
-version = "2.19.1"
+version = "2.19.2"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 }
+sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 },
+ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 },
]
[[package]]
@@ -2204,19 +2129,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120 },
]
-[[package]]
-name = "pyright"
-version = "1.1.402"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "nodeenv" },
- { name = "typing-extensions" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/aa/04/ce0c132d00e20f2d2fb3b3e7c125264ca8b909e693841210534b1ea1752f/pyright-1.1.402.tar.gz", hash = "sha256:85a33c2d40cd4439c66aa946fd4ce71ab2f3f5b8c22ce36a623f59ac22937683", size = 3888207 }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/fe/37/1a1c62d955e82adae588be8e374c7f77b165b6cb4203f7d581269959abbc/pyright-1.1.402-py3-none-any.whl", hash = "sha256:2c721f11869baac1884e846232800fe021c33f1b4acb3929cff321f7ea4e2982", size = 5624004 },
-]
-
[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
@@ -2231,11 +2143,11 @@ wheels = [
[[package]]
name = "python-dotenv"
-version = "1.1.0"
+version = "1.1.1"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 }
+sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 },
+ { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556 },
]
[[package]]
@@ -2436,14 +2348,16 @@ wheels = [
[[package]]
name = "robofactor"
-version = "0.1.0"
+version = "0.1.1"
source = { editable = "." }
dependencies = [
+ { name = "dspy" },
{ name = "dspy-ai" },
{ name = "flake8" },
+ { name = "grep-ast" },
{ name = "mlflow" },
+ { name = "networkx" },
{ name = "pyflakes" },
- { name = "pyright" },
{ name = "returns" },
{ name = "rich" },
{ name = "toml" },
@@ -2452,19 +2366,20 @@ dependencies = [
[package.dev-dependencies]
dev = [
- { name = "isort" },
- { name = "mypy" },
+ { name = "basedpyright" },
{ name = "ruff" },
{ name = "toml" },
]
[package.metadata]
requires-dist = [
+ { name = "dspy", git = "https://github.com/stanfordnlp/dspy.git" },
{ name = "dspy-ai", specifier = "==2.6.19" },
{ name = "flake8", specifier = ">=7.2.0" },
+ { name = "grep-ast", specifier = ">=0.9.0" },
{ name = "mlflow", specifier = ">=3.1.0" },
+ { name = "networkx", specifier = ">=3.4.2" },
{ name = "pyflakes", specifier = ">=3.3.2" },
- { name = "pyright", specifier = ">=1.1.402" },
{ name = "returns", specifier = ">=0.25.0" },
{ name = "rich", specifier = ">=13.7.1" },
{ name = "toml", specifier = ">=0.10.2" },
@@ -2473,8 +2388,7 @@ requires-dist = [
[package.metadata.requires-dev]
dev = [
- { name = "isort", specifier = ">=6.0.1" },
- { name = "mypy", specifier = ">=1.16.1" },
+ { name = "basedpyright", specifier = ">=1.29.4" },
{ name = "ruff", specifier = ">=0.11.13" },
{ name = "toml", specifier = ">=0.10.2" },
]
@@ -2950,6 +2864,105 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 },
]
+[[package]]
+name = "tree-sitter"
+version = "0.24.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a7/a2/698b9d31d08ad5558f8bfbfe3a0781bd4b1f284e89bde3ad18e05101a892/tree-sitter-0.24.0.tar.gz", hash = "sha256:abd95af65ca2f4f7eca356343391ed669e764f37748b5352946f00f7fc78e734", size = 168304 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/08/9a/bd627a02e41671af73222316e1fcf87772c7804dc2fba99405275eb1f3eb/tree_sitter-0.24.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f3f00feff1fc47a8e4863561b8da8f5e023d382dd31ed3e43cd11d4cae445445", size = 140890 },
+ { url = "https://files.pythonhosted.org/packages/5b/9b/b1ccfb187f8be78e2116176a091a2f2abfd043a06d78f80c97c97f315b37/tree_sitter-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f9691be48d98c49ef8f498460278884c666b44129222ed6217477dffad5d4831", size = 134413 },
+ { url = "https://files.pythonhosted.org/packages/01/39/e25b0042a049eb27e991133a7aa7c49bb8e49a8a7b44ca34e7e6353ba7ac/tree_sitter-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:098a81df9f89cf254d92c1cd0660a838593f85d7505b28249216661d87adde4a", size = 560427 },
+ { url = "https://files.pythonhosted.org/packages/1c/59/4d132f1388da5242151b90acf32cc56af779bfba063923699ab28b276b62/tree_sitter-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b26bf9e958da6eb7e74a081aab9d9c7d05f9baeaa830dbb67481898fd16f1f5", size = 574327 },
+ { url = "https://files.pythonhosted.org/packages/ec/97/3914e45ab9e0ff0f157e493caa91791372508488b97ff0961a0640a37d25/tree_sitter-0.24.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2a84ff87a2f2a008867a1064aba510ab3bd608e3e0cd6e8fef0379efee266c73", size = 577171 },
+ { url = "https://files.pythonhosted.org/packages/c5/b0/266a529c3eef171137b73cde8ad7aa282734354609a8b2f5564428e8f12d/tree_sitter-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:c012e4c345c57a95d92ab5a890c637aaa51ab3b7ff25ed7069834b1087361c95", size = 120260 },
+ { url = "https://files.pythonhosted.org/packages/c1/c3/07bfaa345e0037ff75d98b7a643cf940146e4092a1fd54eed0359836be03/tree_sitter-0.24.0-cp310-cp310-win_arm64.whl", hash = "sha256:033506c1bc2ba7bd559b23a6bdbeaf1127cee3c68a094b82396718596dfe98bc", size = 108416 },
+ { url = "https://files.pythonhosted.org/packages/66/08/82aaf7cbea7286ee2a0b43e9b75cb93ac6ac132991b7d3c26ebe5e5235a3/tree_sitter-0.24.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de0fb7c18c6068cacff46250c0a0473e8fc74d673e3e86555f131c2c1346fb13", size = 140733 },
+ { url = "https://files.pythonhosted.org/packages/8c/bd/1a84574911c40734d80327495e6e218e8f17ef318dd62bb66b55c1e969f5/tree_sitter-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a7c9c89666dea2ce2b2bf98e75f429d2876c569fab966afefdcd71974c6d8538", size = 134243 },
+ { url = "https://files.pythonhosted.org/packages/46/c1/c2037af2c44996d7bde84eb1c9e42308cc84b547dd6da7f8a8bea33007e1/tree_sitter-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ddb113e6b8b3e3b199695b1492a47d87d06c538e63050823d90ef13cac585fd", size = 562030 },
+ { url = "https://files.pythonhosted.org/packages/4c/aa/2fb4d81886df958e6ec7e370895f7106d46d0bbdcc531768326124dc8972/tree_sitter-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01ea01a7003b88b92f7f875da6ba9d5d741e0c84bb1bd92c503c0eecd0ee6409", size = 575585 },
+ { url = "https://files.pythonhosted.org/packages/e3/3c/5f997ce34c0d1b744e0f0c0757113bdfc173a2e3dadda92c751685cfcbd1/tree_sitter-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:464fa5b2cac63608915a9de8a6efd67a4da1929e603ea86abaeae2cb1fe89921", size = 578203 },
+ { url = "https://files.pythonhosted.org/packages/d5/1f/f2bc7fa7c3081653ea4f2639e06ff0af4616c47105dbcc0746137da7620d/tree_sitter-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:3b1f3cbd9700e1fba0be2e7d801527e37c49fc02dc140714669144ef6ab58dce", size = 120147 },
+ { url = "https://files.pythonhosted.org/packages/c0/4c/9add771772c4d72a328e656367ca948e389432548696a3819b69cdd6f41e/tree_sitter-0.24.0-cp311-cp311-win_arm64.whl", hash = "sha256:f3f08a2ca9f600b3758792ba2406971665ffbad810847398d180c48cee174ee2", size = 108302 },
+ { url = "https://files.pythonhosted.org/packages/e9/57/3a590f287b5aa60c07d5545953912be3d252481bf5e178f750db75572bff/tree_sitter-0.24.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:14beeff5f11e223c37be7d5d119819880601a80d0399abe8c738ae2288804afc", size = 140788 },
+ { url = "https://files.pythonhosted.org/packages/61/0b/fc289e0cba7dbe77c6655a4dd949cd23c663fd62a8b4d8f02f97e28d7fe5/tree_sitter-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26a5b130f70d5925d67b47db314da209063664585a2fd36fa69e0717738efaf4", size = 133945 },
+ { url = "https://files.pythonhosted.org/packages/86/d7/80767238308a137e0b5b5c947aa243e3c1e3e430e6d0d5ae94b9a9ffd1a2/tree_sitter-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fc5c3c26d83c9d0ecb4fc4304fba35f034b7761d35286b936c1db1217558b4e", size = 564819 },
+ { url = "https://files.pythonhosted.org/packages/bf/b3/6c5574f4b937b836601f5fb556b24804b0a6341f2eb42f40c0e6464339f4/tree_sitter-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:772e1bd8c0931c866b848d0369b32218ac97c24b04790ec4b0e409901945dd8e", size = 579303 },
+ { url = "https://files.pythonhosted.org/packages/0a/f4/bd0ddf9abe242ea67cca18a64810f8af230fc1ea74b28bb702e838ccd874/tree_sitter-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:24a8dd03b0d6b8812425f3b84d2f4763322684e38baf74e5bb766128b5633dc7", size = 581054 },
+ { url = "https://files.pythonhosted.org/packages/8c/1c/ff23fa4931b6ef1bbeac461b904ca7e49eaec7e7e5398584e3eef836ec96/tree_sitter-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:f9e8b1605ab60ed43803100f067eed71b0b0e6c1fb9860a262727dbfbbb74751", size = 120221 },
+ { url = "https://files.pythonhosted.org/packages/b2/2a/9979c626f303177b7612a802237d0533155bf1e425ff6f73cc40f25453e2/tree_sitter-0.24.0-cp312-cp312-win_arm64.whl", hash = "sha256:f733a83d8355fc95561582b66bbea92ffd365c5d7a665bc9ebd25e049c2b2abb", size = 108234 },
+ { url = "https://files.pythonhosted.org/packages/61/cd/2348339c85803330ce38cee1c6cbbfa78a656b34ff58606ebaf5c9e83bd0/tree_sitter-0.24.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d4a6416ed421c4210f0ca405a4834d5ccfbb8ad6692d4d74f7773ef68f92071", size = 140781 },
+ { url = "https://files.pythonhosted.org/packages/8b/a3/1ea9d8b64e8dcfcc0051028a9c84a630301290995cd6e947bf88267ef7b1/tree_sitter-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e0992d483677e71d5c5d37f30dfb2e3afec2f932a9c53eec4fca13869b788c6c", size = 133928 },
+ { url = "https://files.pythonhosted.org/packages/fe/ae/55c1055609c9428a4aedf4b164400ab9adb0b1bf1538b51f4b3748a6c983/tree_sitter-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57277a12fbcefb1c8b206186068d456c600dbfbc3fd6c76968ee22614c5cd5ad", size = 564497 },
+ { url = "https://files.pythonhosted.org/packages/ce/d0/f2ffcd04882c5aa28d205a787353130cbf84b2b8a977fd211bdc3b399ae3/tree_sitter-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25fa22766d63f73716c6fec1a31ee5cf904aa429484256bd5fdf5259051ed74", size = 578917 },
+ { url = "https://files.pythonhosted.org/packages/af/82/aebe78ea23a2b3a79324993d4915f3093ad1af43d7c2208ee90be9273273/tree_sitter-0.24.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7d5d9537507e1c8c5fa9935b34f320bfec4114d675e028f3ad94f11cf9db37b9", size = 581148 },
+ { url = "https://files.pythonhosted.org/packages/a1/b4/6b0291a590c2b0417cfdb64ccb8ea242f270a46ed429c641fbc2bfab77e0/tree_sitter-0.24.0-cp313-cp313-win_amd64.whl", hash = "sha256:f58bb4956917715ec4d5a28681829a8dad5c342cafd4aea269f9132a83ca9b34", size = 120207 },
+ { url = "https://files.pythonhosted.org/packages/a8/18/542fd844b75272630229c9939b03f7db232c71a9d82aadc59c596319ea6a/tree_sitter-0.24.0-cp313-cp313-win_arm64.whl", hash = "sha256:23641bd25dcd4bb0b6fa91b8fb3f46cc9f1c9f475efe4d536d3f1f688d1b84c8", size = 108232 },
+]
+
+[[package]]
+name = "tree-sitter-c-sharp"
+version = "0.23.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/22/85/a61c782afbb706a47d990eaee6977e7c2bd013771c5bf5c81c617684f286/tree_sitter_c_sharp-0.23.1.tar.gz", hash = "sha256:322e2cfd3a547a840375276b2aea3335fa6458aeac082f6c60fec3f745c967eb", size = 1317728 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/58/04/f6c2df4c53a588ccd88d50851155945cff8cd887bd70c175e00aaade7edf/tree_sitter_c_sharp-0.23.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2b612a6e5bd17bb7fa2aab4bb6fc1fba45c94f09cb034ab332e45603b86e32fd", size = 372235 },
+ { url = "https://files.pythonhosted.org/packages/99/10/1aa9486f1e28fc22810fa92cbdc54e1051e7f5536a5e5b5e9695f609b31e/tree_sitter_c_sharp-0.23.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a8b98f62bc53efcd4d971151950c9b9cd5cbe3bacdb0cd69fdccac63350d83e", size = 419046 },
+ { url = "https://files.pythonhosted.org/packages/0f/21/13df29f8fcb9ba9f209b7b413a4764b673dfd58989a0dd67e9c7e19e9c2e/tree_sitter_c_sharp-0.23.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:986e93d845a438ec3c4416401aa98e6a6f6631d644bbbc2e43fcb915c51d255d", size = 415999 },
+ { url = "https://files.pythonhosted.org/packages/ca/72/fc6846795bcdae2f8aa94cc8b1d1af33d634e08be63e294ff0d6794b1efc/tree_sitter_c_sharp-0.23.1-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8024e466b2f5611c6dc90321f232d8584893c7fb88b75e4a831992f877616d2", size = 402830 },
+ { url = "https://files.pythonhosted.org/packages/fe/3a/b6028c5890ce6653807d5fa88c72232c027c6ceb480dbeb3b186d60e5971/tree_sitter_c_sharp-0.23.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7f9bf876866835492281d336b9e1f9626ab668737f74e914c31d285261507da7", size = 397880 },
+ { url = "https://files.pythonhosted.org/packages/47/d2/4facaa34b40f8104d8751746d0e1cd2ddf0beb9f1404b736b97f372bd1f3/tree_sitter_c_sharp-0.23.1-cp39-abi3-win_amd64.whl", hash = "sha256:ae9a9e859e8f44e2b07578d44f9a220d3fa25b688966708af6aa55d42abeebb3", size = 377562 },
+ { url = "https://files.pythonhosted.org/packages/d8/88/3cf6bd9959d94d1fec1e6a9c530c5f08ff4115a474f62aedb5fedb0f7241/tree_sitter_c_sharp-0.23.1-cp39-abi3-win_arm64.whl", hash = "sha256:c81548347a93347be4f48cb63ec7d60ef4b0efa91313330e69641e49aa5a08c5", size = 375157 },
+]
+
+[[package]]
+name = "tree-sitter-embedded-template"
+version = "0.23.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/28/d6/5a58ea2f0480f5ed188b733114a8c275532a2fd1568b3898793b13d28af5/tree_sitter_embedded_template-0.23.2.tar.gz", hash = "sha256:7b24dcf2e92497f54323e617564d36866230a8bfb719dbb7b45b461510dcddaa", size = 8471 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ef/c1/be0c48ed9609b720e74ade86f24ea086e353fe9c7405ee9630c3d52d09a2/tree_sitter_embedded_template-0.23.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a505c2d2494464029d79db541cab52f6da5fb326bf3d355e69bf98b84eb89ae0", size = 9554 },
+ { url = "https://files.pythonhosted.org/packages/6d/a5/7c12f5d302525ee36d1eafc28a68e4454da5bad208436d547326bee4ed76/tree_sitter_embedded_template-0.23.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:28028b93b42cc3753261ae7ce066675d407f59de512417524f9c3ab7792b1d37", size = 10051 },
+ { url = "https://files.pythonhosted.org/packages/cd/87/95aaba8b64b849200bd7d4ae510cc394ecaef46a031499cbff301766970d/tree_sitter_embedded_template-0.23.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec399d59ce93ffb60759a2d96053eed529f3c3f6a27128f261710d0d0de60e10", size = 17532 },
+ { url = "https://files.pythonhosted.org/packages/13/f8/8c837b898f00b35f9f3f76a4abc525e80866a69343083c9ff329e17ecb03/tree_sitter_embedded_template-0.23.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcfa01f62b88d50dbcb736cc23baec8ddbfe08daacfdc613eee8c04ab65efd09", size = 17394 },
+ { url = "https://files.pythonhosted.org/packages/89/9b/893adf9e465d2d7f14870871bf2f3b30045e5ac417cb596f667a72eda493/tree_sitter_embedded_template-0.23.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6debd24791466f887109a433c31aa4a5deeba2b217817521c745a4e748a944ed", size = 16439 },
+ { url = "https://files.pythonhosted.org/packages/40/96/e79934572723673db9f867000500c6eea61a37705e02c7aee9ee031bbb6f/tree_sitter_embedded_template-0.23.2-cp39-abi3-win_amd64.whl", hash = "sha256:158fecb38be5b15db0190ef7238e5248f24bf32ae3cab93bc1197e293a5641eb", size = 12572 },
+ { url = "https://files.pythonhosted.org/packages/63/06/27f678b9874e4e2e39ddc6f5cce3374c8c60e6046ea8588a491ab6fc9fcb/tree_sitter_embedded_template-0.23.2-cp39-abi3-win_arm64.whl", hash = "sha256:9f1f3b79fe273f3d15a5b64c85fc6ebfb48decfbe8542accd05f5b7694860df0", size = 11232 },
+]
+
+[[package]]
+name = "tree-sitter-language-pack"
+version = "0.8.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "tree-sitter" },
+ { name = "tree-sitter-c-sharp" },
+ { name = "tree-sitter-embedded-template" },
+ { name = "tree-sitter-yaml" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/93/b7/1272925d5cccd0c7a79df85fdc1a728a9cd9536adca10c473a86ea6a1022/tree_sitter_language_pack-0.8.0.tar.gz", hash = "sha256:49aafe322eb59ef4d4457577210fb20c18c5535b1a42b8e753aa699ed3bf9eed", size = 43693098 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2e/44/f7d3c4c5e075de1b3ad9e7d006f2057d65d39d5a573d6ee72b1a7f3f6cd1/tree_sitter_language_pack-0.8.0-cp39-abi3-macosx_10_13_universal2.whl", hash = "sha256:7ab5dd0e4383bd0c845c153f65da62df035591fc79759a5f6efd5b27aaa551c5", size = 28609869 },
+ { url = "https://files.pythonhosted.org/packages/bf/24/86f32fae7eaaf829cfd0013f8173fb0f3e75f6e0a8bc58bd165c821e17de/tree_sitter_language_pack-0.8.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:1757c04af8350ffdfd5509951fb7874dc1947604d6d9f16a2f88a0cd4fcc54cb", size = 17871704 },
+ { url = "https://files.pythonhosted.org/packages/00/7d/9356ecb8d5fcc16e39154821226d0dc3662393b9f46326f539e3e71dc384/tree_sitter_language_pack-0.8.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:81aac45ddde6c7e9ac222d0157af03648b1382d4de3af321d1b913af96b796f0", size = 17729371 },
+ { url = "https://files.pythonhosted.org/packages/19/49/cfe141b0be9e08aeb9e20f3a182e58b7af12a28f46949403005e5483afc6/tree_sitter_language_pack-0.8.0-cp39-abi3-win_amd64.whl", hash = "sha256:e870a3cc067352b249393e887710dae4918c6454f7fd41e43108f3621a5f41f8", size = 14552212 },
+]
+
+[[package]]
+name = "tree-sitter-yaml"
+version = "0.7.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/0b/d0/97899f366e3d982ad92dd83faa2b1dd0060e5db99990e0d7f660902493f8/tree_sitter_yaml-0.7.1.tar.gz", hash = "sha256:2cea5f8d4ca4d10439bd7d9e458c61b330cb33cf7a92e4ef1d428e10e1ab7e2c", size = 91533 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3f/7e/83a40de4315b8f9975d3fd562071bda8fa1dfc088b3359d048003f174fd0/tree_sitter_yaml-0.7.1-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0256632914d6eb21819f21a85bab649505496ac01fac940eb08a410669346822", size = 43788 },
+ { url = "https://files.pythonhosted.org/packages/ca/05/760b38e31f9ca1e8667cf82a07119956dcb865728f7d777a22f5ddf296c6/tree_sitter_yaml-0.7.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:bf9dd2649392e1f28a20f920f49acd9398cfb872876e338aa84562f8f868dc4d", size = 45001 },
+ { url = "https://files.pythonhosted.org/packages/88/e9/6d8d502eeb96fb363c1ac926ac456afc55019836fc675263fd23754dfdc6/tree_sitter_yaml-0.7.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94eb8fcb1ac8e43f7da47e63880b6f283524460153f08420a167c1721e42b08a", size = 93852 },
+ { url = "https://files.pythonhosted.org/packages/85/ef/b84bc6aaaa08022b4cc1d36212e837ce051306d50dd62993ffc21c9bf4ab/tree_sitter_yaml-0.7.1-cp310-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30410089828ebdece9abf3aa16b2e172b84cf2fd90a2b7d8022f6ed8cde90ecb", size = 92125 },
+ { url = "https://files.pythonhosted.org/packages/16/0c/5caa26da012c93da1eadf66c6babb1b1e2e8dd4434668c7232739df87e46/tree_sitter_yaml-0.7.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:219af34f4b35b5c16f25426cc3f90cf725fbba17c9592f78504086e67787be09", size = 90443 },
+ { url = "https://files.pythonhosted.org/packages/92/25/a14297ea2a575bc3c19fcf58a5983a926ad732c32af23a346d7fa0563d8d/tree_sitter_yaml-0.7.1-cp310-abi3-win_amd64.whl", hash = "sha256:550645223d68b7d6b4cfedf4972754724e64d369ec321fa33f57d3ca54cafc7c", size = 45517 },
+ { url = "https://files.pythonhosted.org/packages/62/fa/b25e688df5b4e024bc3627bc3f951524ef9c8b0756f0646411efa5063a10/tree_sitter_yaml-0.7.1-cp310-abi3-win_arm64.whl", hash = "sha256:298ade69ad61f76bb3e50ced809650ec30521a51aa2708166b176419ccb0a6ba", size = 43801 },
+]
+
[[package]]
name = "typer"
version = "0.16.0"
@@ -3093,79 +3106,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498 },
]
-[[package]]
-name = "xxhash"
-version = "3.5.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/00/5e/d6e5258d69df8b4ed8c83b6664f2b47d30d2dec551a29ad72a6c69eafd31/xxhash-3.5.0.tar.gz", hash = "sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f", size = 84241 }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/bb/8a/0e9feca390d512d293afd844d31670e25608c4a901e10202aa98785eab09/xxhash-3.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ece616532c499ee9afbb83078b1b952beffef121d989841f7f4b3dc5ac0fd212", size = 31970 },
- { url = "https://files.pythonhosted.org/packages/16/e6/be5aa49580cd064a18200ab78e29b88b1127e1a8c7955eb8ecf81f2626eb/xxhash-3.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3171f693dbc2cef6477054a665dc255d996646b4023fe56cb4db80e26f4cc520", size = 30801 },
- { url = "https://files.pythonhosted.org/packages/20/ee/b8a99ebbc6d1113b3a3f09e747fa318c3cde5b04bd9c197688fadf0eeae8/xxhash-3.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5d3e570ef46adaf93fc81b44aca6002b5a4d8ca11bd0580c07eac537f36680", size = 220927 },
- { url = "https://files.pythonhosted.org/packages/58/62/15d10582ef159283a5c2b47f6d799fc3303fe3911d5bb0bcc820e1ef7ff4/xxhash-3.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7cb29a034301e2982df8b1fe6328a84f4b676106a13e9135a0d7e0c3e9f806da", size = 200360 },
- { url = "https://files.pythonhosted.org/packages/23/41/61202663ea9b1bd8e53673b8ec9e2619989353dba8cfb68e59a9cbd9ffe3/xxhash-3.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d0d307d27099bb0cbeea7260eb39ed4fdb99c5542e21e94bb6fd29e49c57a23", size = 428528 },
- { url = "https://files.pythonhosted.org/packages/f2/07/d9a3059f702dec5b3b703737afb6dda32f304f6e9da181a229dafd052c29/xxhash-3.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0342aafd421795d740e514bc9858ebddfc705a75a8c5046ac56d85fe97bf196", size = 194149 },
- { url = "https://files.pythonhosted.org/packages/eb/58/27caadf78226ecf1d62dbd0c01d152ed381c14c1ee4ad01f0d460fc40eac/xxhash-3.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dbbd9892c5ebffeca1ed620cf0ade13eb55a0d8c84e0751a6653adc6ac40d0c", size = 207703 },
- { url = "https://files.pythonhosted.org/packages/b1/08/32d558ce23e1e068453c39aed7b3c1cdc690c177873ec0ca3a90d5808765/xxhash-3.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4cc2d67fdb4d057730c75a64c5923abfa17775ae234a71b0200346bfb0a7f482", size = 216255 },
- { url = "https://files.pythonhosted.org/packages/3f/d4/2b971e2d2b0a61045f842b622ef11e94096cf1f12cd448b6fd426e80e0e2/xxhash-3.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ec28adb204b759306a3d64358a5e5c07d7b1dd0ccbce04aa76cb9377b7b70296", size = 202744 },
- { url = "https://files.pythonhosted.org/packages/19/ae/6a6438864a8c4c39915d7b65effd85392ebe22710412902487e51769146d/xxhash-3.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1328f6d8cca2b86acb14104e381225a3d7b42c92c4b86ceae814e5c400dbb415", size = 210115 },
- { url = "https://files.pythonhosted.org/packages/48/7d/b3c27c27d1fc868094d02fe4498ccce8cec9fcc591825c01d6bcb0b4fc49/xxhash-3.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8d47ebd9f5d9607fd039c1fbf4994e3b071ea23eff42f4ecef246ab2b7334198", size = 414247 },
- { url = "https://files.pythonhosted.org/packages/a1/05/918f9e7d2fbbd334b829997045d341d6239b563c44e683b9a7ef8fe50f5d/xxhash-3.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b96d559e0fcddd3343c510a0fe2b127fbff16bf346dd76280b82292567523442", size = 191419 },
- { url = "https://files.pythonhosted.org/packages/08/29/dfe393805b2f86bfc47c290b275f0b7c189dc2f4e136fd4754f32eb18a8d/xxhash-3.5.0-cp310-cp310-win32.whl", hash = "sha256:61c722ed8d49ac9bc26c7071eeaa1f6ff24053d553146d5df031802deffd03da", size = 30114 },
- { url = "https://files.pythonhosted.org/packages/7b/d7/aa0b22c4ebb7c3ccb993d4c565132abc641cd11164f8952d89eb6a501909/xxhash-3.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:9bed5144c6923cc902cd14bb8963f2d5e034def4486ab0bbe1f58f03f042f9a9", size = 30003 },
- { url = "https://files.pythonhosted.org/packages/69/12/f969b81541ee91b55f1ce469d7ab55079593c80d04fd01691b550e535000/xxhash-3.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:893074d651cf25c1cc14e3bea4fceefd67f2921b1bb8e40fcfeba56820de80c6", size = 26773 },
- { url = "https://files.pythonhosted.org/packages/b8/c7/afed0f131fbda960ff15eee7f304fa0eeb2d58770fade99897984852ef23/xxhash-3.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02c2e816896dc6f85922ced60097bcf6f008dedfc5073dcba32f9c8dd786f3c1", size = 31969 },
- { url = "https://files.pythonhosted.org/packages/8c/0c/7c3bc6d87e5235672fcc2fb42fd5ad79fe1033925f71bf549ee068c7d1ca/xxhash-3.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6027dcd885e21581e46d3c7f682cfb2b870942feeed58a21c29583512c3f09f8", size = 30800 },
- { url = "https://files.pythonhosted.org/packages/04/9e/01067981d98069eec1c20201f8c145367698e9056f8bc295346e4ea32dd1/xxhash-3.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1308fa542bbdbf2fa85e9e66b1077eea3a88bef38ee8a06270b4298a7a62a166", size = 221566 },
- { url = "https://files.pythonhosted.org/packages/d4/09/d4996de4059c3ce5342b6e1e6a77c9d6c91acce31f6ed979891872dd162b/xxhash-3.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c28b2fdcee797e1c1961cd3bcd3d545cab22ad202c846235197935e1df2f8ef7", size = 201214 },
- { url = "https://files.pythonhosted.org/packages/62/f5/6d2dc9f8d55a7ce0f5e7bfef916e67536f01b85d32a9fbf137d4cadbee38/xxhash-3.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:924361811732ddad75ff23e90efd9ccfda4f664132feecb90895bade6a1b4623", size = 429433 },
- { url = "https://files.pythonhosted.org/packages/d9/72/9256303f10e41ab004799a4aa74b80b3c5977d6383ae4550548b24bd1971/xxhash-3.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89997aa1c4b6a5b1e5b588979d1da048a3c6f15e55c11d117a56b75c84531f5a", size = 194822 },
- { url = "https://files.pythonhosted.org/packages/34/92/1a3a29acd08248a34b0e6a94f4e0ed9b8379a4ff471f1668e4dce7bdbaa8/xxhash-3.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:685c4f4e8c59837de103344eb1c8a3851f670309eb5c361f746805c5471b8c88", size = 208538 },
- { url = "https://files.pythonhosted.org/packages/53/ad/7fa1a109663366de42f724a1cdb8e796a260dbac45047bce153bc1e18abf/xxhash-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbd2ecfbfee70bc1a4acb7461fa6af7748ec2ab08ac0fa298f281c51518f982c", size = 216953 },
- { url = "https://files.pythonhosted.org/packages/35/02/137300e24203bf2b2a49b48ce898ecce6fd01789c0fcd9c686c0a002d129/xxhash-3.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:25b5a51dc3dfb20a10833c8eee25903fd2e14059e9afcd329c9da20609a307b2", size = 203594 },
- { url = "https://files.pythonhosted.org/packages/23/03/aeceb273933d7eee248c4322b98b8e971f06cc3880e5f7602c94e5578af5/xxhash-3.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a8fb786fb754ef6ff8c120cb96629fb518f8eb5a61a16aac3a979a9dbd40a084", size = 210971 },
- { url = "https://files.pythonhosted.org/packages/e3/64/ed82ec09489474cbb35c716b189ddc1521d8b3de12b1b5ab41ce7f70253c/xxhash-3.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a905ad00ad1e1c34fe4e9d7c1d949ab09c6fa90c919860c1534ff479f40fd12d", size = 415050 },
- { url = "https://files.pythonhosted.org/packages/71/43/6db4c02dcb488ad4e03bc86d70506c3d40a384ee73c9b5c93338eb1f3c23/xxhash-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:963be41bcd49f53af6d795f65c0da9b4cc518c0dd9c47145c98f61cb464f4839", size = 192216 },
- { url = "https://files.pythonhosted.org/packages/22/6d/db4abec29e7a567455344433d095fdb39c97db6955bb4a2c432e486b4d28/xxhash-3.5.0-cp311-cp311-win32.whl", hash = "sha256:109b436096d0a2dd039c355fa3414160ec4d843dfecc64a14077332a00aeb7da", size = 30120 },
- { url = "https://files.pythonhosted.org/packages/52/1c/fa3b61c0cf03e1da4767213672efe186b1dfa4fc901a4a694fb184a513d1/xxhash-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:b702f806693201ad6c0a05ddbbe4c8f359626d0b3305f766077d51388a6bac58", size = 30003 },
- { url = "https://files.pythonhosted.org/packages/6b/8e/9e6fc572acf6e1cc7ccb01973c213f895cb8668a9d4c2b58a99350da14b7/xxhash-3.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:c4dcb4120d0cc3cc448624147dba64e9021b278c63e34a38789b688fd0da9bf3", size = 26777 },
- { url = "https://files.pythonhosted.org/packages/07/0e/1bfce2502c57d7e2e787600b31c83535af83746885aa1a5f153d8c8059d6/xxhash-3.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:14470ace8bd3b5d51318782cd94e6f94431974f16cb3b8dc15d52f3b69df8e00", size = 31969 },
- { url = "https://files.pythonhosted.org/packages/3f/d6/8ca450d6fe5b71ce521b4e5db69622383d039e2b253e9b2f24f93265b52c/xxhash-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59aa1203de1cb96dbeab595ded0ad0c0056bb2245ae11fac11c0ceea861382b9", size = 30787 },
- { url = "https://files.pythonhosted.org/packages/5b/84/de7c89bc6ef63d750159086a6ada6416cc4349eab23f76ab870407178b93/xxhash-3.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08424f6648526076e28fae6ea2806c0a7d504b9ef05ae61d196d571e5c879c84", size = 220959 },
- { url = "https://files.pythonhosted.org/packages/fe/86/51258d3e8a8545ff26468c977101964c14d56a8a37f5835bc0082426c672/xxhash-3.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61a1ff00674879725b194695e17f23d3248998b843eb5e933007ca743310f793", size = 200006 },
- { url = "https://files.pythonhosted.org/packages/02/0a/96973bd325412feccf23cf3680fd2246aebf4b789122f938d5557c54a6b2/xxhash-3.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2f2c61bee5844d41c3eb015ac652a0229e901074951ae48581d58bfb2ba01be", size = 428326 },
- { url = "https://files.pythonhosted.org/packages/11/a7/81dba5010f7e733de88af9555725146fc133be97ce36533867f4c7e75066/xxhash-3.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d32a592cac88d18cc09a89172e1c32d7f2a6e516c3dfde1b9adb90ab5df54a6", size = 194380 },
- { url = "https://files.pythonhosted.org/packages/fb/7d/f29006ab398a173f4501c0e4977ba288f1c621d878ec217b4ff516810c04/xxhash-3.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70dabf941dede727cca579e8c205e61121afc9b28516752fd65724be1355cc90", size = 207934 },
- { url = "https://files.pythonhosted.org/packages/8a/6e/6e88b8f24612510e73d4d70d9b0c7dff62a2e78451b9f0d042a5462c8d03/xxhash-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5d0ddaca65ecca9c10dcf01730165fd858533d0be84c75c327487c37a906a27", size = 216301 },
- { url = "https://files.pythonhosted.org/packages/af/51/7862f4fa4b75a25c3b4163c8a873f070532fe5f2d3f9b3fc869c8337a398/xxhash-3.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e5b5e16c5a480fe5f59f56c30abdeba09ffd75da8d13f6b9b6fd224d0b4d0a2", size = 203351 },
- { url = "https://files.pythonhosted.org/packages/22/61/8d6a40f288f791cf79ed5bb113159abf0c81d6efb86e734334f698eb4c59/xxhash-3.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149b7914451eb154b3dfaa721315117ea1dac2cc55a01bfbd4df7c68c5dd683d", size = 210294 },
- { url = "https://files.pythonhosted.org/packages/17/02/215c4698955762d45a8158117190261b2dbefe9ae7e5b906768c09d8bc74/xxhash-3.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:eade977f5c96c677035ff39c56ac74d851b1cca7d607ab3d8f23c6b859379cab", size = 414674 },
- { url = "https://files.pythonhosted.org/packages/31/5c/b7a8db8a3237cff3d535261325d95de509f6a8ae439a5a7a4ffcff478189/xxhash-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa9f547bd98f5553d03160967866a71056a60960be00356a15ecc44efb40ba8e", size = 192022 },
- { url = "https://files.pythonhosted.org/packages/78/e3/dd76659b2811b3fd06892a8beb850e1996b63e9235af5a86ea348f053e9e/xxhash-3.5.0-cp312-cp312-win32.whl", hash = "sha256:f7b58d1fd3551b8c80a971199543379be1cee3d0d409e1f6d8b01c1a2eebf1f8", size = 30170 },
- { url = "https://files.pythonhosted.org/packages/d9/6b/1c443fe6cfeb4ad1dcf231cdec96eb94fb43d6498b4469ed8b51f8b59a37/xxhash-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:fa0cafd3a2af231b4e113fba24a65d7922af91aeb23774a8b78228e6cd785e3e", size = 30040 },
- { url = "https://files.pythonhosted.org/packages/0f/eb/04405305f290173acc0350eba6d2f1a794b57925df0398861a20fbafa415/xxhash-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:586886c7e89cb9828bcd8a5686b12e161368e0064d040e225e72607b43858ba2", size = 26796 },
- { url = "https://files.pythonhosted.org/packages/c9/b8/e4b3ad92d249be5c83fa72916c9091b0965cb0faeff05d9a0a3870ae6bff/xxhash-3.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37889a0d13b0b7d739cfc128b1c902f04e32de17b33d74b637ad42f1c55101f6", size = 31795 },
- { url = "https://files.pythonhosted.org/packages/fc/d8/b3627a0aebfbfa4c12a41e22af3742cf08c8ea84f5cc3367b5de2d039cce/xxhash-3.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97a662338797c660178e682f3bc180277b9569a59abfb5925e8620fba00b9fc5", size = 30792 },
- { url = "https://files.pythonhosted.org/packages/c3/cc/762312960691da989c7cd0545cb120ba2a4148741c6ba458aa723c00a3f8/xxhash-3.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f85e0108d51092bdda90672476c7d909c04ada6923c14ff9d913c4f7dc8a3bc", size = 220950 },
- { url = "https://files.pythonhosted.org/packages/fe/e9/cc266f1042c3c13750e86a535496b58beb12bf8c50a915c336136f6168dc/xxhash-3.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2fd827b0ba763ac919440042302315c564fdb797294d86e8cdd4578e3bc7f3", size = 199980 },
- { url = "https://files.pythonhosted.org/packages/bf/85/a836cd0dc5cc20376de26b346858d0ac9656f8f730998ca4324921a010b9/xxhash-3.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82085c2abec437abebf457c1d12fccb30cc8b3774a0814872511f0f0562c768c", size = 428324 },
- { url = "https://files.pythonhosted.org/packages/b4/0e/15c243775342ce840b9ba34aceace06a1148fa1630cd8ca269e3223987f5/xxhash-3.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07fda5de378626e502b42b311b049848c2ef38784d0d67b6f30bb5008642f8eb", size = 194370 },
- { url = "https://files.pythonhosted.org/packages/87/a1/b028bb02636dfdc190da01951d0703b3d904301ed0ef6094d948983bef0e/xxhash-3.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c279f0d2b34ef15f922b77966640ade58b4ccdfef1c4d94b20f2a364617a493f", size = 207911 },
- { url = "https://files.pythonhosted.org/packages/80/d5/73c73b03fc0ac73dacf069fdf6036c9abad82de0a47549e9912c955ab449/xxhash-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:89e66ceed67b213dec5a773e2f7a9e8c58f64daeb38c7859d8815d2c89f39ad7", size = 216352 },
- { url = "https://files.pythonhosted.org/packages/b6/2a/5043dba5ddbe35b4fe6ea0a111280ad9c3d4ba477dd0f2d1fe1129bda9d0/xxhash-3.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bcd51708a633410737111e998ceb3b45d3dbc98c0931f743d9bb0a209033a326", size = 203410 },
- { url = "https://files.pythonhosted.org/packages/a2/b2/9a8ded888b7b190aed75b484eb5c853ddd48aa2896e7b59bbfbce442f0a1/xxhash-3.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ff2c0a34eae7df88c868be53a8dd56fbdf592109e21d4bfa092a27b0bf4a7bf", size = 210322 },
- { url = "https://files.pythonhosted.org/packages/98/62/440083fafbc917bf3e4b67c2ade621920dd905517e85631c10aac955c1d2/xxhash-3.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e28503dccc7d32e0b9817aa0cbfc1f45f563b2c995b7a66c4c8a0d232e840c7", size = 414725 },
- { url = "https://files.pythonhosted.org/packages/75/db/009206f7076ad60a517e016bb0058381d96a007ce3f79fa91d3010f49cc2/xxhash-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a6c50017518329ed65a9e4829154626f008916d36295b6a3ba336e2458824c8c", size = 192070 },
- { url = "https://files.pythonhosted.org/packages/1f/6d/c61e0668943a034abc3a569cdc5aeae37d686d9da7e39cf2ed621d533e36/xxhash-3.5.0-cp313-cp313-win32.whl", hash = "sha256:53a068fe70301ec30d868ece566ac90d873e3bb059cf83c32e76012c889b8637", size = 30172 },
- { url = "https://files.pythonhosted.org/packages/96/14/8416dce965f35e3d24722cdf79361ae154fa23e2ab730e5323aa98d7919e/xxhash-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:80babcc30e7a1a484eab952d76a4f4673ff601f54d5142c26826502740e70b43", size = 30041 },
- { url = "https://files.pythonhosted.org/packages/27/ee/518b72faa2073f5aa8e3262408d284892cb79cf2754ba0c3a5870645ef73/xxhash-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:4811336f1ce11cac89dcbd18f3a25c527c16311709a89313c3acaf771def2d4b", size = 26801 },
- { url = "https://files.pythonhosted.org/packages/ab/9a/233606bada5bd6f50b2b72c45de3d9868ad551e83893d2ac86dc7bb8553a/xxhash-3.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2014c5b3ff15e64feecb6b713af12093f75b7926049e26a580e94dcad3c73d8c", size = 29732 },
- { url = "https://files.pythonhosted.org/packages/0c/67/f75276ca39e2c6604e3bee6c84e9db8a56a4973fde9bf35989787cf6e8aa/xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fab81ef75003eda96239a23eda4e4543cedc22e34c373edcaf744e721a163986", size = 36214 },
- { url = "https://files.pythonhosted.org/packages/0f/f8/f6c61fd794229cc3848d144f73754a0c107854372d7261419dcbbd286299/xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e2febf914ace002132aa09169cc572e0d8959d0f305f93d5828c4836f9bc5a6", size = 32020 },
- { url = "https://files.pythonhosted.org/packages/79/d3/c029c99801526f859e6b38d34ab87c08993bf3dcea34b11275775001638a/xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d3a10609c51da2a1c0ea0293fc3968ca0a18bd73838455b5bca3069d7f8e32b", size = 40515 },
- { url = "https://files.pythonhosted.org/packages/62/e3/bef7b82c1997579c94de9ac5ea7626d01ae5858aa22bf4fcb38bf220cb3e/xxhash-3.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5a74f23335b9689b66eb6dbe2a931a88fcd7a4c2cc4b1cb0edba8ce381c7a1da", size = 30064 },
-]
-
[[package]]
name = "yarl"
version = "1.20.1"