diff --git a/IDEAS.mk b/IDEAS.mk index 51ea28d..4256fe4 100644 --- a/IDEAS.mk +++ b/IDEAS.mk @@ -21,6 +21,7 @@ endif RUSTFLAGS ?= -Awarnings## Ignore Rust compiler warnings CARGO_NET_OFFLINE ?= true## Cargo offline mode CFLAGS ?= -w## Ignore C compiler warnings +export EXTRACT_INFO_CMAKE CFLAGS GIT = git -C ${TRANSLATION_DIR} @@ -34,42 +35,17 @@ endif # cmake -cmake: build-ninja/build.log - -.PRECIOUS: build-ninja/CMakeCache.txt -build-ninja/CMakeCache.txt: test_case/CMakeLists.txt ${EXTRACT_INFO_CMAKE} - @rm -rf build-ninja -ifeq ($(wildcard CMakePresets.json),) - cmake -S test_case -B build-ninja -G Ninja \ - -DCMAKE_BUILD_TYPE=Debug \ - -DCMAKE_C_FLAGS_DEBUG="-g -O0" \ - -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES="${EXTRACT_INFO_CMAKE}" \ - -DCMAKE_C_FLAGS="${CFLAGS}" \ - -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -else - cmake -S . --preset test \ - -DCMAKE_BUILD_TYPE=Debug \ - -DCMAKE_C_FLAGS_DEBUG="-g -O0" \ - -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES="${EXTRACT_INFO_CMAKE}" \ - -DCMAKE_C_FLAGS="${CFLAGS}" \ - -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -endif - -.PRECIOUS: build-ninja/compile_commands.json -build-ninja/compile_commands.json: build-ninja/CMakeCache.txt ; +.PHONY: cmake +cmake: build-ninja/cmake.log -.PRECIOUS: build-ninja/build.log -build-ninja/build.log: build-ninja/CMakeCache.txt -ifeq ($(wildcard CMakePresets.json),) - -cmake --build build-ninja --target all 2> $@ -else - -cmake --build build-ninja --target all --preset test 2> $@ -endif - @find build-ninja -maxdepth 1 -type f -executable | \ - xargs -I{} sh -c "nm --extern-only {} | \ - awk '{if (\$$2 == \"T\") print \$$NF}' | \ - grep -v ^_ > {}.symbols" +build-ninja/cmake.log: test_case/CMakeLists.txt ${EXTRACT_INFO_CMAKE} + uv run python -m ideas.cmake source_dir=test_case \ + build_dir=build-ninja + @touch $@ +build-ninja/CMakeCache.txt: build-ninja/cmake.log +build-ninja/compile_commands.json: build-ninja/cmake.log +build-ninja/build.log: build-ninja/cmake.log # init .PHONY: init @@ -87,7 +63,7 @@ ${TRANSLATION_DIR}/.git/config: ${GIT} commit --quiet --all --message "Initial commit" .PRECIOUS: ${TRANSLATION_DIR}/Cargo.toml -${TRANSLATION_DIR}/Cargo.toml: ${TRANSLATION_DIR}/.git/config +${TRANSLATION_DIR}/Cargo.toml: | ${TRANSLATION_DIR}/.git/config echo -n "[workspace]\nresolver = \"3\"" > $@ ${GIT} add Cargo.toml ${GIT} commit --quiet --all --message "Created cargo workspace" @@ -180,16 +156,16 @@ ${TRANSLATION_DIR}/cargo_test.log: ${TRANSLATION_DIR}/build.log $(patsubst %,${T .PRECIOUS: ${TRANSLATION_DIR}/%/cargo_test.log ${TRANSLATION_DIR}/%/cargo_test.log: ${TRANSLATION_DIR}/%/build.log ${TRANSLATION_DIR}/%/tests/test_cases.rs if [ $$(stat -c %s ${TRANSLATION_DIR}/$*/build.log) = 0 ]; then \ - cargo test --manifest-path ${TRANSLATION_DIR}/$*/Cargo.toml --test test_cases | tee $@ ; \ -else \ - find test_vectors -name '*.json' -exec echo "test {} ... FAILED" \; | tee $@ ; \ -fi \ + cargo test --manifest-path ${TRANSLATION_DIR}/$*/Cargo.toml --test test_cases | tee $@ ; \ + else \ + find test_vectors -name '*.json' -exec echo "test {} ... FAILED" \; | tee $@ ; \ + fi \ .PRECIOUS: ${TRANSLATION_DIR}/%/tests/test_cases.rs ${TRANSLATION_DIR}/%/tests/test_cases.rs: | ${TEST_FILES} ${TRANSLATION_DIR}/%/Cargo.toml build-ninja/%.type @mkdir -p $(@D) - cargo add --quiet --manifest-path ${TRANSLATION_DIR}/$*/Cargo.toml --dev assert_cmd@2.0.17 ntest@0.9.3 predicates@3.1.3 - -uv run python -m ideas.convert_tests ${TEST_FILES} --crate_manifest $(realpath ${TRANSLATION_DIR}/$*/Cargo.toml) | rustfmt > $@ + -uv run python -m ideas.convert_tests --crate_manifest $(realpath ${TRANSLATION_DIR}/$*/Cargo.toml) \ + ${TEST_FILES} | rustfmt > $@ ${GIT} add $*/Cargo.toml $*/tests/test_cases.rs ${GIT} commit --quiet --message "Converted \`$*\` test vectors" diff --git a/pyproject.toml b/pyproject.toml index 3072390..516fc2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,11 +8,10 @@ requires-python = "~=3.14.0" dependencies = [ "clang==21.1.7", - "dspy==3.1.0", + "dspy==3.1.2", "hydra-core", - "tree-sitter==0.24.0", - "tree-sitter-c==0.23.4", - "tree-sitter-rust==0.23.2", + "tree-sitter==0.25.2", + "tree-sitter-rust==0.24.0", ] [dependency-groups] diff --git a/src/ideas/adapters.py b/src/ideas/adapters.py new file mode 100644 index 0000000..89475df --- /dev/null +++ b/src/ideas/adapters.py @@ -0,0 +1,45 @@ +# +# Copyright (C) 2026 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# + +from unittest.mock import patch + +from pydantic.fields import FieldInfo + +import dspy +import dspy.adapters.chat_adapter +from dspy.adapters.chat_adapter import ChatAdapter as _ChatAdapter +from dspy.adapters.utils import translate_field_type as _translate_field_type +from dspy.signatures.utils import get_dspy_field_type + + +class Code(dspy.Code): + def format(self): + return f"```{self.language.lower()}\n{self.code.rstrip()}\n```" + + @classmethod + def short_description(cls): + return f"must be {cls.__name__}" + + +class ChatAdapter(_ChatAdapter): + def format_field_structure(self, signature: type[dspy.Signature]) -> str: + with patch.object( + dspy.adapters.chat_adapter, "translate_field_type", translate_field_type + ): + return super().format_field_structure(signature) + + +def translate_field_type(field_name: str, field_info: FieldInfo) -> str: + # If a non-input field has a short_description, then use that. + field_type = field_info.annotation + if not field_type: + raise RuntimeError(f"Field '{field_name}' is missing a type annotation") + + if hasattr(field_type, "short_description") and get_dspy_field_type(field_info) != "input": + desc = field_type.short_description() + desc = (" " * 8) + f"# note: the value you produce {desc}" if desc else "" + return f"{{{field_name}}}{desc}" + return _translate_field_type(field_name, field_info) diff --git a/src/ideas/ast_rust.py b/src/ideas/ast_rust.py new file mode 100644 index 0000000..bbfc91f --- /dev/null +++ b/src/ideas/ast_rust.py @@ -0,0 +1,143 @@ +# +# Copyright (C) 2026 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# + +from collections import OrderedDict + +from tree_sitter import Language, Parser, Node, Query, QueryCursor +import tree_sitter_rust + +# Initialize the Rust language once +RUST_LANGUAGE = Language(tree_sitter_rust.language()) +RUST_PARSER = Parser(RUST_LANGUAGE) + + +class RustFnSignature: + def __init__(self, node: Node): + if not node.type == "function_item": + raise ValueError( + f"Node {node} is not a function_item, so cannot extract a signature!" + ) + + name = node.child_by_field_name("name") + if not name: + raise ValueError(f"Function name not found in {node}!") + + self.name: Node = name + self.params: Node | None = node.child_by_field_name("parameters") + self.return_type: Node | None = node.child_by_field_name("return_type") + + def __repr__(self) -> str: + text = "" + if _text := self.name.text: + text += _text.decode() + + if self.params and (_text := self.params.text): + text += _text.decode() + + if self.return_type and (_text := self.return_type.text): + text += _text.decode() + return text + + def __eq__(self, other: object) -> bool: + if not isinstance(other, RustFnSignature): + return NotImplemented + + return self.__repr__() == other.__repr__() + + +def get_root(code: str) -> Node: + tree = RUST_PARSER.parse(code.encode()) + return tree.root_node + + +def get_nodes(node: Node, node_type: str | None = None) -> list[Node]: + nodes = [] + for child in node.children: + if not node_type or child.type == node_type: + nodes.append(child) + return nodes + + +def get_ancestor_nodes(node: Node, node_type: str | None = None) -> list[Node]: + ancestors = [] + # Excluding self + current = node.parent + while current: + if not node_type or current.type == node_type: + ancestors.append(current) + current = current.parent + + # Remove root node from ancestors + return ancestors[:-1] + + +def get_macro_nodes(root: Node, placeholder: str) -> list[Node]: + # Query for all nodes containing macro invocation + source = f""" + (macro_invocation + macro: (identifier) @macro_name + (#eq? @macro_name "{placeholder}")) @macro + """ + + query = Query(RUST_LANGUAGE, source) + cursor = QueryCursor(query) + captures = cursor.captures(root) + + # Collect all unique ancestors by walking up from each macro invocation + ancestors = set() + for macro_node in captures.get("macro", []): + ancestors.update(get_ancestor_nodes(macro_node)) + + return list(ancestors) + + +def validate_changes(code: str, template: str) -> OrderedDict[str, str]: + code_root = get_root(code) + template_root = get_root(template) + + nodes = get_nodes(code_root) + template_nodes = get_nodes(template_root) + allowed_change_nodes = get_macro_nodes(template_root, "unimplemented") + + scope_feedback = OrderedDict() + + # Check for top-level changes + if len(nodes) != len(template_nodes): + scope_feedback["top_level_changes"] = ( + "The generated code modifies parts outside the function body.\n" + "You must **only** modify the `unimplemented!()` function body and leave everything else **unchanged**!" + ) + + # Check for allowed changes + for template_node, node in zip(template_nodes, nodes): + if not template_node.text == node.text: + if ( + template_node not in allowed_change_nodes + or not template_node.type == "function_item" + ): + scope_feedback["top_level_changes"] = ( + "The generated code modifies parts outside the function body.\n" + "You must **only** modify the `unimplemented!()` function body and leave everything else **unchanged**!" + ) + + if not node.type == "function_item" or (node.type != template_node.type): + scope_feedback["signature_changes"] = ( + "You must preserve the function signature in the template intact and **not modify it**!" + ) + else: + # Compare signatures + template_signature = RustFnSignature(template_node) + try: + signature = RustFnSignature(node) + except ValueError: + signature = None + + if template_signature != signature: + scope_feedback["signature_changes"] = ( + "You must preserve the function signature in the template intact and **not modify it**!" + ) + + return scope_feedback diff --git a/src/ideas/cmake.py b/src/ideas/cmake.py new file mode 100644 index 0000000..dc04a50 --- /dev/null +++ b/src/ideas/cmake.py @@ -0,0 +1,128 @@ +# +# Copyright (C) 2026 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# + +import os +import logging +import shutil + +from dataclasses import dataclass +from pathlib import Path + +import hydra +from omegaconf import MISSING +from hydra.core.config_store import ConfigStore + +from .tools import run_subprocess + +logger = logging.getLogger("ideas.cmake") + + +@dataclass +class CmakeConfig: + source_dir: Path = MISSING + build_dir: Path = MISSING + + +cs = ConfigStore.instance() +cs.store(name="cmake", node=CmakeConfig) + + +def configure( + source_dir: Path, + build_dir: Path, + preset: str | None = None, +) -> None: + # Clean existing build directory + shutil.rmtree(build_dir, ignore_errors=True) + + flags = [ + "-DCMAKE_BUILD_TYPE=Debug", + "-DCMAKE_C_FLAGS_DEBUG=-g -O0", + "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON", + ] + if extract_info_cmake := os.environ.get("EXTRACT_INFO_CMAKE"): + flags.append(f"-DCMAKE_PROJECT_TOP_LEVEL_INCLUDES={extract_info_cmake}") + if cflags := os.environ.get("CFLAGS"): + flags.append(f"-DCMAKE_C_FLAGS={cflags}") + + if not preset: + cmd = ["cmake", "-S", str(source_dir), "-B", str(build_dir), "-G", "Ninja"] + flags + else: + cmd = ["cmake", "-S", ".", "--preset", preset] + flags + + success, output = run_subprocess(cmd) + if not success: + raise RuntimeError(f"CMake configuration failed!\n{output}") + + +def build(build_dir: Path, preset: str | None = None) -> None: + if not preset: + cmd = ["cmake", "--build", str(build_dir), "--target", "all"] + else: + cmd = ["cmake", "--build", str(build_dir), "--target", "all", "--preset", preset] + + build_log_path = build_dir / "build.log" + success, output = run_subprocess(cmd) + if not success: + with open(build_log_path, "w") as log_file: + log_file.write(output) + raise RuntimeError(f"CMake build failed!\n{output}") + + +def extract_symbols(build_dir: Path) -> None: + # Find executables + cmd = ["find", str(build_dir), "-maxdepth", "1", "-type", "f", "-executable"] + success, output = run_subprocess(cmd) + if not success: + raise RuntimeError(f"Finding executables failed!\n{output}") + + executables = output.strip().split("\n") + for exe in executables: + if not exe: + raise RuntimeError(f"Found an empty line in the list of executables {executables}!") + + # Extract symbols using nm and awk + cmd = ["nm", "--extern-only", exe] + success, output = run_subprocess(cmd) + if not success: + raise RuntimeError(f"Extracting symbols from {exe} failed!\n{output}") + + # Filter for text symbols (T) and exclude symbols starting with _ + symbols = [] + for line in output.strip().split("\n"): + parts = line.split() + if len(parts) >= 2 and parts[1] == "T": + symbol = parts[-1] + if not symbol.startswith("_"): + symbols.append(symbol) + + # Write symbols to file + symbol_file = f"{exe}.symbols" + with open(symbol_file, "w") as f: + f.write("\n".join(symbols) + "\n") + + +@hydra.main(version_base=None, config_name="cmake") +def main(cfg: CmakeConfig) -> None: + # Determine Cmake preset + preset = "test" if os.path.exists("CMakePresets.json") else None + + # Configure Cmake + configure( + source_dir=cfg.source_dir, + build_dir=cfg.build_dir, + preset=preset, + ) + + # Build with Cmake + build(cfg.build_dir, preset) + + # Extract per-target symbols + extract_symbols(build_dir=cfg.build_dir) + + +if __name__ == "__main__": + main() diff --git a/src/ideas/init.py b/src/ideas/init.py index 2ae401c..0e4b401 100644 --- a/src/ideas/init.py +++ b/src/ideas/init.py @@ -17,10 +17,11 @@ from hydra.core.config_store import ConfigStore from hydra.core.hydra_config import HydraConfig from clang.cindex import CompilationDatabase, TranslationUnit, CursorKind +from clang.cindex import Rewriter, TokenKind, SourceRange from .ast import get_cursor_code, extract_info_c, TreeResult, get_internally_linked_cursors from .utils import Symbol -from .tools import Crate, clang_rename_ +from .tools import Crate, clang_rename_, check_c logger = logging.getLogger("ideas.preprocess") @@ -53,14 +54,12 @@ def init( pretty_print: bool = True, ) -> str: # Get symbol table and dependencies taking into account source priority and exported symbols, - # and prefix internally linked declarations/references when more than 1 translation unit since - # there can be name collisions between translation units. + # and prefix internally linked declarations/references since there can be name collisions + # between translation units. asts = get_asts( compile_commands, valid_paths=source_priority, - prefix_internally_linked=( - True if source_priority is not None and len(source_priority) > 1 else False - ), + prefix_internally_linked=False, ) symbols, dependencies = get_symbols_and_dependencies(asts, source_priority, export_symbols) logger.info(f"Found {len(symbols)} symbols in {compile_commands}!") @@ -169,14 +168,41 @@ def add_prefix_to_internally_linked_cursors( # use tu.cursor.spelling which needs to point to a valid file. source_bytes = source.read_bytes() try: + # Remove static visibility from internally-linked cursors + remove_static_keyword_(tu) + + # Add prefix to internally-linked declarations clang_rename_(source, renames, compile_commands=compile_commands) tu.reparse() finally: source.write_bytes(source_bytes) + # There should be no more internally linked cursors because we made them externally visible + assert len(get_internally_linked_cursors(tu.cursor)) == 0 + return tu +def remove_static_keyword_(tu: TranslationUnit): + assert tu.cursor is not None + cursors = get_internally_linked_cursors(tu.cursor) + + rewriter = Rewriter.create(tu) + for cursor in cursors: + # Find static keyword in cursor tokens + tokens = list(cursor.get_tokens()) + for i, token in enumerate(tokens): + if token.kind == TokenKind.KEYWORD and token.spelling == "static": + # Use next token's start as end of extent so we capture the spacing between the static + # keyword and the next token. + extent = SourceRange.from_locations( + token.extent.start, + tokens[i + 1].extent.start if i + 1 < len(tokens) else token.extent.end, + ) + rewriter.remove_text(extent) + rewriter.overwrite_changed_files() + + def filter_symbols( symbols: dict[str, Symbol], filter_system: bool = True, @@ -337,10 +363,7 @@ def main(cfg: InitConfig) -> None: type=cfg.crate_type, # type: ignore[reportArgumentType] vcs=cfg.vcs, # type: ignore[reportArgumentType] ) - commit_msg = f"Consolidated `{crate.root_package['name']}` C code\n" - commit_msg += f"Running ideas.init with args:\n\n{sys.argv}\n\n" crate.add(crate.cargo_toml) - commit_msg += f"Created {crate.root_package['name']} crate\n\n" crate.cargo_add(dep="openssl@0.10.75") if cfg.crate_type == "lib": @@ -364,17 +387,29 @@ def main(cfg: InitConfig) -> None: source_priority=source_priority, pretty_print=cfg.pretty_print, ) - logger.info(f"Prepared translation in {output_dir}") - # Create initial outputs + # Only run preprocess, compile, and assemble steps on C code + compiles, compile_errors = check_c(output, flags=["-c"]) + + # Write C code to disk crate.rust_src_path.parent.mkdir(exist_ok=True, parents=True) crate.rust_src_path.with_suffix(".c").write_text(output) - - # Commit initial outputs crate.add(crate.rust_src_path.with_suffix(".c")) + + # Add hydra directory if (output_subdir := HydraConfig.get().output_subdir) is not None: crate.add(output_dir / output_subdir) - crate.commit(commit_msg) + + # If the C code didn't compile, then error loudly + name = crate.root_package["name"] + if not compiles: + logger.error(f"Failed to compile `{name}` C code!") + crate.commit( + f"Failed to compile `{name}` C code!\n\n{' '.join(sys.argv)}\n\n{compile_errors}" + ) + sys.exit(1) + logger.info(f"Consolidated `{name}` in {output_dir}") + crate.commit(f"Consolidated `{name}`\n\n{' '.join(sys.argv)}") if __name__ == "__main__": diff --git a/src/ideas/tools.py b/src/ideas/tools.py index 4d1f039..6630b48 100644 --- a/src/ideas/tools.py +++ b/src/ideas/tools.py @@ -149,16 +149,25 @@ def cargo_build(self, allow_unsafe: bool = False) -> tuple[bool, str]: # Disallow unsafe by default; allow when explicitly requested if not allow_unsafe: env["RUSTFLAGS"] = (env.get("RUSTFLAGS", "") + " -D unsafe-code").strip() - return run_subprocess( - [ - "cargo", - "build", - "--quiet", - "--color=never", - f"--manifest-path={self.cargo_toml}", - ], - env=env, - ) + + cmd = [ + "cargo", + "build", + "--quiet", + "--color=never", + f"--manifest-path={self.cargo_toml}", + ] + builds, output = run_subprocess(cmd, env=env) + + # Work around E0601 error "No main function was found in a binary crate." + if "error[E0601]" in output: + rust_src = self.rust_src_path.read_text() + with self.rust_src_path.open("a") as f: + f.write('fn main() {\n println!("Hello, world!");\n}\n') + builds, output = run_subprocess(cmd, env=env) + self.rust_src_path.write_text(rust_src) + + return builds, output def add(self, *paths: Path) -> bool: if self.vcs != "git": diff --git a/src/ideas/translate.py b/src/ideas/translate.py index 20ab346..f9d1ba4 100644 --- a/src/ideas/translate.py +++ b/src/ideas/translate.py @@ -15,7 +15,7 @@ from hydra.core.config_store import ConfigStore from hydra.core.hydra_config import HydraConfig -from ideas import model, ModelConfig, GenerateConfig +from ideas import adapters, model, ModelConfig, GenerateConfig from ideas import SymbolTranslator, RecurrentTranslator from ideas import extract_info_c from .init import get_symbols_and_dependencies @@ -47,6 +47,7 @@ def main(cfg: TranslateConfig) -> None: crate = Crate(cargo_toml=output_dir / "Cargo.toml", vcs=cfg.vcs) # type: ignore[reportArgumentType] model.configure(cfg.model, cfg.generate) + dspy.configure(adapter=adapters.ChatAdapter()) translator = getattr(dspy, cfg.translator) symbol_translator = SymbolTranslator(translator, crate, cfg.max_iters) agent = RecurrentTranslator(symbol_translator) diff --git a/src/ideas/translate_recurrent.py b/src/ideas/translate_recurrent.py index da2cad2..bbcd9d1 100644 --- a/src/ideas/translate_recurrent.py +++ b/src/ideas/translate_recurrent.py @@ -31,7 +31,7 @@ def forward( for symbol_name in sorted_symbol_names: # Ignore tag definitions and function declarations if symbol_name not in symbols: - logger.warning(f"Skipping `{symbol_name}` ...") + logger.warning(f"Skipping symbol `{symbol_name}` ...") continue symbol = symbols[symbol_name] @@ -43,10 +43,8 @@ def forward( ref_translations = "\n\n".join(translations.values()) dep_symbols = [symbols[name] for name in sorted_symbol_names if name in dep_names] - logger.info(f"Translating `{symbol_name}` ...") pred = self.translate_symbol(ref_translations, symbol, dep_symbols) if not pred.success: - logger.error(f"Failed to translate `{symbol_name}`!") break # Save translation if it builds diff --git a/src/ideas/translate_symbol.py b/src/ideas/translate_symbol.py index 0034664..05656aa 100644 --- a/src/ideas/translate_symbol.py +++ b/src/ideas/translate_symbol.py @@ -12,10 +12,14 @@ from .tools import Crate from .utils import Symbol from .ast import get_cursor_code +from .adapters import Code logger = logging.getLogger("ideas.translate_symbol") +CodeC = Code["c"] +CodeRust = Code["rust"] + class SymbolTranslatorSignature(dspy.Signature): """ @@ -34,12 +38,12 @@ class SymbolTranslatorSignature(dspy.Signature): Use the `cargo build` feedback about the prior_translation, if provided, when generating the Rust translation. """ - reference_code: dspy.Code["Rust"] = dspy.InputField() # noqa: F821 - snippet: dspy.Code["C"] = dspy.InputField() # noqa: F821 - dependent_code: dspy.Code["C"] = dspy.InputField() # noqa: F821 - prior_translation: dspy.Code["Rust"] = dspy.InputField() # noqa: F821 + reference_code: CodeRust = dspy.InputField() + snippet: CodeC = dspy.InputField() + dependent_code: CodeC = dspy.InputField() + prior_translation: CodeRust = dspy.InputField() feedback: str = dspy.InputField() - translation: dspy.Code["Rust"] = dspy.OutputField() # noqa: F821 + translation: CodeRust = dspy.OutputField() class SymbolTranslator(dspy.Module): @@ -65,6 +69,7 @@ def forward( prior_translation: str = "", feedback: str = "", ) -> dspy.Prediction: + logger.info(f"Translating symbol `{symbol.name}` ...") snippet = get_cursor_code(symbol.cursor) dependent_code = "\n\n".join([get_cursor_code(s.cursor) for s in dependent_symbols]) @@ -72,10 +77,10 @@ def forward( for i in range(max(self.max_iters, 1)): # Predict symbol translation pred = self.translate( - reference_code=reference_code, - snippet=snippet, - dependent_code=dependent_code, - prior_translation=prior_translation, + reference_code=CodeRust(code=reference_code), + snippet=CodeC(code=snippet), + dependent_code=CodeC(code=dependent_code), + prior_translation=CodeRust(code=prior_translation), feedback=feedback, ) @@ -84,9 +89,6 @@ def forward( if len(reference_code) > 0: rust_src += reference_code + "\n\n" rust_src += pred.translation.code + "\n\n" - if self.crate.is_bin and symbol.name != "c:@F@main": - # Work around E0601 error - rust_src += 'fn main() {\n println!("Hello, world!");\n}\n' self.crate.rust_src_path.write_text(rust_src) self.crate.add(self.crate.rust_src_path) # FIXME: Add rustfmt and FeedbackException? @@ -116,9 +118,13 @@ def forward( self.crate.commit( f"Translated symbol `{symbol.name}`\n\n# Reasoning\n{pred.reasoning}" ) + logger.info(f"Translated symbol `{symbol.name}`") break self.crate.commit( f"Failed to translate symbol `{symbol.name}` ({i + 1}/{self.max_iters})!\n\n# Reasoning\n{pred.reasoning}\n\n# Feedback\n{pred.feedback}" ) + logger.error( + f"Failed to translate symbol `{symbol.name}` ({i + 1}/{self.max_iters})!" + ) prior_translation = pred.translation.code return pred diff --git a/src/ideas/wrapper.py b/src/ideas/wrapper.py index f8642f8..fda9e4a 100644 --- a/src/ideas/wrapper.py +++ b/src/ideas/wrapper.py @@ -7,7 +7,7 @@ import re import logging from pathlib import Path -from collections import defaultdict +from collections import defaultdict, OrderedDict from dataclasses import dataclass, field import dspy @@ -16,10 +16,13 @@ from hydra.core.config_store import ConfigStore from hydra.core.hydra_config import HydraConfig -from ideas import model, ModelConfig, GenerateConfig -from ideas.tools import Crate, run_subprocess +from ideas import adapters, model, ModelConfig, GenerateConfig +from ideas.tools import Crate, check_rust, run_subprocess +from ideas.adapters import Code +from ideas.ast_rust import validate_changes logger = logging.getLogger("ideas.wrapper") +CodeRust = Code["rust"] @dataclass @@ -49,15 +52,19 @@ class Signature(dspy.Signature): After this conversion, the wrapper should call the Rust function `crate::{symbol_name}`. After the call to `crate::{symbol_name}`, the wrapper should convert back the `crate::` types to `crate::wrapper::` types. The wrapper will be written to "{wrapper_path}". - Use the feedback, if provided, from `cargo build` about the `prior_wrapper` when generating the wrapper. + You will receive feedback about a `prior_wrapper` attempt that should be fixed, if any. + Use the `build_feedback` from `cargo build` about possible build errors. + Use the `scope_feedback` about possible deviations from the templated `example_wrapper`. """ # FIXME: Move crate and example_wrapper into instructions? - crate: dspy.Code["Rust"] = dspy.InputField() # noqa: F821 - example_wrapper: dspy.Code["Rust"] = dspy.InputField() # noqa: F821 - wrapper: dspy.Code["Rust"] = dspy.OutputField() # noqa: F821 - prior_wrapper: dspy.Code["Rust"] = dspy.InputField() # noqa: F821 - feedback: str = dspy.InputField() + crate: CodeRust = dspy.InputField() + example_wrapper: CodeRust = dspy.InputField() + prior_wrapper: CodeRust = dspy.InputField() + build_feedback: str = dspy.InputField() + scope_feedback: str = dspy.InputField() + + wrapper: CodeRust = dspy.OutputField() class WrapperGenerator(dspy.Module): @@ -99,15 +106,6 @@ def forward( unimplemented_wrapper = self.generate_unimplemented_wrapper(symbol_name) symbol_wrapper_path.write_text(unimplemented_wrapper) - # Replace lines containing "unimplemented!()" with ".*". Note the triple-backslashes - # are due to re.escape turning "(" into "\(". - allowed_changes = re.sub( - r"^.* unimplemented!\\\(\\\);.*$", - r".*", - re.escape(unimplemented_wrapper), - flags=re.MULTILINE, - ) - # Generate dynamic signature and module for symbol signature = Signature.with_instructions( Signature.instructions.format( @@ -120,40 +118,46 @@ def forward( # Try generating wrapper up to max_iter times code = self.crate.rust_src_path.read_text() - wrapper, success, feedback = "", False, "" + wrapper, success, build_feedback = "", False, "" + scope_feedback: OrderedDict[str, str] = OrderedDict() for i in range(max_iters): pred = generate_wrapper( - crate=code, - example_wrapper=unimplemented_wrapper, - feedback=feedback, - prior_wrapper=wrapper, + crate=CodeRust(code=code), + example_wrapper=CodeRust(code=unimplemented_wrapper), + prior_wrapper=CodeRust(code=wrapper), + build_feedback=build_feedback, + scope_feedback="\n\n".join(scope_feedback.values()), ) + # Reset scope feedback + scope_feedback.clear() if pred.wrapper is None: - feedback = "No wrapper was generated. You must respect the template and instructions **exactly**!" - continue - wrapper = pred.wrapper.code - - # Enforce only function body changes - if re.match(f"^{allowed_changes}$", wrapper, flags=re.DOTALL) is None: - feedback = ( - "The generated wrapper modifies parts outside the function body." - "You must **only** modify the `unimplemented!()` function body and leave everything else **unchanged**!" + scope_feedback["no_wrapper"] = ( + "No wrapper was generated. You must respect the template and instructions **exactly**!" ) - continue + wrapper = unimplemented_wrapper + else: + wrapper = pred.wrapper.code + # Validate that changes are in scope + scope_feedback.update(validate_changes(wrapper, unimplemented_wrapper)) + + # TODO: Check for a single crate function call in scope # Write wrapper to disk and check if we build with unsafe code since wrappers can use unsafe code symbol_wrapper_path.write_text(wrapper) self.crate.add(self.crate.rust_src_path, self.wrapper_path, symbol_wrapper_path) - success, feedback = self.crate.cargo_build(allow_unsafe=True) - if success: + success, build_feedback = self.crate.cargo_build(allow_unsafe=True) + if success and not build_feedback and not scope_feedback: self.crate.commit( f"Wrapped symbol `{symbol_name}`\n\n# Reasoning\n{pred.reasoning}" ) break self.crate.commit( - f"Failed to wrap symbol `{symbol_name}` ({i + 1}/{max_iters})!\n\n# Reasoning\n{pred.reasoning}\n\n# Feedback\n{feedback}" + f"Failed to wrap symbol `{symbol_name}` ({i + 1}/{max_iters})!\n\n" + f"# Reasoning\n{pred.reasoning}\n\n" + f"# Build feedback\n{build_feedback}\n\n" + f"# Scope Feedback\n{scope_feedback}" ) else: logger.warning(f"Wrapper generation failed after {max_iters} feedback iterations!") @@ -195,7 +199,17 @@ def generate_unimplemented_wrapper(self, symbol_name) -> str: ) if unimplemented_wrapper == bindgen_wrapper: raise ValueError("Failed to convert bindgen to valid wrapper!") - return unimplemented_wrapper.rstrip() + unimplemented_wrapper = unimplemented_wrapper.rstrip() + + # Validate the template + success, output = check_rust( + unimplemented_wrapper, flags=["--crate-type", "lib", "--emit", "metadata"] + ) + if not success: + raise ValueError( + f"Invalid template for the wrapper: {unimplemented_wrapper}\n\nBuild error:\n{output}" + ) + return unimplemented_wrapper @hydra.main(version_base=None, config_name="wrapper") @@ -206,6 +220,7 @@ def main(cfg: WrapperConfig) -> None: crate = Crate(cargo_toml=cfg.cargo_toml.resolve(), vcs=cfg.vcs) # type: ignore[reportArgumentType] model.configure(cfg.model, cfg.generate) + dspy.configure(adapter=adapters.ChatAdapter()) agent = WrapperGenerator(crate, max_iters=cfg.max_iters) wrappers: dict[Path, list[str]] = defaultdict(list) diff --git a/test/fixtures/templating/modified_invalid.rs b/test/fixtures/templating/modified_invalid.rs new file mode 100644 index 0000000..2dbb6bc --- /dev/null +++ b/test/fixtures/templating/modified_invalid.rs @@ -0,0 +1,32 @@ +struct Context { + other_var: i32, + more_var: u32, +} + +fn function(var: i32) -> i32 { + fn inner_function(param: i32) { + println!("Inner function received: {}", param); + } + + let closure_func = || { + println!("Closure accessed var directly: {}", var); + }; + + var +} + +fn other_function(other_var: i32) -> i32 { + unimplemented!() +} + +fn immutable_function(var: i32) -> i32 { + var +} + +fn immutable_function2(var: i32) -> i32 { + var +} + +fn main() -> Result<()> { + Ok(()) +} diff --git a/test/fixtures/templating/modified_valid.rs b/test/fixtures/templating/modified_valid.rs new file mode 100644 index 0000000..f9d40cf --- /dev/null +++ b/test/fixtures/templating/modified_valid.rs @@ -0,0 +1,27 @@ +struct Context { + other_var: i32, +} + +fn function(var: i32) -> i32 { + fn inner_function(param: i32) { + println!("Inner function received: {}", param); + } + + let closure_func = || { + println!("Closure accessed var directly: {}", var); + }; + + var +} + +fn other_function(other_var: i32) -> i32 { +unimplemented!() +} + +fn immutable_function(var: i32) -> i32 { + var +} + +fn main() -> Result<()> { + Ok(()) +} diff --git a/test/fixtures/templating/template.rs b/test/fixtures/templating/template.rs new file mode 100644 index 0000000..0203851 --- /dev/null +++ b/test/fixtures/templating/template.rs @@ -0,0 +1,19 @@ +struct Context { + other_var: i32, +} + +fn function(var: i32) -> i32 { + unimplemented!() +} + +fn other_function(other_var: i32) -> i32 { + unimplemented!() +} + +fn immutable_function(var: i32) -> i32 { + var +} + +fn main() -> Result<()> { + Ok(()) +} diff --git a/test/test_adapters.py b/test/test_adapters.py new file mode 100644 index 0000000..88753a3 --- /dev/null +++ b/test/test_adapters.py @@ -0,0 +1,23 @@ +# +# Copyright (C) 2026 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# + +import dspy +from src.ideas.adapters import ChatAdapter, Code + +CodeLanguage = Code["language"] + + +def test_chat_adapter_uses_custom_translate_field_type(): + class TestSignature(dspy.Signature): + input_text: str = dspy.InputField() + code_output: CodeLanguage = dspy.OutputField() + + adapter = ChatAdapter() + result = adapter.format_field_structure(TestSignature) + + # The result should contain the custom format with "note: the value you produce must be Code" + assert f"# note: the value you produce {CodeLanguage.short_description()}" in result + assert "{code_output}" in result diff --git a/test/test_templating.py b/test/test_templating.py new file mode 100644 index 0000000..6221cdf --- /dev/null +++ b/test/test_templating.py @@ -0,0 +1,42 @@ +# +# Copyright (C) 2026 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# + + +import pytest +from pathlib import Path + +from ideas.ast_rust import validate_changes + + +@pytest.fixture +def fixtures_dir() -> Path: + return Path(__file__).parent / "fixtures" / "templating" + + +@pytest.fixture +def template(fixtures_dir: Path) -> str: + return (fixtures_dir / "template.rs").read_text() + + +@pytest.fixture +def modified_valid(fixtures_dir: Path) -> str: + return (fixtures_dir / "modified_valid.rs").read_text() + + +@pytest.fixture +def modified_invalid(fixtures_dir: Path) -> str: + return (fixtures_dir / "modified_invalid.rs").read_text() + + +def test_modified_valid(template: str, modified_valid: str): + feedback = validate_changes(modified_valid, template) + assert not feedback + + +def test_modified_invalid(template: str, modified_invalid: str): + feedback = validate_changes(modified_invalid, template) + assert feedback + assert list(feedback.keys()) == ["top_level_changes", "signature_changes"] diff --git a/uv.lock b/uv.lock index 9a19251..48d4a1a 100644 --- a/uv.lock +++ b/uv.lock @@ -274,7 +274,7 @@ wheels = [ [[package]] name = "dspy" -version = "3.1.0" +version = "3.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -296,9 +296,9 @@ dependencies = [ { name = "tqdm" }, { name = "xxhash" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d9/bd/339e1d949aa24e50b6edca8574cf8afbff005a1dfadc2d5b82a0003fa7da/dspy-3.1.0.tar.gz", hash = "sha256:3c1660cb687411f064509cd7ac47ed0231761f50ed92823cbbb59b3af2885702", size = 242611, upload-time = "2026-01-06T18:50:18.655Z" } +sdist = { url = "https://files.pythonhosted.org/packages/22/92/4eeed6796c48e799a41aa521bf206fabab3dbb2fbf0958655b476e43726e/dspy-3.1.2.tar.gz", hash = "sha256:6dfd8a4bbf74b1b432ff466468994a590c43fce0d45c2c0094313c9314aa3bb6", size = 261253, upload-time = "2026-01-19T14:21:47.424Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/34/64e900657cad3c4df8a417fccbc4370ca851864edbd0f468210f1b57d084/dspy-3.1.0-py3-none-any.whl", hash = "sha256:b9f4d42cad9ac32b13fdf085dd3246c8ee8339c759cb291c5e7b7f9f5fb5dd72", size = 291317, upload-time = "2026-01-06T18:50:17.559Z" }, + { url = "https://files.pythonhosted.org/packages/91/18/1c93641f25f4e76772b4ffb52a4e289f1706d6bda4f3b59bb6f7c339df46/dspy-3.1.2-py3-none-any.whl", hash = "sha256:23b98bf5abeda260722c445d397d07ea27488c204b8c0ccd6d3e607c4b41bc6b", size = 312290, upload-time = "2026-01-19T14:21:45.776Z" }, ] [[package]] @@ -322,11 +322,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.20.2" +version = "3.20.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c1/e0/a75dbe4bca1e7d41307323dad5ea2efdd95408f74ab2de8bd7dba9b51a1a/filelock-3.20.2.tar.gz", hash = "sha256:a2241ff4ddde2a7cebddf78e39832509cb045d18ec1a09d7248d6bfc6bfbbe64", size = 19510, upload-time = "2026-01-02T15:33:32.582Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/30/ab407e2ec752aa541704ed8f93c11e2a5d92c168b8a755d818b74a3c5c2d/filelock-3.20.2-py3-none-any.whl", hash = "sha256:fbba7237d6ea277175a32c54bb71ef814a8546d8601269e1bfc388de333974e8", size = 16697, upload-time = "2026-01-02T15:33:31.133Z" }, + { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" }, ] [[package]] @@ -381,11 +381,11 @@ wheels = [ [[package]] name = "gepa" -version = "0.0.22" +version = "0.0.24" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/be/9a4c31c65c4910e73bd4a316df99347681bce4f2ef6d10877cc1ed8d57da/gepa-0.0.22.tar.gz", hash = "sha256:0b13a644efd2af52186e456eaad2ae28fcf88c6f796a9c999910c587863d4315", size = 116337, upload-time = "2025-11-10T21:39:27.044Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/2c/8f249827bcdc56212a445f4f671ee2201b864e433d2c88428933bd4b08f3/gepa-0.0.24.tar.gz", hash = "sha256:8035d1a0877661b6a63db457dc935b4878ee76acf7da1e488d7e6209bb32c054", size = 135673, upload-time = "2026-01-05T16:45:30.553Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/0e/d41272b28f2163f5bf840b508cbaf4a0bc01eaeaa6e5d447558d1050eb3b/gepa-0.0.22-py3-none-any.whl", hash = "sha256:ff57ee0442399a5cc62d01411935476bc80cfa8f1c318bed82e3db4c2c834665", size = 119666, upload-time = "2025-11-10T21:39:26.028Z" }, + { url = "https://files.pythonhosted.org/packages/7e/b1/33b035ff1aaf22d4e104c5b15ba48fe5050639764457048e967c20d6317a/gepa-0.0.24-py3-none-any.whl", hash = "sha256:6d8b16699e7b24ed01435dea7bbbc89156a88cbb4b877b14d90e7455db2b0032", size = 137539, upload-time = "2026-01-05T16:45:29.244Z" }, ] [[package]] @@ -527,7 +527,6 @@ dependencies = [ { name = "dspy" }, { name = "hydra-core" }, { name = "tree-sitter" }, - { name = "tree-sitter-c" }, { name = "tree-sitter-rust" }, ] @@ -542,11 +541,10 @@ dev = [ [package.metadata] requires-dist = [ { name = "clang", specifier = "==21.1.7" }, - { name = "dspy", specifier = "==3.1.0" }, + { name = "dspy", specifier = "==3.1.2" }, { name = "hydra-core", git = "https://github.com/facebookresearch/hydra.git?rev=1ac07c7c001f73b2a6cdbc4c2ad700287cdf592c" }, - { name = "tree-sitter", specifier = "==0.24.0" }, - { name = "tree-sitter-c", specifier = "==0.23.4" }, - { name = "tree-sitter-rust", specifier = "==0.23.2" }, + { name = "tree-sitter", specifier = "==0.25.2" }, + { name = "tree-sitter-rust", specifier = "==0.24.0" }, ] [package.metadata.requires-dev] @@ -815,31 +813,31 @@ wheels = [ [[package]] name = "numpy" -version = "2.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a4/7a/6a3d14e205d292b738db449d0de649b373a59edb0d0b4493821d0a3e8718/numpy-2.4.0.tar.gz", hash = "sha256:6e504f7b16118198f138ef31ba24d985b124c2c469fe8467007cf30fd992f934", size = 20685720, upload-time = "2025-12-20T16:18:19.023Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/ed/52eac27de39d5e5a6c9aadabe672bc06f55e24a3d9010cd1183948055d76/numpy-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c95eb6db2884917d86cde0b4d4cf31adf485c8ec36bf8696dd66fa70de96f36b", size = 16647476, upload-time = "2025-12-20T16:17:17.671Z" }, - { url = "https://files.pythonhosted.org/packages/77/c0/990ce1b7fcd4e09aeaa574e2a0a839589e4b08b2ca68070f1acb1fea6736/numpy-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:65167da969cd1ec3a1df31cb221ca3a19a8aaa25370ecb17d428415e93c1935e", size = 12374563, upload-time = "2025-12-20T16:17:20.216Z" }, - { url = "https://files.pythonhosted.org/packages/37/7c/8c5e389c6ae8f5fd2277a988600d79e9625db3fff011a2d87ac80b881a4c/numpy-2.4.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3de19cfecd1465d0dcf8a5b5ea8b3155b42ed0b639dba4b71e323d74f2a3be5e", size = 5203107, upload-time = "2025-12-20T16:17:22.47Z" }, - { url = "https://files.pythonhosted.org/packages/e6/94/ca5b3bd6a8a70a5eec9a0b8dd7f980c1eff4b8a54970a9a7fef248ef564f/numpy-2.4.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6c05483c3136ac4c91b4e81903cb53a8707d316f488124d0398499a4f8e8ef51", size = 6538067, upload-time = "2025-12-20T16:17:24.001Z" }, - { url = "https://files.pythonhosted.org/packages/79/43/993eb7bb5be6761dde2b3a3a594d689cec83398e3f58f4758010f3b85727/numpy-2.4.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36667db4d6c1cea79c8930ab72fadfb4060feb4bfe724141cd4bd064d2e5f8ce", size = 14411926, upload-time = "2025-12-20T16:17:25.822Z" }, - { url = "https://files.pythonhosted.org/packages/03/75/d4c43b61de473912496317a854dac54f1efec3eeb158438da6884b70bb90/numpy-2.4.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9a818668b674047fd88c4cddada7ab8f1c298812783e8328e956b78dc4807f9f", size = 16354295, upload-time = "2025-12-20T16:17:28.308Z" }, - { url = "https://files.pythonhosted.org/packages/b8/0a/b54615b47ee8736a6461a4bb6749128dd3435c5a759d5663f11f0e9af4ac/numpy-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1ee32359fb7543b7b7bd0b2f46294db27e29e7bbdf70541e81b190836cd83ded", size = 16190242, upload-time = "2025-12-20T16:17:30.993Z" }, - { url = "https://files.pythonhosted.org/packages/98/ce/ea207769aacad6246525ec6c6bbd66a2bf56c72443dc10e2f90feed29290/numpy-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e493962256a38f58283de033d8af176c5c91c084ea30f15834f7545451c42059", size = 18280875, upload-time = "2025-12-20T16:17:33.327Z" }, - { url = "https://files.pythonhosted.org/packages/17/ef/ec409437aa962ea372ed601c519a2b141701683ff028f894b7466f0ab42b/numpy-2.4.0-cp314-cp314-win32.whl", hash = "sha256:6bbaebf0d11567fa8926215ae731e1d58e6ec28a8a25235b8a47405d301332db", size = 6002530, upload-time = "2025-12-20T16:17:35.729Z" }, - { url = "https://files.pythonhosted.org/packages/5f/4a/5cb94c787a3ed1ac65e1271b968686521169a7b3ec0b6544bb3ca32960b0/numpy-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d857f55e7fdf7c38ab96c4558c95b97d1c685be6b05c249f5fdafcbd6f9899e", size = 12435890, upload-time = "2025-12-20T16:17:37.599Z" }, - { url = "https://files.pythonhosted.org/packages/48/a0/04b89db963af9de1104975e2544f30de89adbf75b9e75f7dd2599be12c79/numpy-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:bb50ce5fb202a26fd5404620e7ef820ad1ab3558b444cb0b55beb7ef66cd2d63", size = 10591892, upload-time = "2025-12-20T16:17:39.649Z" }, - { url = "https://files.pythonhosted.org/packages/53/e5/d74b5ccf6712c06c7a545025a6a71bfa03bdc7e0568b405b0d655232fd92/numpy-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:355354388cba60f2132df297e2d53053d4063f79077b67b481d21276d61fc4df", size = 12494312, upload-time = "2025-12-20T16:17:41.714Z" }, - { url = "https://files.pythonhosted.org/packages/c2/08/3ca9cc2ddf54dfee7ae9a6479c071092a228c68aef08252aa08dac2af002/numpy-2.4.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:1d8f9fde5f6dc1b6fc34df8162f3b3079365468703fee7f31d4e0cc8c63baed9", size = 5322862, upload-time = "2025-12-20T16:17:44.145Z" }, - { url = "https://files.pythonhosted.org/packages/87/74/0bb63a68394c0c1e52670cfff2e309afa41edbe11b3327d9af29e4383f34/numpy-2.4.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e0434aa22c821f44eeb4c650b81c7fbdd8c0122c6c4b5a576a76d5a35625ecd9", size = 6644986, upload-time = "2025-12-20T16:17:46.203Z" }, - { url = "https://files.pythonhosted.org/packages/06/8f/9264d9bdbcf8236af2823623fe2f3981d740fc3461e2787e231d97c38c28/numpy-2.4.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40483b2f2d3ba7aad426443767ff5632ec3156ef09742b96913787d13c336471", size = 14457958, upload-time = "2025-12-20T16:17:48.017Z" }, - { url = "https://files.pythonhosted.org/packages/8c/d9/f9a69ae564bbc7236a35aa883319364ef5fd41f72aa320cc1cbe66148fe2/numpy-2.4.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6a7664ddd9746e20b7325351fe1a8408d0a2bf9c63b5e898290ddc8f09544", size = 16398394, upload-time = "2025-12-20T16:17:50.409Z" }, - { url = "https://files.pythonhosted.org/packages/34/c7/39241501408dde7f885d241a98caba5421061a2c6d2b2197ac5e3aa842d8/numpy-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ecb0019d44f4cdb50b676c5d0cb4b1eae8e15d1ed3d3e6639f986fc92b2ec52c", size = 16241044, upload-time = "2025-12-20T16:17:52.661Z" }, - { url = "https://files.pythonhosted.org/packages/7c/95/cae7effd90e065a95e59fe710eeee05d7328ed169776dfdd9f789e032125/numpy-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d0ffd9e2e4441c96a9c91ec1783285d80bf835b677853fc2770a89d50c1e48ac", size = 18321772, upload-time = "2025-12-20T16:17:54.947Z" }, - { url = "https://files.pythonhosted.org/packages/96/df/3c6c279accd2bfb968a76298e5b276310bd55d243df4fa8ac5816d79347d/numpy-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:77f0d13fa87036d7553bf81f0e1fe3ce68d14c9976c9851744e4d3e91127e95f", size = 6148320, upload-time = "2025-12-20T16:17:57.249Z" }, - { url = "https://files.pythonhosted.org/packages/92/8d/f23033cce252e7a75cae853d17f582e86534c46404dea1c8ee094a9d6d84/numpy-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b1f5b45829ac1848893f0ddf5cb326110604d6df96cdc255b0bf9edd154104d4", size = 12623460, upload-time = "2025-12-20T16:17:58.963Z" }, - { url = "https://files.pythonhosted.org/packages/a4/4f/1f8475907d1a7c4ef9020edf7f39ea2422ec896849245f00688e4b268a71/numpy-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:23a3e9d1a6f360267e8fbb38ba5db355a6a7e9be71d7fce7ab3125e88bb646c8", size = 10661799, upload-time = "2025-12-20T16:18:01.078Z" }, +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/62/ae72ff66c0f1fd959925b4c11f8c2dea61f47f6acaea75a08512cdfe3fed/numpy-2.4.1.tar.gz", hash = "sha256:a1ceafc5042451a858231588a104093474c6a5c57dcc724841f5c888d237d690", size = 20721320, upload-time = "2026-01-10T06:44:59.619Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/a7/ef08d25698e0e4b4efbad8d55251d20fe2a15f6d9aa7c9b30cd03c165e6f/numpy-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3869ea1ee1a1edc16c29bbe3a2f2a4e515cc3a44d43903ad41e0cacdbaf733dc", size = 16652046, upload-time = "2026-01-10T06:43:54.797Z" }, + { url = "https://files.pythonhosted.org/packages/8f/39/e378b3e3ca13477e5ac70293ec027c438d1927f18637e396fe90b1addd72/numpy-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e867df947d427cdd7a60e3e271729090b0f0df80f5f10ab7dd436f40811699c3", size = 12378858, upload-time = "2026-01-10T06:43:57.099Z" }, + { url = "https://files.pythonhosted.org/packages/c3/74/7ec6154f0006910ed1fdbb7591cf4432307033102b8a22041599935f8969/numpy-2.4.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:e3bd2cb07841166420d2fa7146c96ce00cb3410664cbc1a6be028e456c4ee220", size = 5207417, upload-time = "2026-01-10T06:43:59.037Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b7/053ac11820d84e42f8feea5cb81cc4fcd1091499b45b1ed8c7415b1bf831/numpy-2.4.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:f0a90aba7d521e6954670550e561a4cb925713bd944445dbe9e729b71f6cabee", size = 6542643, upload-time = "2026-01-10T06:44:01.852Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c4/2e7908915c0e32ca636b92e4e4a3bdec4cb1e7eb0f8aedf1ed3c68a0d8cd/numpy-2.4.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d558123217a83b2d1ba316b986e9248a1ed1971ad495963d555ccd75dcb1556", size = 14418963, upload-time = "2026-01-10T06:44:04.047Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c0/3ed5083d94e7ffd7c404e54619c088e11f2e1939a9544f5397f4adb1b8ba/numpy-2.4.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f44de05659b67d20499cbc96d49f2650769afcb398b79b324bb6e297bfe3844", size = 16363811, upload-time = "2026-01-10T06:44:06.207Z" }, + { url = "https://files.pythonhosted.org/packages/0e/68/42b66f1852bf525050a67315a4fb94586ab7e9eaa541b1bef530fab0c5dd/numpy-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:69e7419c9012c4aaf695109564e3387f1259f001b4326dfa55907b098af082d3", size = 16197643, upload-time = "2026-01-10T06:44:08.33Z" }, + { url = "https://files.pythonhosted.org/packages/d2/40/e8714fc933d85f82c6bfc7b998a0649ad9769a32f3494ba86598aaf18a48/numpy-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2ffd257026eb1b34352e749d7cc1678b5eeec3e329ad8c9965a797e08ccba205", size = 18289601, upload-time = "2026-01-10T06:44:10.841Z" }, + { url = "https://files.pythonhosted.org/packages/80/9a/0d44b468cad50315127e884802351723daca7cf1c98d102929468c81d439/numpy-2.4.1-cp314-cp314-win32.whl", hash = "sha256:727c6c3275ddefa0dc078524a85e064c057b4f4e71ca5ca29a19163c607be745", size = 6005722, upload-time = "2026-01-10T06:44:13.332Z" }, + { url = "https://files.pythonhosted.org/packages/7e/bb/c6513edcce5a831810e2dddc0d3452ce84d208af92405a0c2e58fd8e7881/numpy-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:7d5d7999df434a038d75a748275cd6c0094b0ecdb0837342b332a82defc4dc4d", size = 12438590, upload-time = "2026-01-10T06:44:15.006Z" }, + { url = "https://files.pythonhosted.org/packages/e9/da/a598d5cb260780cf4d255102deba35c1d072dc028c4547832f45dd3323a8/numpy-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:ce9ce141a505053b3c7bce3216071f3bf5c182b8b28930f14cd24d43932cd2df", size = 10596180, upload-time = "2026-01-10T06:44:17.386Z" }, + { url = "https://files.pythonhosted.org/packages/de/bc/ea3f2c96fcb382311827231f911723aeff596364eb6e1b6d1d91128aa29b/numpy-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4e53170557d37ae404bf8d542ca5b7c629d6efa1117dac6a83e394142ea0a43f", size = 12498774, upload-time = "2026-01-10T06:44:19.467Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ab/ef9d939fe4a812648c7a712610b2ca6140b0853c5efea361301006c02ae5/numpy-2.4.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:a73044b752f5d34d4232f25f18160a1cc418ea4507f5f11e299d8ac36875f8a0", size = 5327274, upload-time = "2026-01-10T06:44:23.189Z" }, + { url = "https://files.pythonhosted.org/packages/bd/31/d381368e2a95c3b08b8cf7faac6004849e960f4a042d920337f71cef0cae/numpy-2.4.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:fb1461c99de4d040666ca0444057b06541e5642f800b71c56e6ea92d6a853a0c", size = 6648306, upload-time = "2026-01-10T06:44:25.012Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e5/0989b44ade47430be6323d05c23207636d67d7362a1796ccbccac6773dd2/numpy-2.4.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:423797bdab2eeefbe608d7c1ec7b2b4fd3c58d51460f1ee26c7500a1d9c9ee93", size = 14464653, upload-time = "2026-01-10T06:44:26.706Z" }, + { url = "https://files.pythonhosted.org/packages/10/a7/cfbe475c35371cae1358e61f20c5f075badc18c4797ab4354140e1d283cf/numpy-2.4.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:52b5f61bdb323b566b528899cc7db2ba5d1015bda7ea811a8bcf3c89c331fa42", size = 16405144, upload-time = "2026-01-10T06:44:29.378Z" }, + { url = "https://files.pythonhosted.org/packages/f8/a3/0c63fe66b534888fa5177cc7cef061541064dbe2b4b60dcc60ffaf0d2157/numpy-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42d7dd5fa36d16d52a84f821eb96031836fd405ee6955dd732f2023724d0aa01", size = 16247425, upload-time = "2026-01-10T06:44:31.721Z" }, + { url = "https://files.pythonhosted.org/packages/6b/2b/55d980cfa2c93bd40ff4c290bf824d792bd41d2fe3487b07707559071760/numpy-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7b6b5e28bbd47b7532698e5db2fe1db693d84b58c254e4389d99a27bb9b8f6b", size = 18330053, upload-time = "2026-01-10T06:44:34.617Z" }, + { url = "https://files.pythonhosted.org/packages/23/12/8b5fc6b9c487a09a7957188e0943c9ff08432c65e34567cabc1623b03a51/numpy-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:5de60946f14ebe15e713a6f22850c2372fa72f4ff9a432ab44aa90edcadaa65a", size = 6152482, upload-time = "2026-01-10T06:44:36.798Z" }, + { url = "https://files.pythonhosted.org/packages/00/a5/9f8ca5856b8940492fc24fbe13c1bc34d65ddf4079097cf9e53164d094e1/numpy-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:8f085da926c0d491ffff3096f91078cc97ea67e7e6b65e490bc8dcda65663be2", size = 12627117, upload-time = "2026-01-10T06:44:38.828Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0d/eca3d962f9eef265f01a8e0d20085c6dd1f443cbffc11b6dede81fd82356/numpy-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:6436cffb4f2bf26c974344439439c95e152c9a527013f26b3577be6c2ca64295", size = 10667121, upload-time = "2026-01-10T06:44:41.644Z" }, ] [[package]] @@ -1350,38 +1348,32 @@ wheels = [ [[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, upload-time = "2025-01-17T05:06:38.115Z" } - -[[package]] -name = "tree-sitter-c" -version = "0.23.4" +version = "0.25.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/27/27/254ebffa4066b3073dddee00c1915893794f5cbf938335c1cc926cd32385/tree_sitter_c-0.23.4.tar.gz", hash = "sha256:9215c7888dd019038f162ea5646178f6e129cd2b49fc506d14becf5e426121d7", size = 223089, upload-time = "2024-12-15T22:24:42.833Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/7c/0350cfc47faadc0d3cf7d8237a4e34032b3014ddf4a12ded9933e1648b55/tree-sitter-0.25.2.tar.gz", hash = "sha256:fe43c158555da46723b28b52e058ad444195afd1db3ca7720c59a254544e9c20", size = 177961, upload-time = "2025-09-25T17:37:59.751Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/a9/41e5177fd9309bf142d6772f6885e6a93baa0ad40f17c7a4144ba1275c9c/tree_sitter_c-0.23.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2c92c0571b36b6da06f8882f34151dc11e67a493e9101cc0026a16da27709c05", size = 80812, upload-time = "2024-12-15T22:24:26.318Z" }, - { url = "https://files.pythonhosted.org/packages/90/99/cf0a3a8a661fffc7f6843cafbbc1887c47e1a79f751cf9c88002008c8eae/tree_sitter_c-0.23.4-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:98c285a23bf4fb6fb34140d6ea0f0d25d0a93e0d93692f9dffe3db6d1fe08534", size = 85813, upload-time = "2024-12-15T22:24:28.438Z" }, - { url = "https://files.pythonhosted.org/packages/01/c1/d346a08e05223bff3cea08a8f96d685d19bc2c022fde719bfd3e9f6aaaac/tree_sitter_c-0.23.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e42a3519825ca59c91b2b7aec08dd3c89e02690c7b315d54a1e1743f9be3f15", size = 110085, upload-time = "2024-12-15T22:24:30.823Z" }, - { url = "https://files.pythonhosted.org/packages/a8/88/b7d395038b109d42a4682b9f3d72f8e02de8f7c7caf9ad2b289991f1ac19/tree_sitter_c-0.23.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c15c7588c3d95872328019073a8d5eaf7c2691b4d4ef0393a0168399b2ad2356", size = 98075, upload-time = "2024-12-15T22:24:32.946Z" }, - { url = "https://files.pythonhosted.org/packages/e8/12/754a8166d3860cdd614bf7d117c94a740ce1ab1ab2ba766321249909e7b1/tree_sitter_c-0.23.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:013403e74765d74e523f380f9df8f3d99e9fe94132a3fc0c8b29cba538a7b2bf", size = 94071, upload-time = "2024-12-15T22:24:34.974Z" }, - { url = "https://files.pythonhosted.org/packages/14/da/2f97b96f081d6ac9b37c87c9d8e5c0ff5948802562ae28b1a58afd8dec1d/tree_sitter_c-0.23.4-cp39-abi3-win_amd64.whl", hash = "sha256:a4d7bdeaca8f1da72352a945853f56aa5d34e7bc22569ec5bda5d7c1a04e5b0f", size = 84483, upload-time = "2024-12-15T22:24:37.052Z" }, - { url = "https://files.pythonhosted.org/packages/d9/33/0d3b72634e2f34e64b07aaf100207cf3d01e32d814e72e144af0a0e785ad/tree_sitter_c-0.23.4-cp39-abi3-win_arm64.whl", hash = "sha256:edd36e12cc79b8b5bbc81fc336ff7d2577d0fe16afd18163c9aff7ae3ff69e15", size = 82482, upload-time = "2024-12-15T22:24:40.758Z" }, + { url = "https://files.pythonhosted.org/packages/07/e3/d9526ba71dfbbe4eba5e51d89432b4b333a49a1e70712aa5590cd22fc74f/tree_sitter-0.25.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:65d3c931013ea798b502782acab986bbf47ba2c452610ab0776cf4a8ef150fc0", size = 146776, upload-time = "2025-09-25T17:37:50.898Z" }, + { url = "https://files.pythonhosted.org/packages/42/97/4bd4ad97f85a23011dd8a535534bb1035c4e0bac1234d58f438e15cff51f/tree_sitter-0.25.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bda059af9d621918efb813b22fb06b3fe00c3e94079c6143fcb2c565eb44cb87", size = 137732, upload-time = "2025-09-25T17:37:51.877Z" }, + { url = "https://files.pythonhosted.org/packages/b6/19/1e968aa0b1b567988ed522f836498a6a9529a74aab15f09dd9ac1e41f505/tree_sitter-0.25.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eac4e8e4c7060c75f395feec46421eb61212cb73998dbe004b7384724f3682ab", size = 609456, upload-time = "2025-09-25T17:37:52.925Z" }, + { url = "https://files.pythonhosted.org/packages/48/b6/cf08f4f20f4c9094006ef8828555484e842fc468827ad6e56011ab668dbd/tree_sitter-0.25.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:260586381b23be33b6191a07cea3d44ecbd6c01aa4c6b027a0439145fcbc3358", size = 636772, upload-time = "2025-09-25T17:37:54.647Z" }, + { url = "https://files.pythonhosted.org/packages/57/e2/d42d55bf56360987c32bc7b16adb06744e425670b823fb8a5786a1cea991/tree_sitter-0.25.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7d2ee1acbacebe50ba0f85fff1bc05e65d877958f00880f49f9b2af38dce1af0", size = 631522, upload-time = "2025-09-25T17:37:55.833Z" }, + { url = "https://files.pythonhosted.org/packages/03/87/af9604ebe275a9345d88c3ace0cf2a1341aa3f8ef49dd9fc11662132df8a/tree_sitter-0.25.2-cp314-cp314-win_amd64.whl", hash = "sha256:4973b718fcadfb04e59e746abfbb0288694159c6aeecd2add59320c03368c721", size = 130864, upload-time = "2025-09-25T17:37:57.453Z" }, + { url = "https://files.pythonhosted.org/packages/a6/6e/e64621037357acb83d912276ffd30a859ef117f9c680f2e3cb955f47c680/tree_sitter-0.25.2-cp314-cp314-win_arm64.whl", hash = "sha256:b8d4429954a3beb3e844e2872610d2a4800ba4eb42bb1990c6a4b1949b18459f", size = 117470, upload-time = "2025-09-25T17:37:58.431Z" }, ] [[package]] name = "tree-sitter-rust" -version = "0.23.2" +version = "0.24.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fd/bf/ff80aef689875cce54f1b80729c703af162b03ddd20b9829c53453dd43db/tree_sitter_rust-0.23.2.tar.gz", hash = "sha256:9088a0e0342d3de2749088811f5561994423cb10dab5ad3251003dffaa0a1bd1", size = 318912, upload-time = "2024-11-24T18:52:27.724Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/ae/fde1ab896f3d79205add86749f6f443537f59c747616a8fc004c7a453c29/tree_sitter_rust-0.24.0.tar.gz", hash = "sha256:c7185f482717bd41f24ffcd90b5ee24e7e0d6334fecce69f1579609994cd599d", size = 335850, upload-time = "2025-04-01T21:06:03.522Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/55/05/f5c50201de4c959a8fdc1edbc29ea541a96cbe4fb794e36040bc8207ea39/tree_sitter_rust-0.23.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b6b26a4c07ddc243f3701450ff34093b8e3b08f14d269db2d049c625d151677c", size = 123482, upload-time = "2024-11-24T18:52:18.391Z" }, - { url = "https://files.pythonhosted.org/packages/be/5a/8f5b0e1e7dbe4994b692af989d10aaa295cf374ae0b8fe019341c9a2d70a/tree_sitter_rust-0.23.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:c6224f608df559d75425e5ef428f635b9fb87d7aa8716444915ee67ec6955085", size = 130906, upload-time = "2024-11-24T18:52:20.545Z" }, - { url = "https://files.pythonhosted.org/packages/00/51/ebe563835da804af4dbab5c94b5887601be60788e3caca55b140ef988bcc/tree_sitter_rust-0.23.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deced590a85ce848cda56f33728bad93b95827c1e3c736b707b24fb4280b3788", size = 159109, upload-time = "2024-11-24T18:52:21.474Z" }, - { url = "https://files.pythonhosted.org/packages/22/85/9b137393d39279d24acbb5ff69e6103a3793b988f11c2ab3c06e3b23a09b/tree_sitter_rust-0.23.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:540cf826932fe7cfd800361e368617e138c3d7914fad3b90786b7505af216be6", size = 157641, upload-time = "2024-11-24T18:52:22.597Z" }, - { url = "https://files.pythonhosted.org/packages/bf/a0/ee88affed29ec9e9b3d84663f4b700d55f68ab6e7ba69b1afc37982d5390/tree_sitter_rust-0.23.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:880be40b220e87105b60db48c57cdd8019b5039b324afb1d643fa9c2fc187873", size = 154858, upload-time = "2024-11-24T18:52:23.58Z" }, - { url = "https://files.pythonhosted.org/packages/c2/e3/7c9eed5a9ee61997be5b9594a838d38b708abab0332754d67ce9b650c5c9/tree_sitter_rust-0.23.2-cp39-abi3-win_amd64.whl", hash = "sha256:d8e0bea4fd76fc8b325247f3d1bb3dc2707db7dd3818b02c251efdea1b47909c", size = 121448, upload-time = "2024-11-24T18:52:25.117Z" }, - { url = "https://files.pythonhosted.org/packages/3e/43/35a8da20e7a897eb33f39483a626bf21d87bc38c4effeb663a58a42c6fc3/tree_sitter_rust-0.23.2-cp39-abi3-win_arm64.whl", hash = "sha256:3ea49daa887ad59230758e7a96432193af4a2c7183781e1e85c35d4f8cb30b6b", size = 119579, upload-time = "2024-11-24T18:52:26.762Z" }, + { url = "https://files.pythonhosted.org/packages/3c/29/0594a6b135d2475d1bb8478029dad127b87856eeb13b23ce55984dd22bb4/tree_sitter_rust-0.24.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7ea455443f5ab245afd8c5ce63a8ae38da455ef27437b459ce3618a9d4ec4f9a", size = 131884, upload-time = "2025-04-01T21:05:56.35Z" }, + { url = "https://files.pythonhosted.org/packages/bf/00/4c400fe94eb3cb141b008b489d582dcd8b41e4168aca5dd8746c47a2b1bc/tree_sitter_rust-0.24.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:a0a1a2694117a0e86e156b28ee7def810ec94e52402069bf805be22d43e3c1a1", size = 137904, upload-time = "2025-04-01T21:05:57.743Z" }, + { url = "https://files.pythonhosted.org/packages/f3/4d/c5eb85a68a2115d9f5c23fa5590a28873c4cf3b4e17c536ff0cb098e1a91/tree_sitter_rust-0.24.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3362992ea3150b0dd15577dd59caef4f2926b6e10806f2bb4f2533485acee2f", size = 166554, upload-time = "2025-04-01T21:05:58.965Z" }, + { url = "https://files.pythonhosted.org/packages/ba/72/8ee8cf2bd51bc402531da7d8741838a4ea632b46a8c1e2df9968c7326cc7/tree_sitter_rust-0.24.0-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2c1f4b87df568352a9e523600af7cb32c5748dc75275f4794d6f811ab13dfe", size = 165457, upload-time = "2025-04-01T21:05:59.939Z" }, + { url = "https://files.pythonhosted.org/packages/74/d1/389eecb15c3f8ef4c947fcfbcc794ef4036b3b892c0f981e110860371daa/tree_sitter_rust-0.24.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:615f989241b717f14105b1bc621ff0c2200c86f1c3b36f1842d61f6605021152", size = 162857, upload-time = "2025-04-01T21:06:00.835Z" }, + { url = "https://files.pythonhosted.org/packages/b9/df/a6321043d6dee313e5fa3b6a13384119d590393368134cf12f2ee7f9e664/tree_sitter_rust-0.24.0-cp39-abi3-win_amd64.whl", hash = "sha256:2e29be0292eaf1f99389b3af4281f92187612af31ba129e90f4755f762993441", size = 130052, upload-time = "2025-04-01T21:06:01.743Z" }, + { url = "https://files.pythonhosted.org/packages/c8/33/70b320d24cd127d6ca427d2bef1279830f0786a1f2cde160f59b4fb80728/tree_sitter_rust-0.24.0-cp39-abi3-win_arm64.whl", hash = "sha256:7a0538eaf4063b443c6cd80a47df19249f65e27dbdf129396a9193749912d0c0", size = 128583, upload-time = "2025-04-01T21:06:02.58Z" }, ] [[package]] @@ -1420,25 +1412,25 @@ wheels = [ [[package]] name = "urllib3" -version = "2.6.2" +version = "2.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" }, + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] [[package]] name = "virtualenv" -version = "20.35.4" +version = "20.36.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/20/28/e6f1a6f655d620846bd9df527390ecc26b3805a0c5989048c210e22c5ca9/virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c", size = 6028799, upload-time = "2025-10-29T06:57:40.511Z" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/a3/4d310fa5f00863544e1d0f4de93bddec248499ccf97d4791bc3122c9d4f3/virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba", size = 6032239, upload-time = "2026-01-09T18:21:01.296Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b", size = 6005095, upload-time = "2025-10-29T06:57:37.598Z" }, + { url = "https://files.pythonhosted.org/packages/6a/2a/dc2228b2888f51192c7dc766106cd475f1b768c10caaf9727659726f7391/virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f", size = 6008258, upload-time = "2026-01-09T18:20:59.425Z" }, ] [[package]]