From d73daba5fd6eca84b39256b705822dca80ed2088 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Mon, 2 Mar 2026 22:42:07 -0500 Subject: [PATCH 1/3] feat: add foundation for language optimizer extraction from main Bring additive changes from main into omni-java: - LanguageSupport protocol: new methods/properties for language-specific optimizers - PythonFunctionOptimizer and JavaScriptFunctionOptimizer subclasses - Python/JS normalizers (moved from code_utils/normalizers/) - Python/JS optimizer helpers (AST resolution, module prep) - Mocha test runner for JavaScript - Config constants for token limit errors --- codeflash/code_utils/config_consts.py | 2 + codeflash/languages/base.py | 114 +++- .../javascript/function_optimizer.py | 228 +++++++ .../languages/javascript/mocha_runner.py | 597 ++++++++++++++++++ codeflash/languages/javascript/normalizer.py | 257 ++++++++ codeflash/languages/javascript/optimizer.py | 52 ++ .../languages/python/function_optimizer.py | 215 +++++++ codeflash/languages/python/normalizer.py | 180 ++++++ codeflash/languages/python/optimizer.py | 63 ++ 9 files changed, 1707 insertions(+), 1 deletion(-) create mode 100644 codeflash/languages/javascript/function_optimizer.py create mode 100644 codeflash/languages/javascript/mocha_runner.py create mode 100644 codeflash/languages/javascript/normalizer.py create mode 100644 codeflash/languages/javascript/optimizer.py create mode 100644 codeflash/languages/python/function_optimizer.py create mode 100644 codeflash/languages/python/normalizer.py create mode 100644 codeflash/languages/python/optimizer.py diff --git a/codeflash/code_utils/config_consts.py b/codeflash/code_utils/config_consts.py index 73af5607e..5a2d1800b 100644 --- a/codeflash/code_utils/config_consts.py +++ b/codeflash/code_utils/config_consts.py @@ -6,6 +6,8 @@ MAX_TEST_RUN_ITERATIONS = 5 OPTIMIZATION_CONTEXT_TOKEN_LIMIT = 64000 TESTGEN_CONTEXT_TOKEN_LIMIT = 64000 +READ_WRITABLE_LIMIT_ERROR = "Read-writable code has exceeded token limit, cannot proceed" +TESTGEN_LIMIT_ERROR = "Testgen code context has exceeded token limit, cannot proceed" INDIVIDUAL_TESTCASE_TIMEOUT = 15 # For Python pytest JAVA_TESTCASE_TIMEOUT = 120 # Java Maven tests need more time due to startup overhead MAX_FUNCTION_TEST_SECONDS = 60 diff --git a/codeflash/languages/base.py b/codeflash/languages/base.py index 60aa064b2..92ae95e63 100644 --- a/codeflash/languages/base.py +++ b/codeflash/languages/base.py @@ -13,11 +13,13 @@ from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable if TYPE_CHECKING: + import ast from collections.abc import Callable, Iterable, Sequence from pathlib import Path from codeflash.discovery.functions_to_optimize import FunctionToOptimize - from codeflash.models.models import FunctionSource, GeneratedTestsList, InvocationId + from codeflash.models.models import FunctionSource, GeneratedTestsList, InvocationId, ValidCode + from codeflash.verification.verification_utils import TestConfig from codeflash.languages.language_enum import Language from codeflash.models.function_types import FunctionParent @@ -322,6 +324,78 @@ def dir_excludes(self) -> frozenset[str]: """ ... + @property + def default_language_version(self) -> str | None: + """Default language version string sent to AI service. + + Returns None for languages where the runtime version is auto-detected (e.g. Python). + Returns a version string (e.g. "ES2022") for languages that need an explicit default. + """ + return None + + @property + def valid_test_frameworks(self) -> tuple[str, ...]: + """Valid test frameworks for this language.""" + ... + + @property + def test_result_serialization_format(self) -> str: + """How test return values are serialized: "pickle" or "json".""" + return "pickle" + + def load_coverage( + self, + coverage_database_file: Path, + function_name: str, + code_context: Any, + source_file: Path, + coverage_config_file: Path | None = None, + ) -> Any: + """Load coverage data from language-specific format. + + Returns a CoverageData instance. + """ + ... + + @property + def function_optimizer_class(self) -> type: + """Return the FunctionOptimizer subclass for this language.""" + from codeflash.optimization.function_optimizer import FunctionOptimizer + + return FunctionOptimizer + + def prepare_module( + self, module_code: str, module_path: Path, project_root: Path + ) -> tuple[dict[Path, ValidCode], ast.Module | None] | None: + """Parse/validate a module before optimization.""" + ... + + def setup_test_config(self, test_cfg: TestConfig, file_path: Path) -> None: + """One-time project setup after language detection. Default: no-op.""" + + def adjust_test_config_for_discovery(self, test_cfg: TestConfig) -> None: + """Adjust test config before test discovery. Default: no-op.""" + + def detect_module_system(self, project_root: Path, source_file: Path) -> str | None: + """Detect the module system used by the project. Default: None (not applicable).""" + return None + + def process_generated_test_strings( + self, + generated_test_source: str, + instrumented_behavior_test_source: str, + instrumented_perf_test_source: str, + function_to_optimize: FunctionToOptimize, + test_path: Path, + test_cfg: Any, + project_module_system: str | None, + ) -> tuple[str, str, str]: + """Process raw generated test strings (instrumentation, placeholder replacement, etc.). + + Returns (generated_test_source, instrumented_behavior_source, instrumented_perf_source). + """ + ... + # === Discovery === def discover_functions( @@ -754,6 +828,44 @@ def parse_line_profile_results(self, line_profiler_output_file: Path) -> dict: # === Test Execution === + def generate_concolic_tests( + self, + test_cfg: TestConfig, + project_root: Path, + function_to_optimize: FunctionToOptimize, + function_to_optimize_ast: Any, + ) -> tuple[dict, str]: + """Generate concolic tests for a function. + + Default implementation returns empty results. Override for languages + that support concolic testing (e.g. Python via CrossHair). + """ + return {}, "" + + def run_line_profile_tests( + self, + test_paths: Any, + test_env: dict[str, str], + cwd: Path, + timeout: int | None = None, + project_root: Path | None = None, + line_profile_output_file: Path | None = None, + ) -> tuple[Path, Any]: + """Run tests for line profiling. + + Args: + test_paths: TestFiles object containing test file information. + test_env: Environment variables for the test run. + cwd: Working directory for running tests. + timeout: Optional timeout in seconds. + project_root: Project root directory. + line_profile_output_file: Path where line profile results will be written. + + Returns: + Tuple of (result_file_path, subprocess_result). + """ + ... + def run_behavioral_tests( self, test_paths: Any, diff --git a/codeflash/languages/javascript/function_optimizer.py b/codeflash/languages/javascript/function_optimizer.py new file mode 100644 index 000000000..59cf502ce --- /dev/null +++ b/codeflash/languages/javascript/function_optimizer.py @@ -0,0 +1,228 @@ +from __future__ import annotations + +import hashlib +from collections import defaultdict +from pathlib import Path +from typing import TYPE_CHECKING, Any + +from codeflash.cli_cmds.console import logger +from codeflash.code_utils.code_utils import encoded_tokens_len, get_run_tmp_file +from codeflash.code_utils.config_consts import ( + OPTIMIZATION_CONTEXT_TOKEN_LIMIT, + READ_WRITABLE_LIMIT_ERROR, + TESTGEN_CONTEXT_TOKEN_LIMIT, + TESTGEN_LIMIT_ERROR, + TOTAL_LOOPING_TIME_EFFECTIVE, +) +from codeflash.either import Failure, Success +from codeflash.models.models import ( + CodeOptimizationContext, + CodeString, + CodeStringsMarkdown, + FunctionSource, + TestingMode, + TestResults, +) +from codeflash.optimization.function_optimizer import FunctionOptimizer +from codeflash.verification.equivalence import compare_test_results + +if TYPE_CHECKING: + from codeflash.either import Result + from codeflash.languages.base import CodeContext, HelperFunction + from codeflash.models.models import CoverageData, OriginalCodeBaseline, TestDiff + + +class JavaScriptFunctionOptimizer(FunctionOptimizer): + def get_code_optimization_context(self) -> Result[CodeOptimizationContext, str]: + from codeflash.languages import get_language_support + from codeflash.languages.base import Language + + language = Language(self.function_to_optimize.language) + lang_support = get_language_support(language) + + try: + code_context = lang_support.extract_code_context( + self.function_to_optimize, self.project_root, self.project_root + ) + return Success( + self._build_optimization_context( + code_context, + self.function_to_optimize.file_path, + self.function_to_optimize.language, + self.project_root, + ) + ) + except ValueError as e: + return Failure(str(e)) + + @staticmethod + def _build_optimization_context( + code_context: CodeContext, + file_path: Path, + language: str, + project_root: Path, + optim_token_limit: int = OPTIMIZATION_CONTEXT_TOKEN_LIMIT, + testgen_token_limit: int = TESTGEN_CONTEXT_TOKEN_LIMIT, + ) -> CodeOptimizationContext: + imports_code = "\n".join(code_context.imports) if code_context.imports else "" + + try: + target_relative_path = file_path.resolve().relative_to(project_root.resolve()) + except ValueError: + target_relative_path = file_path + + helpers_by_file: dict[Path, list[HelperFunction]] = defaultdict(list) + helper_function_sources = [] + + for helper in code_context.helper_functions: + helpers_by_file[helper.file_path].append(helper) + helper_function_sources.append( + FunctionSource( + file_path=helper.file_path, + qualified_name=helper.qualified_name, + fully_qualified_name=helper.qualified_name, + only_function_name=helper.name, + source_code=helper.source_code, + ) + ) + + target_file_code = code_context.target_code + same_file_helpers = helpers_by_file.get(file_path, []) + if same_file_helpers: + helper_code = "\n\n".join(h.source_code for h in same_file_helpers) + target_file_code = target_file_code + "\n\n" + helper_code + + if imports_code: + target_file_code = imports_code + "\n\n" + target_file_code + + read_writable_code_strings = [ + CodeString(code=target_file_code, file_path=target_relative_path, language=language) + ] + + for helper_file_path, file_helpers in helpers_by_file.items(): + if helper_file_path == file_path: + continue + try: + helper_relative_path = helper_file_path.resolve().relative_to(project_root.resolve()) + except ValueError: + helper_relative_path = helper_file_path + combined_helper_code = "\n\n".join(h.source_code for h in file_helpers) + read_writable_code_strings.append( + CodeString(code=combined_helper_code, file_path=helper_relative_path, language=language) + ) + + read_writable_code = CodeStringsMarkdown(code_strings=read_writable_code_strings, language=language) + testgen_context = CodeStringsMarkdown(code_strings=read_writable_code_strings.copy(), language=language) + + read_writable_tokens = encoded_tokens_len(read_writable_code.markdown) + if read_writable_tokens > optim_token_limit: + raise ValueError(READ_WRITABLE_LIMIT_ERROR) + + testgen_tokens = encoded_tokens_len(testgen_context.markdown) + if testgen_tokens > testgen_token_limit: + raise ValueError(TESTGEN_LIMIT_ERROR) + + code_hash = hashlib.sha256(read_writable_code.flat.encode("utf-8")).hexdigest() + + return CodeOptimizationContext( + testgen_context=testgen_context, + read_writable_code=read_writable_code, + read_only_context_code=code_context.read_only_context, + hashing_code_context=read_writable_code.flat, + hashing_code_context_hash=code_hash, + helper_functions=helper_function_sources, + testgen_helper_fqns=[fs.fully_qualified_name for fs in helper_function_sources], + preexisting_objects=set(), + ) + + def compare_candidate_results( + self, + baseline_results: OriginalCodeBaseline, + candidate_behavior_results: TestResults, + optimization_candidate_index: int, + ) -> tuple[bool, list[TestDiff]]: + original_sqlite = get_run_tmp_file(Path("test_return_values_0.sqlite")) + candidate_sqlite = get_run_tmp_file(Path(f"test_return_values_{optimization_candidate_index}.sqlite")) + + if original_sqlite.exists() and candidate_sqlite.exists(): + js_root = self.test_cfg.js_project_root or self.project_root + match, diffs = self.language_support.compare_test_results( + original_sqlite, candidate_sqlite, project_root=js_root + ) + candidate_sqlite.unlink(missing_ok=True) + else: + match, diffs = compare_test_results( + baseline_results.behavior_test_results, candidate_behavior_results, pass_fail_only=True + ) + return match, diffs + + def should_skip_sqlite_cleanup(self, testing_type: TestingMode, optimization_iteration: int) -> bool: + return testing_type == TestingMode.BEHAVIOR or optimization_iteration == 0 + + def parse_line_profile_test_results( + self, line_profiler_output_file: Path | None + ) -> tuple[TestResults | dict[str, Any], CoverageData | None]: + if line_profiler_output_file is None or not line_profiler_output_file.exists(): + return TestResults(test_results=[]), None + if hasattr(self.language_support, "parse_line_profile_results"): + return self.language_support.parse_line_profile_results(line_profiler_output_file), None + return TestResults(test_results=[]), None + + def line_profiler_step( + self, code_context: CodeOptimizationContext, original_helper_code: dict[Path, str], candidate_index: int + ) -> dict[str, Any]: + if not hasattr(self.language_support, "instrument_source_for_line_profiler"): + logger.warning(f"Language support for {self.language_support.language} doesn't support line profiling") + return {"timings": {}, "unit": 0, "str_out": ""} + + original_source = self.function_to_optimize.file_path.read_text(encoding="utf-8") + try: + line_profiler_output_path = get_run_tmp_file(Path("line_profiler_output.json")) + + success = self.language_support.instrument_source_for_line_profiler( + func_info=self.function_to_optimize, line_profiler_output_file=line_profiler_output_path + ) + if not success: + return {"timings": {}, "unit": 0, "str_out": ""} + + test_env = self.get_test_env( + codeflash_loop_index=0, codeflash_test_iteration=candidate_index, codeflash_tracer_disable=1 + ) + + _test_results, _ = self.run_and_parse_tests( + testing_type=TestingMode.LINE_PROFILE, + test_env=test_env, + test_files=self.test_files, + optimization_iteration=0, + testing_time=TOTAL_LOOPING_TIME_EFFECTIVE, + enable_coverage=False, + code_context=code_context, + line_profiler_output_file=line_profiler_output_path, + ) + + return self.language_support.parse_line_profile_results(line_profiler_output_path) + except Exception as e: + logger.warning(f"Failed to run line profiling: {e}") + return {"timings": {}, "unit": 0, "str_out": ""} + finally: + self.function_to_optimize.file_path.write_text(original_source, encoding="utf-8") + + def replace_function_and_helpers_with_optimized_code( + self, + code_context: CodeOptimizationContext, + optimized_code: CodeStringsMarkdown, + original_helper_code: dict[Path, str], + ) -> bool: + from codeflash.languages.code_replacer import replace_function_definitions_for_language + + did_update = False + for module_abspath, qualified_names in self.group_functions_by_file(code_context).items(): + did_update |= replace_function_definitions_for_language( + function_names=list(qualified_names), + optimized_code=optimized_code, + module_abspath=module_abspath, + project_root_path=self.project_root, + lang_support=self.language_support, + function_to_optimize=self.function_to_optimize, + ) + return did_update diff --git a/codeflash/languages/javascript/mocha_runner.py b/codeflash/languages/javascript/mocha_runner.py new file mode 100644 index 000000000..5c288d67b --- /dev/null +++ b/codeflash/languages/javascript/mocha_runner.py @@ -0,0 +1,597 @@ +"""Mocha test runner for JavaScript/TypeScript. + +This module provides functions for running Mocha tests for behavioral +verification and performance benchmarking. Uses Mocha's built-in JSON reporter +and converts the output to JUnit XML in Python, avoiding extra npm dependencies. +""" + +from __future__ import annotations + +import json +import subprocess +import time +from pathlib import Path +from typing import TYPE_CHECKING +from xml.etree.ElementTree import Element, SubElement, tostring + +from codeflash.cli_cmds.console import logger +from codeflash.cli_cmds.init_javascript import get_package_install_command +from codeflash.code_utils.code_utils import get_run_tmp_file +from codeflash.code_utils.shell_utils import get_cross_platform_subprocess_run_args + +if TYPE_CHECKING: + from codeflash.models.models import TestFiles + + +def _find_mocha_project_root(file_path: Path) -> Path | None: + """Find the Mocha project root by looking for .mocharc.* or package.json. + + Traverses up from the given file path to find the directory containing + a Mocha config file. Falls back to package.json if no Mocha config is found. + + Args: + file_path: A file path within the Mocha project. + + Returns: + The project root directory, or None if not found. + + """ + current = file_path.parent if file_path.is_file() else file_path + package_json_dir = None + + mocha_config_names = ( + ".mocharc.yml", + ".mocharc.yaml", + ".mocharc.json", + ".mocharc.js", + ".mocharc.cjs", + ".mocharc.mjs", + ) + + while current != current.parent: + if any((current / cfg).exists() for cfg in mocha_config_names): + return current + if package_json_dir is None and (current / "package.json").exists(): + package_json_dir = current + current = current.parent + + return package_json_dir + + +def _ensure_runtime_files(project_root: Path) -> None: + """Ensure JavaScript runtime package is installed in the project. + + Installs codeflash package if not already present. + The package provides all runtime files needed for test instrumentation. + + Args: + project_root: The project root directory. + + """ + node_modules_pkg = project_root / "node_modules" / "codeflash" + if node_modules_pkg.exists(): + logger.debug("codeflash already installed") + return + + install_cmd = get_package_install_command(project_root, "codeflash", dev=True) + try: + result = subprocess.run(install_cmd, check=False, cwd=project_root, capture_output=True, text=True, timeout=120) + if result.returncode == 0: + logger.debug(f"Installed codeflash using {install_cmd[0]}") + return + logger.warning(f"Failed to install codeflash: {result.stderr}") + except Exception as e: + logger.warning(f"Error installing codeflash: {e}") + + logger.error(f"Could not install codeflash. Please install it manually: {' '.join(install_cmd)}") + + +def mocha_json_to_junit_xml(json_str: str, output_file: Path) -> None: + """Convert Mocha's JSON reporter output to JUnit XML. + + Mocha JSON format: + { stats: {...}, tests: [...], failures: [...], passes: [...], pending: [...] } + + Each test object has: fullTitle, title, duration, err, ... + + Args: + json_str: JSON string from Mocha's --reporter json output. + output_file: Path to write the JUnit XML file. + + """ + try: + data = json.loads(json_str) + except json.JSONDecodeError: + logger.warning("Failed to parse Mocha JSON output") + # Write a minimal empty JUnit XML so downstream parsing doesn't break + output_file.write_text('\n\n') + return + + tests = data.get("tests", []) + stats = data.get("stats", {}) + + testsuites = Element("testsuites") + testsuites.set("tests", str(stats.get("tests", len(tests)))) + testsuites.set("failures", str(stats.get("failures", 0))) + testsuites.set("time", str((stats.get("duration", 0) or 0) / 1000.0)) + + # Group tests by suite (parent describe block) + suites: dict[str, list[dict]] = {} + for test in tests: + full_title = test.get("fullTitle", "") + title = test.get("title", "") + # Suite name = fullTitle minus the test's own title + suite_name = full_title[: -len(title)].strip() if title and full_title.endswith(title) else "root" + suite_name = suite_name or "root" + suites.setdefault(suite_name, []).append(test) + + for suite_name, suite_tests in suites.items(): + testsuite = SubElement(testsuites, "testsuite") + testsuite.set("name", suite_name) + testsuite.set("tests", str(len(suite_tests))) + + suite_failures = 0 + suite_time = 0.0 + + for test in suite_tests: + testcase = SubElement(testsuite, "testcase") + testcase.set("classname", suite_name) + testcase.set("name", test.get("title", "unknown")) + duration_ms = test.get("duration", 0) or 0 + duration_s = duration_ms / 1000.0 + testcase.set("time", str(duration_s)) + suite_time += duration_s + + err = test.get("err", {}) + if err and err.get("message"): + suite_failures += 1 + failure = SubElement(testcase, "failure") + failure.set("message", err.get("message", "")) + failure.text = err.get("stack", err.get("message", "")) + + if test.get("pending"): + skipped = SubElement(testcase, "skipped") + skipped.set("message", "pending") + + testsuite.set("failures", str(suite_failures)) + testsuite.set("time", str(suite_time)) + + xml_bytes = tostring(testsuites, encoding="unicode") + output_file.write_text(f'\n{xml_bytes}\n') + + +def _extract_mocha_json(stdout: str) -> str | None: + """Extract Mocha JSON output from stdout that may contain mixed content. + + Mocha's JSON reporter writes the JSON blob to stdout, but other output + (console.log from tests, codeflash markers) may be interleaved. + We look for the JSON object by finding the outermost { ... } that + contains the expected "stats" key. + + Args: + stdout: Full stdout from the Mocha subprocess. + + Returns: + The extracted JSON string, or None if not found. + + """ + # Try the whole stdout first + stripped = stdout.strip() + if stripped.startswith("{") and '"stats"' in stripped: + try: + json.loads(stripped) + return stripped + except json.JSONDecodeError: + pass + + # Find the outermost JSON object containing "stats" + depth = 0 + start = None + for i, ch in enumerate(stdout): + if ch == "{": + if depth == 0: + start = i + depth += 1 + elif ch == "}": + depth -= 1 + if depth == 0 and start is not None: + candidate = stdout[start : i + 1] + if '"stats"' in candidate: + try: + json.loads(candidate) + return candidate + except json.JSONDecodeError: + pass + start = None + + return None + + +def _build_mocha_behavioral_command( + test_files: list[Path], timeout: int | None = None, project_root: Path | None = None +) -> list[str]: + """Build Mocha command for behavioral tests. + + Args: + test_files: List of test files to run. + timeout: Optional timeout in seconds (converted to ms for Mocha). + project_root: Project root directory. + + Returns: + Command list for subprocess execution. + + """ + cmd = ["npx", "mocha", "--reporter", "json", "--jobs", "1", "--exit"] + + if timeout: + cmd.extend(["--timeout", str(timeout * 1000)]) + else: + cmd.extend(["--timeout", "60000"]) + + cmd.extend(str(f.resolve()) for f in test_files) + + return cmd + + +def _build_mocha_benchmarking_command( + test_files: list[Path], timeout: int | None = None, project_root: Path | None = None +) -> list[str]: + """Build Mocha command for benchmarking tests. + + Args: + test_files: List of test files to run. + timeout: Optional timeout in seconds (converted to ms for Mocha). + project_root: Project root directory. + + Returns: + Command list for subprocess execution. + + """ + cmd = ["npx", "mocha", "--reporter", "json", "--jobs", "1", "--exit"] + + if timeout: + cmd.extend(["--timeout", str(timeout * 1000)]) + else: + cmd.extend(["--timeout", "120000"]) + + cmd.extend(str(f.resolve()) for f in test_files) + + return cmd + + +def _build_mocha_line_profile_command( + test_files: list[Path], timeout: int | None = None, project_root: Path | None = None +) -> list[str]: + """Build Mocha command for line profiling tests. + + Args: + test_files: List of test files to run. + timeout: Optional timeout in seconds (converted to ms for Mocha). + project_root: Project root directory. + + Returns: + Command list for subprocess execution. + + """ + cmd = ["npx", "mocha", "--reporter", "json", "--jobs", "1", "--exit"] + + if timeout: + cmd.extend(["--timeout", str(timeout * 1000)]) + else: + cmd.extend(["--timeout", "60000"]) + + cmd.extend(str(f.resolve()) for f in test_files) + + return cmd + + +def _run_mocha_and_convert( + mocha_cmd: list[str], + mocha_env: dict[str, str], + effective_cwd: Path, + result_file_path: Path, + subprocess_timeout: int, + label: str, +) -> subprocess.CompletedProcess: + """Run Mocha subprocess, extract JSON output, and convert to JUnit XML. + + Args: + mocha_cmd: Mocha command list. + mocha_env: Environment variables. + effective_cwd: Working directory. + result_file_path: Path to write JUnit XML. + subprocess_timeout: Timeout in seconds. + label: Label for log messages (e.g. "behavioral", "benchmarking"). + + Returns: + CompletedProcess with combined stdout/stderr. + + """ + try: + run_args = get_cross_platform_subprocess_run_args( + cwd=effective_cwd, env=mocha_env, timeout=subprocess_timeout, check=False, text=True, capture_output=True + ) + result = subprocess.run(mocha_cmd, **run_args) # noqa: PLW1510 + + # Combine stderr into stdout + stdout = result.stdout or "" + if result.stderr: + stdout = stdout + "\n" + result.stderr if stdout else result.stderr + + result = subprocess.CompletedProcess(args=result.args, returncode=result.returncode, stdout=stdout, stderr="") + + logger.debug(f"Mocha {label} result: returncode={result.returncode}") + if result.returncode != 0: + logger.warning( + f"Mocha {label} failed with returncode={result.returncode}.\n" + f"Command: {' '.join(mocha_cmd)}\n" + f"Stdout: {stdout[:2000] if stdout else '(empty)'}" + ) + + except subprocess.TimeoutExpired: + logger.warning(f"Mocha {label} tests timed out after {subprocess_timeout}s") + result = subprocess.CompletedProcess( + args=mocha_cmd, returncode=-1, stdout="", stderr=f"{label} tests timed out" + ) + except FileNotFoundError: + logger.error("Mocha not found. Make sure Mocha is installed (npm install mocha)") + result = subprocess.CompletedProcess( + args=mocha_cmd, returncode=-1, stdout="", stderr="Mocha not found. Run: npm install mocha" + ) + + # Extract Mocha JSON from stdout and convert to JUnit XML + if result.stdout: + mocha_json = _extract_mocha_json(result.stdout) + if mocha_json: + mocha_json_to_junit_xml(mocha_json, result_file_path) + logger.debug(f"Converted Mocha JSON to JUnit XML: {result_file_path}") + else: + logger.warning(f"Could not extract Mocha JSON from stdout (len={len(result.stdout)})") + result_file_path.write_text('\n\n') + else: + result_file_path.write_text('\n\n') + + return result + + +def run_mocha_behavioral_tests( + test_paths: TestFiles, + test_env: dict[str, str], + cwd: Path, + *, + timeout: int | None = None, + project_root: Path | None = None, + enable_coverage: bool = False, + candidate_index: int = 0, +) -> tuple[Path, subprocess.CompletedProcess, Path | None, Path | None]: + """Run Mocha tests and return results in a format compatible with pytest output. + + Args: + test_paths: TestFiles object containing test file information. + test_env: Environment variables for the test run. + cwd: Working directory for running tests. + timeout: Optional timeout in seconds. + project_root: Mocha project root (directory containing .mocharc.* or package.json). + enable_coverage: Whether to collect coverage information (not yet supported for Mocha). + candidate_index: Index of the candidate being tested. + + Returns: + Tuple of (result_file_path, subprocess_result, coverage_json_path, None). + + """ + result_file_path = get_run_tmp_file(Path("mocha_results.xml")) + + test_files = [Path(file.instrumented_behavior_file_path) for file in test_paths.test_files] + + if project_root is None and test_files: + project_root = _find_mocha_project_root(test_files[0]) + + effective_cwd = project_root if project_root else cwd + logger.debug(f"Mocha working directory: {effective_cwd}") + + _ensure_runtime_files(effective_cwd) + + mocha_cmd = _build_mocha_behavioral_command(test_files=test_files, timeout=timeout, project_root=effective_cwd) + + mocha_env = test_env.copy() + codeflash_sqlite_file = get_run_tmp_file(Path(f"test_return_values_{candidate_index}.sqlite")) + mocha_env["CODEFLASH_OUTPUT_FILE"] = str(codeflash_sqlite_file) + mocha_env["CODEFLASH_TEST_ITERATION"] = str(candidate_index) + mocha_env["CODEFLASH_LOOP_INDEX"] = "1" + mocha_env["CODEFLASH_MODE"] = "behavior" + mocha_env["CODEFLASH_RANDOM_SEED"] = "42" + + logger.debug(f"Running Mocha behavioral tests: {' '.join(mocha_cmd)}") + + subprocess_timeout = max(120, (timeout or 60) * 10) + + start_time_ns = time.perf_counter_ns() + try: + result = _run_mocha_and_convert( + mocha_cmd=mocha_cmd, + mocha_env=mocha_env, + effective_cwd=effective_cwd, + result_file_path=result_file_path, + subprocess_timeout=subprocess_timeout, + label="behavioral", + ) + finally: + wall_clock_ns = time.perf_counter_ns() - start_time_ns + logger.debug(f"Mocha behavioral tests completed in {wall_clock_ns / 1e9:.2f}s") + + if result_file_path.exists(): + file_size = result_file_path.stat().st_size + logger.debug(f"Mocha JUnit XML created: {result_file_path} ({file_size} bytes)") + else: + logger.warning(f"Mocha JUnit XML not created at {result_file_path}") + + return result_file_path, result, None, None + + +def run_mocha_benchmarking_tests( + test_paths: TestFiles, + test_env: dict[str, str], + cwd: Path, + *, + timeout: int | None = None, + project_root: Path | None = None, + min_loops: int = 5, + max_loops: int = 100, + target_duration_ms: int = 10_000, + stability_check: bool = True, +) -> tuple[Path, subprocess.CompletedProcess]: + """Run Mocha benchmarking tests with internal looping via capturePerf. + + Args: + test_paths: TestFiles object containing test file information. + test_env: Environment variables for the test run. + cwd: Working directory for running tests. + timeout: Optional timeout in seconds for the entire benchmark run. + project_root: Mocha project root. + min_loops: Minimum number of loop iterations. + max_loops: Maximum number of loop iterations. + target_duration_ms: Target total duration in milliseconds for all loops. + stability_check: Whether to enable stability-based early stopping. + + Returns: + Tuple of (result_file_path, subprocess_result with stdout from all iterations). + + """ + result_file_path = get_run_tmp_file(Path("mocha_perf_results.xml")) + + test_files = [Path(file.benchmarking_file_path) for file in test_paths.test_files if file.benchmarking_file_path] + + logger.debug( + f"Mocha benchmark test file selection: {len(test_files)}/{len(test_paths.test_files)} have benchmarking_file_path" + ) + if not test_files: + logger.warning("No perf test files found! Cannot run benchmarking tests.") + + if project_root is None and test_files: + project_root = _find_mocha_project_root(test_files[0]) + + effective_cwd = project_root if project_root else cwd + logger.debug(f"Mocha benchmarking working directory: {effective_cwd}") + + _ensure_runtime_files(effective_cwd) + + mocha_cmd = _build_mocha_benchmarking_command(test_files=test_files, timeout=timeout, project_root=effective_cwd) + + mocha_env = test_env.copy() + codeflash_sqlite_file = get_run_tmp_file(Path("test_return_values_0.sqlite")) + mocha_env["CODEFLASH_OUTPUT_FILE"] = str(codeflash_sqlite_file) + mocha_env["CODEFLASH_TEST_ITERATION"] = "0" + mocha_env["CODEFLASH_MODE"] = "performance" + mocha_env["CODEFLASH_RANDOM_SEED"] = "42" + + mocha_env["CODEFLASH_PERF_LOOP_COUNT"] = str(max_loops) + mocha_env["CODEFLASH_PERF_MIN_LOOPS"] = str(min_loops) + mocha_env["CODEFLASH_PERF_TARGET_DURATION_MS"] = str(target_duration_ms) + mocha_env["CODEFLASH_PERF_STABILITY_CHECK"] = "true" if stability_check else "false" + mocha_env["CODEFLASH_LOOP_INDEX"] = "1" + + if test_files: + test_module_path = str( + test_files[0].relative_to(effective_cwd) + if test_files[0].is_relative_to(effective_cwd) + else test_files[0].name + ) + mocha_env["CODEFLASH_TEST_MODULE"] = test_module_path + + total_timeout = max(120, (target_duration_ms // 1000) + 60, timeout or 120) + + logger.debug(f"Running Mocha benchmarking tests: {' '.join(mocha_cmd)}") + logger.debug( + f"Config: min_loops={min_loops}, max_loops={max_loops}, " + f"target_duration={target_duration_ms}ms, stability_check={stability_check}" + ) + + total_start_time = time.time() + try: + result = _run_mocha_and_convert( + mocha_cmd=mocha_cmd, + mocha_env=mocha_env, + effective_cwd=effective_cwd, + result_file_path=result_file_path, + subprocess_timeout=total_timeout, + label="benchmarking", + ) + finally: + wall_clock_seconds = time.time() - total_start_time + logger.debug(f"Mocha benchmarking completed in {wall_clock_seconds:.2f}s, returncode={result.returncode}") + + return result_file_path, result + + +def run_mocha_line_profile_tests( + test_paths: TestFiles, + test_env: dict[str, str], + cwd: Path, + *, + timeout: int | None = None, + project_root: Path | None = None, + line_profile_output_file: Path | None = None, +) -> tuple[Path, subprocess.CompletedProcess]: + """Run Mocha tests for line profiling. + + Args: + test_paths: TestFiles object containing test file information. + test_env: Environment variables for the test run. + cwd: Working directory for running tests. + timeout: Optional timeout in seconds for the subprocess. + project_root: Mocha project root. + line_profile_output_file: Path where line profile results will be written. + + Returns: + Tuple of (result_file_path, subprocess_result). + + """ + result_file_path = get_run_tmp_file(Path("mocha_line_profile_results.xml")) + + test_files = [] + for file in test_paths.test_files: + if file.instrumented_behavior_file_path: + test_files.append(Path(file.instrumented_behavior_file_path)) + elif file.benchmarking_file_path: + test_files.append(Path(file.benchmarking_file_path)) + + if project_root is None and test_files: + project_root = _find_mocha_project_root(test_files[0]) + + effective_cwd = project_root if project_root else cwd + logger.debug(f"Mocha line profiling working directory: {effective_cwd}") + + _ensure_runtime_files(effective_cwd) + + mocha_cmd = _build_mocha_line_profile_command(test_files=test_files, timeout=timeout, project_root=effective_cwd) + + mocha_env = test_env.copy() + codeflash_sqlite_file = get_run_tmp_file(Path("test_return_values_line_profile.sqlite")) + mocha_env["CODEFLASH_OUTPUT_FILE"] = str(codeflash_sqlite_file) + mocha_env["CODEFLASH_TEST_ITERATION"] = "0" + mocha_env["CODEFLASH_LOOP_INDEX"] = "1" + mocha_env["CODEFLASH_MODE"] = "line_profile" + mocha_env["CODEFLASH_RANDOM_SEED"] = "42" + + if line_profile_output_file: + mocha_env["CODEFLASH_LINE_PROFILE_OUTPUT"] = str(line_profile_output_file) + + subprocess_timeout = max(120, (timeout or 60) * 10) + + logger.debug(f"Running Mocha line profile tests: {' '.join(mocha_cmd)}") + + start_time_ns = time.perf_counter_ns() + try: + result = _run_mocha_and_convert( + mocha_cmd=mocha_cmd, + mocha_env=mocha_env, + effective_cwd=effective_cwd, + result_file_path=result_file_path, + subprocess_timeout=subprocess_timeout, + label="line_profile", + ) + finally: + wall_clock_ns = time.perf_counter_ns() - start_time_ns + logger.debug(f"Mocha line profile tests completed in {wall_clock_ns / 1e9:.2f}s") + + return result_file_path, result diff --git a/codeflash/languages/javascript/normalizer.py b/codeflash/languages/javascript/normalizer.py new file mode 100644 index 000000000..39ae952cb --- /dev/null +++ b/codeflash/languages/javascript/normalizer.py @@ -0,0 +1,257 @@ +"""JavaScript/TypeScript code normalizer using tree-sitter. + +Not currently wired into JavaScriptSupport.normalize_code — kept as a +ready-to-use upgrade path when AST-based JS deduplication is needed. + +The old CodeNormalizer ABC (deleted from base.py) is preserved below for reference. +""" + +from __future__ import annotations + +import re +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from tree_sitter import Node + + +# --------------------------------------------------------------------------- +# Reference: the old CodeNormalizer ABC that was deleted from base.py. +# Kept here so the interface contract is visible if we re-introduce a +# normalizer hierarchy later. +# --------------------------------------------------------------------------- +class CodeNormalizer(ABC): + @property + @abstractmethod + def language(self) -> str: ... + + @abstractmethod + def normalize(self, code: str) -> str: ... + + @abstractmethod + def normalize_for_hash(self, code: str) -> str: ... + + def are_duplicates(self, code1: str, code2: str) -> bool: + try: + return self.normalize_for_hash(code1) == self.normalize_for_hash(code2) + except Exception: + return False + + def get_fingerprint(self, code: str) -> str: + import hashlib + + return hashlib.sha256(self.normalize_for_hash(code).encode()).hexdigest() + + +# --------------------------------------------------------------------------- + + +class JavaScriptVariableNormalizer: + """Normalizes JavaScript/TypeScript code for duplicate detection using tree-sitter. + + Normalizes local variable names while preserving function names, class names, + parameters, and imported names. + """ + + def __init__(self) -> None: + self.var_counter = 0 + self.var_mapping: dict[str, str] = {} + self.preserved_names: set[str] = set() + # Common JavaScript builtins + self.builtins = { + "console", + "window", + "document", + "Math", + "JSON", + "Object", + "Array", + "String", + "Number", + "Boolean", + "Date", + "RegExp", + "Error", + "Promise", + "Map", + "Set", + "WeakMap", + "WeakSet", + "Symbol", + "Proxy", + "Reflect", + "undefined", + "null", + "NaN", + "Infinity", + "globalThis", + "parseInt", + "parseFloat", + "isNaN", + "isFinite", + "eval", + "setTimeout", + "setInterval", + "clearTimeout", + "clearInterval", + "fetch", + "require", + "module", + "exports", + "process", + "__dirname", + "__filename", + "Buffer", + } + + def get_normalized_name(self, name: str) -> str: + """Get or create normalized name for a variable.""" + if name in self.builtins or name in self.preserved_names: + return name + if name not in self.var_mapping: + self.var_mapping[name] = f"var_{self.var_counter}" + self.var_counter += 1 + return self.var_mapping[name] + + def collect_preserved_names(self, node: Node, source_code: bytes) -> None: + """Collect names that should be preserved (function names, class names, imports, params).""" + # Function declarations and expressions - preserve the function name + if node.type in ("function_declaration", "function_expression", "method_definition", "arrow_function"): + name_node = node.child_by_field_name("name") + if name_node: + self.preserved_names.add(source_code[name_node.start_byte : name_node.end_byte].decode("utf-8")) + # Preserve parameters + params_node = node.child_by_field_name("parameters") or node.child_by_field_name("parameter") + if params_node: + self._collect_parameter_names(params_node, source_code) + + # Class declarations + elif node.type == "class_declaration": + name_node = node.child_by_field_name("name") + if name_node: + self.preserved_names.add(source_code[name_node.start_byte : name_node.end_byte].decode("utf-8")) + + # Import declarations + elif node.type in ("import_statement", "import_declaration"): + for child in node.children: + if child.type == "import_clause": + self._collect_import_names(child, source_code) + elif child.type == "identifier": + self.preserved_names.add(source_code[child.start_byte : child.end_byte].decode("utf-8")) + + # Recurse + for child in node.children: + self.collect_preserved_names(child, source_code) + + def _collect_parameter_names(self, node: Node, source_code: bytes) -> None: + """Collect parameter names from a parameters node.""" + for child in node.children: + if child.type == "identifier": + self.preserved_names.add(source_code[child.start_byte : child.end_byte].decode("utf-8")) + elif child.type in ("required_parameter", "optional_parameter", "rest_parameter"): + pattern_node = child.child_by_field_name("pattern") + if pattern_node and pattern_node.type == "identifier": + self.preserved_names.add( + source_code[pattern_node.start_byte : pattern_node.end_byte].decode("utf-8") + ) + # Recurse for nested patterns + self._collect_parameter_names(child, source_code) + + def _collect_import_names(self, node: Node, source_code: bytes) -> None: + """Collect imported names from import clause.""" + for child in node.children: + if child.type == "identifier": + self.preserved_names.add(source_code[child.start_byte : child.end_byte].decode("utf-8")) + elif child.type == "import_specifier": + # Get the local name (alias or original) + alias_node = child.child_by_field_name("alias") + name_node = child.child_by_field_name("name") + if alias_node: + self.preserved_names.add(source_code[alias_node.start_byte : alias_node.end_byte].decode("utf-8")) + elif name_node: + self.preserved_names.add(source_code[name_node.start_byte : name_node.end_byte].decode("utf-8")) + self._collect_import_names(child, source_code) + + def normalize_tree(self, node: Node, source_code: bytes) -> str: + """Normalize the AST tree to a string representation for comparison.""" + parts: list[str] = [] + self._normalize_node(node, source_code, parts) + return " ".join(parts) + + def _normalize_node(self, node: Node, source_code: bytes, parts: list[str]) -> None: + """Recursively normalize a node.""" + # Skip comments + if node.type in ("comment", "line_comment", "block_comment"): + return + + # Handle identifiers - normalize variable names + if node.type == "identifier": + name = source_code[node.start_byte : node.end_byte].decode("utf-8") + normalized = self.get_normalized_name(name) + parts.append(normalized) + return + + # Handle type identifiers (TypeScript) - preserve as-is + if node.type == "type_identifier": + parts.append(source_code[node.start_byte : node.end_byte].decode("utf-8")) + return + + # Handle string literals - normalize to placeholder + if node.type in ("string", "template_string", "string_fragment"): + parts.append('"STR"') + return + + # Handle number literals - normalize to placeholder + if node.type == "number": + parts.append("NUM") + return + + # For leaf nodes, output the node type + if len(node.children) == 0: + text = source_code[node.start_byte : node.end_byte].decode("utf-8") + parts.append(text) + return + + # Output node type for structure + parts.append(f"({node.type}") + + # Recurse into children + for child in node.children: + self._normalize_node(child, source_code, parts) + + parts.append(")") + + +def _basic_normalize_js(code: str) -> str: + """Basic normalization: remove comments and normalize whitespace.""" + code = re.sub(r"//.*$", "", code, flags=re.MULTILINE) + code = re.sub(r"/\*.*?\*/", "", code, flags=re.DOTALL) + return " ".join(code.split()) + + +def normalize_js_code(code: str, typescript: bool = False) -> str: + """Normalize JavaScript/TypeScript code to a canonical form for comparison. + + Uses tree-sitter to parse and normalize variable names. Falls back to + basic comment/whitespace stripping if tree-sitter is unavailable or parsing fails. + + Not currently wired into JavaScriptSupport.normalize_code — kept as a + ready-to-use upgrade path when AST-based JS deduplication is needed. + """ + try: + from codeflash.languages.javascript.treesitter import TreeSitterAnalyzer, TreeSitterLanguage + + lang = TreeSitterLanguage.TYPESCRIPT if typescript else TreeSitterLanguage.JAVASCRIPT + analyzer = TreeSitterAnalyzer(lang) + tree = analyzer.parse(code) + + if tree.root_node.has_error: + return _basic_normalize_js(code) + + normalizer = JavaScriptVariableNormalizer() + source_bytes = code.encode("utf-8") + normalizer.collect_preserved_names(tree.root_node, source_bytes) + return normalizer.normalize_tree(tree.root_node, source_bytes) + except Exception: + return _basic_normalize_js(code) diff --git a/codeflash/languages/javascript/optimizer.py b/codeflash/languages/javascript/optimizer.py new file mode 100644 index 000000000..bc88786b1 --- /dev/null +++ b/codeflash/languages/javascript/optimizer.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from codeflash.cli_cmds.console import logger +from codeflash.models.models import ValidCode + +if TYPE_CHECKING: + from pathlib import Path + + from codeflash.verification.verification_utils import TestConfig + + +def prepare_javascript_module( + original_module_code: str, original_module_path: Path +) -> tuple[dict[Path, ValidCode], None]: + """Prepare a JavaScript/TypeScript module for optimization. + + Unlike Python, JS/TS doesn't need AST parsing or import analysis at this stage. + Returns a mapping of the file path to ValidCode with the source as-is. + """ + validated_original_code: dict[Path, ValidCode] = { + original_module_path: ValidCode(source_code=original_module_code, normalized_code=original_module_code) + } + return validated_original_code, None + + +def verify_js_requirements(test_cfg: TestConfig) -> None: + """Verify JavaScript/TypeScript requirements before optimization. + + Checks that Node.js, npm, and the test framework are available. + Logs warnings if requirements are not met but does not abort. + """ + from codeflash.languages import get_language_support + from codeflash.languages.base import Language + from codeflash.languages.test_framework import get_js_test_framework_or_default + + js_project_root = test_cfg.js_project_root + if not js_project_root: + return + + try: + js_support = get_language_support(Language.JAVASCRIPT) + test_framework = get_js_test_framework_or_default() + success, errors = js_support.verify_requirements(js_project_root, test_framework) + + if not success: + logger.warning("JavaScript requirements check found issues:") + for error in errors: + logger.warning(f" - {error}") + except Exception as e: + logger.debug(f"Failed to verify JS requirements: {e}") diff --git a/codeflash/languages/python/function_optimizer.py b/codeflash/languages/python/function_optimizer.py new file mode 100644 index 000000000..15babc6b6 --- /dev/null +++ b/codeflash/languages/python/function_optimizer.py @@ -0,0 +1,215 @@ +from __future__ import annotations + +import ast +from pathlib import Path +from typing import TYPE_CHECKING + +from codeflash.cli_cmds.console import console, logger +from codeflash.code_utils.config_consts import TOTAL_LOOPING_TIME_EFFECTIVE +from codeflash.either import Failure, Success +from codeflash.languages.python.context.unused_definition_remover import ( + detect_unused_helper_functions, + revert_unused_helper_functions, +) +from codeflash.languages.python.optimizer import resolve_python_function_ast +from codeflash.languages.python.static_analysis.code_extractor import get_opt_review_metrics, is_numerical_code +from codeflash.languages.python.static_analysis.code_replacer import ( + add_custom_marker_to_all_tests, + modify_autouse_fixture, +) +from codeflash.languages.python.static_analysis.line_profile_utils import add_decorator_imports, contains_jit_decorator +from codeflash.models.models import TestingMode, TestResults +from codeflash.optimization.function_optimizer import FunctionOptimizer +from codeflash.verification.parse_test_output import calculate_function_throughput_from_test_results + +if TYPE_CHECKING: + from typing import Any + + from codeflash.either import Result + from codeflash.languages.base import Language + from codeflash.models.function_types import FunctionParent + from codeflash.models.models import ( + CodeOptimizationContext, + CodeStringsMarkdown, + ConcurrencyMetrics, + CoverageData, + OriginalCodeBaseline, + TestDiff, + ) + + +class PythonFunctionOptimizer(FunctionOptimizer): + def get_code_optimization_context(self) -> Result[CodeOptimizationContext, str]: + from codeflash.languages.python.context import code_context_extractor + + try: + return Success( + code_context_extractor.get_code_optimization_context( + self.function_to_optimize, self.project_root, call_graph=self.call_graph + ) + ) + except ValueError as e: + return Failure(str(e)) + + def _resolve_function_ast( + self, source_code: str, function_name: str, parents: list[FunctionParent] + ) -> ast.FunctionDef | ast.AsyncFunctionDef | None: + original_module_ast = ast.parse(source_code) + return resolve_python_function_ast(function_name, parents, original_module_ast) + + def requires_function_ast(self) -> bool: + return True + + def analyze_code_characteristics(self, code_context: CodeOptimizationContext) -> None: + self.is_numerical_code = is_numerical_code(code_string=code_context.read_writable_code.flat) + + def get_optimization_review_metrics( + self, + source_code: str, + file_path: Path, + qualified_name: str, + project_root: Path, + tests_root: Path, + language: Language, + ) -> str: + return get_opt_review_metrics(source_code, file_path, qualified_name, project_root, tests_root, language) + + def instrument_test_fixtures(self, test_paths: list[Path]) -> dict[Path, list[str]] | None: + logger.info("Disabling all autouse fixtures associated with the generated test files") + original_conftest_content = modify_autouse_fixture(test_paths) + logger.info("Add custom marker to generated test files") + add_custom_marker_to_all_tests(test_paths) + return original_conftest_content + + def instrument_capture(self, file_path_to_helper_classes: dict[Path, set[str]]) -> None: + from codeflash.verification.instrument_codeflash_capture import instrument_codeflash_capture + + instrument_codeflash_capture(self.function_to_optimize, file_path_to_helper_classes, self.test_cfg.tests_root) + + def should_check_coverage(self) -> bool: + return True + + def collect_async_metrics( + self, + benchmarking_results: TestResults, + code_context: CodeOptimizationContext, + helper_code: dict[Path, str], + test_env: dict[str, str], + ) -> tuple[int | None, ConcurrencyMetrics | None]: + if not self.function_to_optimize.is_async: + return None, None + + async_throughput = calculate_function_throughput_from_test_results( + benchmarking_results, self.function_to_optimize.function_name + ) + logger.debug(f"Async function throughput: {async_throughput} calls/second") + + concurrency_metrics = self.run_concurrency_benchmark( + code_context=code_context, original_helper_code=helper_code, test_env=test_env + ) + if concurrency_metrics: + logger.debug( + f"Concurrency metrics: ratio={concurrency_metrics.concurrency_ratio:.2f}, " + f"seq={concurrency_metrics.sequential_time_ns}ns, conc={concurrency_metrics.concurrent_time_ns}ns" + ) + return async_throughput, concurrency_metrics + + def instrument_async_for_mode(self, mode: TestingMode) -> None: + from codeflash.code_utils.instrument_existing_tests import add_async_decorator_to_function + + add_async_decorator_to_function( + self.function_to_optimize.file_path, self.function_to_optimize, mode, project_root=self.project_root + ) + + def should_skip_sqlite_cleanup(self, testing_type: TestingMode, optimization_iteration: int) -> bool: + return False + + def parse_line_profile_test_results( + self, line_profiler_output_file: Path | None + ) -> tuple[TestResults | dict, CoverageData | None]: + from codeflash.verification.parse_line_profile_test_output import parse_line_profile_results + + return parse_line_profile_results(line_profiler_output_file=line_profiler_output_file) + + def compare_candidate_results( + self, + baseline_results: OriginalCodeBaseline, + candidate_behavior_results: TestResults, + optimization_candidate_index: int, + ) -> tuple[bool, list[TestDiff]]: + from codeflash.verification.equivalence import compare_test_results + + return compare_test_results(baseline_results.behavior_test_results, candidate_behavior_results) + + def replace_function_and_helpers_with_optimized_code( + self, + code_context: CodeOptimizationContext, + optimized_code: CodeStringsMarkdown, + original_helper_code: dict[Path, str], + ) -> bool: + from codeflash.languages.python.static_analysis.code_replacer import replace_function_definitions_in_module + + did_update = False + for module_abspath, qualified_names in self.group_functions_by_file(code_context).items(): + did_update |= replace_function_definitions_in_module( + function_names=list(qualified_names), + optimized_code=optimized_code, + module_abspath=module_abspath, + preexisting_objects=code_context.preexisting_objects, + project_root_path=self.project_root, + ) + + unused_helpers = detect_unused_helper_functions(self.function_to_optimize, code_context, optimized_code) + if unused_helpers: + revert_unused_helper_functions(self.project_root, unused_helpers, original_helper_code) + return did_update + + def line_profiler_step( + self, code_context: CodeOptimizationContext, original_helper_code: dict[Path, str], candidate_index: int + ) -> dict[str, Any]: + candidate_fto_code = Path(self.function_to_optimize.file_path).read_text("utf-8") + if contains_jit_decorator(candidate_fto_code): + logger.info( + f"Skipping line profiler for {self.function_to_optimize.function_name} - code contains JIT decorator" + ) + return {"timings": {}, "unit": 0, "str_out": ""} + + for module_abspath in original_helper_code: + candidate_helper_code = Path(module_abspath).read_text("utf-8") + if contains_jit_decorator(candidate_helper_code): + logger.info( + f"Skipping line profiler for {self.function_to_optimize.function_name} - helper code contains JIT decorator" + ) + return {"timings": {}, "unit": 0, "str_out": ""} + + try: + console.rule() + + test_env = self.get_test_env( + codeflash_loop_index=0, codeflash_test_iteration=candidate_index, codeflash_tracer_disable=1 + ) + line_profiler_output_file = add_decorator_imports(self.function_to_optimize, code_context) + line_profile_results, _ = self.run_and_parse_tests( + testing_type=TestingMode.LINE_PROFILE, + test_env=test_env, + test_files=self.test_files, + optimization_iteration=0, + testing_time=TOTAL_LOOPING_TIME_EFFECTIVE, + enable_coverage=False, + code_context=code_context, + line_profiler_output_file=line_profiler_output_file, + ) + finally: + self.write_code_and_helpers( + self.function_to_optimize_source_code, original_helper_code, self.function_to_optimize.file_path + ) + if isinstance(line_profile_results, TestResults) and not line_profile_results.test_results: + logger.warning( + f"Timeout occurred while running line profiler for original function {self.function_to_optimize.function_name}" + ) + return {"timings": {}, "unit": 0, "str_out": ""} + if line_profile_results["str_out"] == "": + logger.warning( + f"Couldn't run line profiler for original function {self.function_to_optimize.function_name}" + ) + return line_profile_results diff --git a/codeflash/languages/python/normalizer.py b/codeflash/languages/python/normalizer.py new file mode 100644 index 000000000..e01580547 --- /dev/null +++ b/codeflash/languages/python/normalizer.py @@ -0,0 +1,180 @@ +"""Python code normalizer using AST transformation.""" + +from __future__ import annotations + +import ast + + +class VariableNormalizer(ast.NodeTransformer): + """Normalizes only local variable names in AST to canonical forms like var_0, var_1, etc. + + Preserves function names, class names, parameters, built-ins, and imported names. + """ + + def __init__(self) -> None: + self.var_counter = 0 + self.var_mapping: dict[str, str] = {} + self.scope_stack: list[dict] = [] + self.builtins = set(dir(__builtins__)) + self.imports: set[str] = set() + self.global_vars: set[str] = set() + self.nonlocal_vars: set[str] = set() + self.parameters: set[str] = set() + + def enter_scope(self) -> None: + """Enter a new scope (function/class).""" + self.scope_stack.append( + {"var_mapping": dict(self.var_mapping), "var_counter": self.var_counter, "parameters": set(self.parameters)} + ) + + def exit_scope(self) -> None: + """Exit current scope and restore parent scope.""" + if self.scope_stack: + scope = self.scope_stack.pop() + self.var_mapping = scope["var_mapping"] + self.var_counter = scope["var_counter"] + self.parameters = scope["parameters"] + + def get_normalized_name(self, name: str) -> str: + """Get or create normalized name for a variable.""" + if ( + name in self.builtins + or name in self.imports + or name in self.global_vars + or name in self.nonlocal_vars + or name in self.parameters + ): + return name + + if name not in self.var_mapping: + self.var_mapping[name] = f"var_{self.var_counter}" + self.var_counter += 1 + return self.var_mapping[name] + + def visit_Import(self, node: ast.Import) -> ast.Import: + """Track imported names.""" + for alias in node.names: + name = alias.asname if alias.asname else alias.name + self.imports.add(name.split(".")[0]) + return node + + def visit_ImportFrom(self, node: ast.ImportFrom) -> ast.ImportFrom: + """Track imported names from modules.""" + for alias in node.names: + name = alias.asname if alias.asname else alias.name + self.imports.add(name) + return node + + def visit_Global(self, node: ast.Global) -> ast.Global: + """Track global variable declarations.""" + self.global_vars.update(node.names) + return node + + def visit_Nonlocal(self, node: ast.Nonlocal) -> ast.Nonlocal: + """Track nonlocal variable declarations.""" + self.nonlocal_vars.update(node.names) + return node + + def visit_FunctionDef(self, node: ast.FunctionDef) -> ast.FunctionDef: + """Process function but keep function name and parameters unchanged.""" + self.enter_scope() + + for arg in node.args.args: + self.parameters.add(arg.arg) + if node.args.vararg: + self.parameters.add(node.args.vararg.arg) + if node.args.kwarg: + self.parameters.add(node.args.kwarg.arg) + for arg in node.args.kwonlyargs: + self.parameters.add(arg.arg) + + node = self.generic_visit(node) + self.exit_scope() + return node + + def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> ast.AsyncFunctionDef: + """Handle async functions same as regular functions.""" + return self.visit_FunctionDef(node) # type: ignore[return-value] + + def visit_ClassDef(self, node: ast.ClassDef) -> ast.ClassDef: + """Process class but keep class name unchanged.""" + self.enter_scope() + node = self.generic_visit(node) + self.exit_scope() + return node + + def visit_Name(self, node: ast.Name) -> ast.Name: + """Normalize variable names in Name nodes.""" + if isinstance(node.ctx, (ast.Store, ast.Del)): + if ( + node.id not in self.builtins + and node.id not in self.imports + and node.id not in self.parameters + and node.id not in self.global_vars + and node.id not in self.nonlocal_vars + ): + node.id = self.get_normalized_name(node.id) + elif isinstance(node.ctx, ast.Load) and node.id in self.var_mapping: + node.id = self.var_mapping[node.id] + return node + + def visit_ExceptHandler(self, node: ast.ExceptHandler) -> ast.ExceptHandler: + """Normalize exception variable names.""" + if node.name: + node.name = self.get_normalized_name(node.name) + return self.generic_visit(node) + + def visit_comprehension(self, node: ast.comprehension) -> ast.comprehension: + """Normalize comprehension target variables.""" + old_mapping = dict(self.var_mapping) + old_counter = self.var_counter + + node = self.generic_visit(node) + + self.var_mapping = old_mapping + self.var_counter = old_counter + return node + + def visit_For(self, node: ast.For) -> ast.For: + """Handle for loop target variables.""" + return self.generic_visit(node) + + def visit_With(self, node: ast.With) -> ast.With: + """Handle with statement as variables.""" + return self.generic_visit(node) + + +def _remove_docstrings_from_ast(node: ast.AST) -> None: + """Remove docstrings from AST nodes.""" + node_types = (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, ast.Module) + stack = [node] + while stack: + current_node = stack.pop() + if isinstance(current_node, node_types): + body = current_node.body + if ( + body + and isinstance(body[0], ast.Expr) + and isinstance(body[0].value, ast.Constant) + and isinstance(body[0].value.value, str) + ): + current_node.body = body[1:] + stack.extend([child for child in body if isinstance(child, node_types)]) + + +def normalize_python_code(code: str, remove_docstrings: bool = True) -> str: + """Normalize Python code to a canonical form for comparison. + + Replaces local variable names with canonical forms (var_0, var_1, etc.) + while preserving function names, class names, parameters, and imports. + """ + tree = ast.parse(code) + + if remove_docstrings: + _remove_docstrings_from_ast(tree) + + normalizer = VariableNormalizer() + normalized_tree = normalizer.visit(tree) + ast.fix_missing_locations(normalized_tree) + + return ast.unparse(normalized_tree) diff --git a/codeflash/languages/python/optimizer.py b/codeflash/languages/python/optimizer.py new file mode 100644 index 000000000..475c834fc --- /dev/null +++ b/codeflash/languages/python/optimizer.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +import ast +from typing import TYPE_CHECKING + +from codeflash.cli_cmds.console import logger +from codeflash.models.models import ValidCode + +if TYPE_CHECKING: + from pathlib import Path + + from codeflash.models.function_types import FunctionParent + + +def prepare_python_module( + original_module_code: str, original_module_path: Path, project_root: Path +) -> tuple[dict[Path, ValidCode], ast.Module] | None: + """Parse a Python module, normalize its code, and validate imported callee modules. + + Returns a mapping of file paths to ValidCode (for the module and its imported callees) + plus the parsed AST, or None on syntax error. + """ + from codeflash.languages.python.static_analysis.code_replacer import normalize_code, normalize_node + from codeflash.languages.python.static_analysis.static_analysis import analyze_imported_modules + + try: + original_module_ast = ast.parse(original_module_code) + except SyntaxError as e: + logger.warning(f"Syntax error parsing code in {original_module_path}: {e}") + logger.info("Skipping optimization due to file error.") + return None + + normalized_original_module_code = ast.unparse(normalize_node(original_module_ast)) + validated_original_code: dict[Path, ValidCode] = { + original_module_path: ValidCode( + source_code=original_module_code, normalized_code=normalized_original_module_code + ) + } + + imported_module_analyses = analyze_imported_modules(original_module_code, original_module_path, project_root) + + for analysis in imported_module_analyses: + callee_original_code = analysis.file_path.read_text(encoding="utf8") + try: + normalized_callee_original_code = normalize_code(callee_original_code) + except SyntaxError as e: + logger.warning(f"Syntax error parsing code in callee module {analysis.file_path}: {e}") + logger.info("Skipping optimization due to helper file error.") + return None + validated_original_code[analysis.file_path] = ValidCode( + source_code=callee_original_code, normalized_code=normalized_callee_original_code + ) + + return validated_original_code, original_module_ast + + +def resolve_python_function_ast( + function_name: str, parents: list[FunctionParent], module_ast: ast.Module +) -> ast.FunctionDef | ast.AsyncFunctionDef | None: + """Look up a function/method AST node in a parsed Python module.""" + from codeflash.languages.python.static_analysis.static_analysis import get_first_top_level_function_or_method_ast + + return get_first_top_level_function_or_method_ast(function_name, parents, module_ast) From e8e1e2bb1a94b90622c8a1b791736bd10732e64e Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Mon, 2 Mar 2026 22:45:29 -0500 Subject: [PATCH 2/3] feat: bring safe incremental changes from main - test_framework: add Java framework literals, use protocol dispatch - code_utils: cache temp dir Path, remove lru_cache from humanize_runtime - tabulate: remove unused param from _column_type call - checkpoint: add subagent/yes flag support - import_resolver: refactor helper extraction - treesitter: add named export clause discovery --- codeflash/code_utils/checkpoint.py | 14 ++- codeflash/code_utils/code_utils.py | 5 +- codeflash/code_utils/tabulate.py | 2 +- codeflash/code_utils/time_utils.py | 3 - .../languages/javascript/import_resolver.py | 40 +++---- codeflash/languages/javascript/treesitter.py | 109 +++++++++++++++++- codeflash/languages/test_framework.py | 8 +- 7 files changed, 138 insertions(+), 43 deletions(-) diff --git a/codeflash/code_utils/checkpoint.py b/codeflash/code_utils/checkpoint.py index 1160bf2e0..367e150b7 100644 --- a/codeflash/code_utils/checkpoint.py +++ b/codeflash/code_utils/checkpoint.py @@ -141,12 +141,18 @@ def get_all_historical_functions(module_root: Path, checkpoint_dir: Path) -> dic def ask_should_use_checkpoint_get_functions(args: argparse.Namespace) -> Optional[dict[str, dict[str, str]]]: previous_checkpoint_functions = None + if getattr(args, "subagent", False): + console.rule() + return None if args.all and codeflash_temp_dir.is_dir(): previous_checkpoint_functions = get_all_historical_functions(args.module_root, codeflash_temp_dir) - if previous_checkpoint_functions and Confirm.ask( - "Previous Checkpoint detected from an incomplete optimization run, shall I continue the optimization from that point?", - default=True, - console=console, + if previous_checkpoint_functions and ( + getattr(args, "yes", False) + or Confirm.ask( + "Previous Checkpoint detected from an incomplete optimization run, shall I continue the optimization from that point?", + default=True, + console=console, + ) ): console.rule() else: diff --git a/codeflash/code_utils/code_utils.py b/codeflash/code_utils/code_utils.py index 7a9afc96f..45a64f0fc 100644 --- a/codeflash/code_utils/code_utils.py +++ b/codeflash/code_utils/code_utils.py @@ -408,9 +408,10 @@ def get_all_function_names(code: str) -> tuple[bool, list[str]]: def get_run_tmp_file(file_path: Path | str) -> Path: if isinstance(file_path, str): file_path = Path(file_path) - if not hasattr(get_run_tmp_file, "tmpdir"): + if not hasattr(get_run_tmp_file, "tmpdir_path"): get_run_tmp_file.tmpdir = TemporaryDirectory(prefix="codeflash_") - return Path(get_run_tmp_file.tmpdir.name) / file_path + get_run_tmp_file.tmpdir_path = Path(get_run_tmp_file.tmpdir.name) + return get_run_tmp_file.tmpdir_path / file_path def path_belongs_to_site_packages(file_path: Path) -> bool: diff --git a/codeflash/code_utils/tabulate.py b/codeflash/code_utils/tabulate.py index 0d5004f6c..1024afc4b 100644 --- a/codeflash/code_utils/tabulate.py +++ b/codeflash/code_utils/tabulate.py @@ -705,7 +705,7 @@ def tabulate( # format rows and columns, convert numeric values to strings cols = list(izip_longest(*list_of_lists)) numparses = _expand_numparse(disable_numparse, len(cols)) - coltypes = [_column_type(col, has_invisible, numparse=np) for col, np in zip(cols, numparses)] + coltypes = [_column_type(col, numparse=np) for col, np in zip(cols, numparses)] if isinstance(floatfmt, str): # old version float_formats = len(cols) * [floatfmt] # just duplicate the string to use in each column else: # if floatfmt is list, tuple etc we have one per column diff --git a/codeflash/code_utils/time_utils.py b/codeflash/code_utils/time_utils.py index e1a3d4a0e..42cfa9703 100644 --- a/codeflash/code_utils/time_utils.py +++ b/codeflash/code_utils/time_utils.py @@ -1,11 +1,8 @@ from __future__ import annotations -from functools import lru_cache - from codeflash.result.critic import performance_gain -@lru_cache(maxsize=1024) def humanize_runtime(time_in_ns: int) -> str: runtime_human: str = str(time_in_ns) units = "nanoseconds" diff --git a/codeflash/languages/javascript/import_resolver.py b/codeflash/languages/javascript/import_resolver.py index b5ec67115..34dd1990f 100644 --- a/codeflash/languages/javascript/import_resolver.py +++ b/codeflash/languages/javascript/import_resolver.py @@ -499,6 +499,18 @@ def _extract_helper_from_file( # Split source into lines for JSDoc extraction lines = source.splitlines(keepends=True) + def helper_from_func(func): + effective_start = func.doc_start_line or func.start_line + helper_source = "".join(lines[effective_start - 1 : func.end_line]) + return HelperFunction( + name=func.name, + qualified_name=func.name, + file_path=file_path, + source_code=helper_source, + start_line=effective_start, + end_line=func.end_line, + ) + # Handle "default" export - look for default exported function if function_name == "default": # Find the default export @@ -506,38 +518,14 @@ def _extract_helper_from_file( # For now, return first function if looking for default # TODO: Implement proper default export detection for func in functions: - # Extract source including JSDoc if present - effective_start = func.doc_start_line or func.start_line - helper_lines = lines[effective_start - 1 : func.end_line] - helper_source = "".join(helper_lines) - - return HelperFunction( - name=func.name, - qualified_name=func.name, - file_path=file_path, - source_code=helper_source, - start_line=effective_start, - end_line=func.end_line, - ) + return helper_from_func(func) return None # Find the function by name functions = file_analyzer.find_functions(source, include_methods=True) for func in functions: if func.name == function_name: - # Extract source including JSDoc if present - effective_start = func.doc_start_line or func.start_line - helper_lines = lines[effective_start - 1 : func.end_line] - helper_source = "".join(helper_lines) - - return HelperFunction( - name=func.name, - qualified_name=func.name, - file_path=file_path, - source_code=helper_source, - start_line=effective_start, - end_line=func.end_line, - ) + return helper_from_func(func) logger.debug("Function %s not found in %s", function_name, file_path) return None diff --git a/codeflash/languages/javascript/treesitter.py b/codeflash/languages/javascript/treesitter.py index c00cb228e..f3ba0453f 100644 --- a/codeflash/languages/javascript/treesitter.py +++ b/codeflash/languages/javascript/treesitter.py @@ -208,6 +208,22 @@ def find_functions( current_function=None, ) + # Post-process: upgrade is_exported for functions referenced in named export clauses + # e.g., const joinBy = () => {}; export { joinBy }; + exports = self.find_exports(source) + exported_names: set[str] = set() + for export in exports: + for name, _ in export.exported_names: + exported_names.add(name) + if export.default_export: + exported_names.add(export.default_export) + if export.wrapped_default_args: + exported_names.update(export.wrapped_default_args) + + for func in functions: + if not func.is_exported and func.name in exported_names: + func.is_exported = True + return functions def _walk_tree_for_functions( @@ -505,7 +521,11 @@ def _check_commonjs_assignment_exports(self, node: Node, name: str, source_bytes # Check module.exports = name (single export) if left_text == "module.exports" and right_node.type == "identifier": - if self.get_node_text(right_node, source_bytes) == name: + exported_var = self.get_node_text(right_node, source_bytes) + if exported_var == name: + return True + # module.exports = varName → check if name is a property of varName's object + if self._is_name_property_of_variable(node, exported_var, name, source_bytes): return True # Check module.exports.name = ... or exports.name = ... @@ -514,6 +534,85 @@ def _check_commonjs_assignment_exports(self, node: Node, name: str, source_bytes return False + def _resolve_variable_object_properties( + self, node: Node, var_name: str, source_bytes: bytes + ) -> list[tuple[str, str | None]]: + """Resolve a variable name to its object literal and return property names. + + For `const utils = { match() {}, foo: bar }`, returns [("match", None), ("foo", None)]. + """ + root = node + while root.parent: + root = root.parent + + properties: list[tuple[str, str | None]] = [] + for child in root.children: + if child.type in ("lexical_declaration", "variable_declaration"): + for decl in child.children: + if decl.type == "variable_declarator": + name_node = decl.child_by_field_name("name") + value_node = decl.child_by_field_name("value") + if ( + name_node + and self.get_node_text(name_node, source_bytes) == var_name + and value_node + and value_node.type == "object" + ): + for obj_child in value_node.children: + if obj_child.type == "method_definition": + method_name_node = obj_child.child_by_field_name("name") + if method_name_node: + properties.append((self.get_node_text(method_name_node, source_bytes), None)) + elif obj_child.type == "shorthand_property_identifier": + properties.append((self.get_node_text(obj_child, source_bytes), None)) + elif obj_child.type == "pair": + key_node = obj_child.child_by_field_name("key") + if key_node: + properties.append((self.get_node_text(key_node, source_bytes), None)) + return properties + + def _is_name_property_of_variable(self, node: Node, var_name: str, prop_name: str, source_bytes: bytes) -> bool: + """Check if prop_name is a property/method of the object assigned to var_name. + + Resolves patterns like: + const utils = { match() {}, foo: bar }; + module.exports = utils; + → checks if prop_name is a key of the object literal assigned to var_name. + """ + root = node + while root.parent: + root = root.parent + + for child in root.children: + # Look for: const/let/var varName = { ... } + if child.type in ("lexical_declaration", "variable_declaration"): + for decl in child.children: + if decl.type == "variable_declarator": + name_node = decl.child_by_field_name("name") + value_node = decl.child_by_field_name("value") + if ( + name_node + and self.get_node_text(name_node, source_bytes) == var_name + and value_node + and value_node.type == "object" + ): + for obj_child in value_node.children: + if obj_child.type == "method_definition": + method_name_node = obj_child.child_by_field_name("name") + if ( + method_name_node + and self.get_node_text(method_name_node, source_bytes) == prop_name + ): + return True + elif obj_child.type == "shorthand_property_identifier": + if self.get_node_text(obj_child, source_bytes) == prop_name: + return True + elif obj_child.type == "pair": + key_node = obj_child.child_by_field_name("key") + if key_node and self.get_node_text(key_node, source_bytes) == prop_name: + return True + return False + def _find_preceding_jsdoc(self, node: Node, source_bytes: bytes) -> int | None: """Find JSDoc comment immediately preceding a function node. @@ -1005,8 +1104,12 @@ def _extract_commonjs_export(self, node: Node, source_bytes: bytes) -> ExportInf name_node = right_node.child_by_field_name("name") default_export = self.get_node_text(name_node, source_bytes) if name_node else "default" elif right_node.type == "identifier": - # module.exports = someFunction - default_export = self.get_node_text(right_node, source_bytes) + # module.exports = someFunction or module.exports = someObject + var_name = self.get_node_text(right_node, source_bytes) + default_export = var_name + # Resolve variable to object literal and add its properties as exports + obj_props = self._resolve_variable_object_properties(node, var_name, source_bytes) + exported_names.extend(obj_props) elif right_node.type == "object": # module.exports = { foo, bar, baz: qux } for child in right_node.children: diff --git a/codeflash/languages/test_framework.py b/codeflash/languages/test_framework.py index ce1f3f5fe..7a5483f00 100644 --- a/codeflash/languages/test_framework.py +++ b/codeflash/languages/test_framework.py @@ -30,7 +30,7 @@ from typing import Literal -TestFramework = Literal["jest", "vitest", "mocha", "pytest", "unittest"] +TestFramework = Literal["jest", "vitest", "mocha", "pytest", "unittest", "junit5", "junit4", "testng"] # Module-level singleton for the current test framework _current_test_framework: TestFramework | None = None @@ -63,11 +63,11 @@ def set_current_test_framework(framework: TestFramework | str | None) -> None: if framework is not None: framework = framework.lower() - if framework not in ("jest", "vitest", "mocha", "pytest", "unittest"): + if framework not in ("jest", "vitest", "mocha", "pytest", "unittest", "junit5", "junit4", "testng"): # Default to jest for unknown JS frameworks, pytest for unknown Python - from codeflash.languages.current import is_javascript + from codeflash.languages.current import current_language_support - framework = "jest" if is_javascript() else "pytest" + framework = current_language_support().test_framework _current_test_framework = framework From 08dad4e6a471135d41012fa59a0ff9bb080f29fc Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Mon, 2 Mar 2026 22:48:28 -0500 Subject: [PATCH 3/3] chore: sync docs, rules, and workflows from main --- .claude/rules/architecture.md | 51 +- .claude/rules/code-style.md | 3 +- .claude/rules/language-patterns.md | 3 +- .claude/rules/optimization-patterns.md | 2 +- .github/workflows/claude.yml | 259 ++++++---- .github/workflows/unit-tests.yaml | 13 - CLAUDE.md | 4 +- docs/FRICTIONLESS_SETUP_PLAN.md | 1 + docs/JS_PROMPT_PARITY_RECOMMENDATIONS.md | 1 + .../how-codeflash-works.mdx | 18 +- docs/configuration.mdx | 79 +-- docs/configuration/javascript.mdx | 220 +++++++++ docs/configuration/python.mdx | 80 +++ .../javascript-installation.mdx | 467 +++++++++--------- docs/index.mdx | 28 +- .../benchmarking.mdx | 12 +- .../codeflash-all.mdx | 26 +- .../codeflash-github-actions.mdx | 57 ++- .../one-function.mdx | 43 +- .../trace-and-optimize.mdx | 88 +++- 20 files changed, 964 insertions(+), 491 deletions(-) create mode 100644 docs/FRICTIONLESS_SETUP_PLAN.md create mode 100644 docs/JS_PROMPT_PARITY_RECOMMENDATIONS.md create mode 100644 docs/configuration/javascript.mdx create mode 100644 docs/configuration/python.mdx diff --git a/.claude/rules/architecture.md b/.claude/rules/architecture.md index 535e08d79..96eb3a43c 100644 --- a/.claude/rules/architecture.md +++ b/.claude/rules/architecture.md @@ -1,11 +1,12 @@ # Architecture +When adding, moving, or deleting source files, update this doc to match. + ``` codeflash/ ├── main.py # CLI entry point ├── cli_cmds/ # Command handling, console output (Rich) ├── discovery/ # Find optimizable functions -├── context/ # Extract code dependencies and imports ├── optimization/ # Generate optimized code via AI │ ├── optimizer.py # Main optimization orchestration │ └── function_optimizer.py # Per-function optimization logic @@ -15,7 +16,21 @@ codeflash/ ├── api/ # AI service communication ├── code_utils/ # Code parsing, git utilities ├── models/ # Pydantic models and types -├── languages/ # Multi-language support (Python, JavaScript/TypeScript) +├── languages/ # Multi-language support (Python, JavaScript/TypeScript, Java planned) +│ ├── base.py # LanguageSupport protocol and shared data types +│ ├── registry.py # Language registration and lookup by extension/enum +│ ├── current.py # Current language singleton (set_current_language / current_language_support) +│ ├── code_replacer.py # Language-agnostic code replacement +│ ├── python/ +│ │ ├── support.py # PythonSupport (LanguageSupport implementation) +│ │ ├── function_optimizer.py # PythonFunctionOptimizer subclass +│ │ ├── optimizer.py # Python module preparation & AST resolution +│ │ └── normalizer.py # Python code normalization for deduplication +│ └── javascript/ +│ ├── support.py # JavaScriptSupport (LanguageSupport implementation) +│ ├── function_optimizer.py # JavaScriptFunctionOptimizer subclass +│ ├── optimizer.py # JS project root finding & module preparation +│ └── normalizer.py # JS/TS code normalization for deduplication ├── setup/ # Config schema, auto-detection, first-run experience ├── picklepatch/ # Serialization/deserialization utilities ├── tracing/ # Function call tracing @@ -33,10 +48,36 @@ codeflash/ |------|------------| | CLI arguments & commands | `cli_cmds/cli.py` | | Optimization orchestration | `optimization/optimizer.py` → `run()` | -| Per-function optimization | `optimization/function_optimizer.py` | +| Per-function optimization | `optimization/function_optimizer.py` (base), `languages/python/function_optimizer.py`, `languages/javascript/function_optimizer.py` | | Function discovery | `discovery/functions_to_optimize.py` | -| Context extraction | `context/code_context_extractor.py` | -| Test execution | `verification/test_runner.py`, `verification/pytest_plugin.py` | +| Context extraction | `languages//context/code_context_extractor.py` | +| Test execution | `languages//support.py` (`run_behavioral_tests`, etc.), `verification/pytest_plugin.py` | | Performance ranking | `benchmarking/function_ranker.py` | | Domain types | `models/models.py`, `models/function_types.py` | | Result handling | `either.py` (`Result`, `Success`, `Failure`, `is_successful`) | + +## LanguageSupport Protocol Methods + +Core protocol in `languages/base.py`. Each language (`PythonSupport`, `JavaScriptSupport`) implements these. + +| Category | Method/Property | Purpose | +|----------|----------------|---------| +| Identity | `language`, `file_extensions`, `default_file_extension` | Language identification | +| Identity | `comment_prefix`, `dir_excludes` | Language conventions | +| AI service | `default_language_version` | Language version for API payloads (`None` for Python, `"ES2022"` for JS) | +| AI service | `valid_test_frameworks` | Allowed test frameworks for validation | +| Discovery | `discover_functions`, `discover_tests` | Find optimizable functions and their tests | +| Discovery | `adjust_test_config_for_discovery` | Pre-discovery config adjustment (no-op default) | +| Context | `extract_code_context`, `find_helper_functions`, `find_references` | Code dependency extraction | +| Transform | `replace_function`, `format_code`, `normalize_code` | Code modification | +| Validation | `validate_syntax` | Syntax checking | +| Test execution | `run_behavioral_tests`, `run_benchmarking_tests`, `run_line_profile_tests` | Test runners | +| Test results | `test_result_serialization_format` | `"pickle"` (Python) or `"json"` (JS) | +| Test results | `load_coverage` | Load coverage from language-specific format | +| Test results | `compare_test_results` | Equivalence checking between original and candidate | +| Test gen | `postprocess_generated_tests` | Post-process `GeneratedTestsList` objects | +| Test gen | `process_generated_test_strings` | Instrument/transform raw generated test strings | +| Module | `detect_module_system` | Detect project module system (`None` for Python, `"esm"`/`"commonjs"` for JS) | +| Module | `prepare_module` | Parse/validate module before optimization | +| Setup | `setup_test_config` | One-time project setup after language detection | +| Optimizer | `function_optimizer_class` | Return `FunctionOptimizer` subclass for this language | diff --git a/.claude/rules/code-style.md b/.claude/rules/code-style.md index bcb8fd30b..6a2daef87 100644 --- a/.claude/rules/code-style.md +++ b/.claude/rules/code-style.md @@ -7,4 +7,5 @@ - **Comments**: Minimal - only explain "why", not "what" - **Docstrings**: Do not add unless explicitly requested - **Naming**: NEVER use leading underscores (`_function_name`) - Python has no true private functions, use public names -- **Paths**: Always use absolute paths, handle encoding explicitly (UTF-8) +- **Paths**: Always use absolute paths +- **Encoding**: Always pass `encoding="utf-8"` to `open()`, `read_text()`, `write_text()`, etc. in new or changed code — Windows defaults to `cp1252` which breaks on non-ASCII content. Don't flag pre-existing code that lacks it unless you're already modifying that line. diff --git a/.claude/rules/language-patterns.md b/.claude/rules/language-patterns.md index 8616eb478..34d61e605 100644 --- a/.claude/rules/language-patterns.md +++ b/.claude/rules/language-patterns.md @@ -9,4 +9,5 @@ paths: - Use `get_language_support(identifier)` from `languages/registry.py` to get a `LanguageSupport` instance — never import language classes directly - New language support classes must use the `@register_language` decorator to register with the extension and language registries - `languages/__init__.py` uses `__getattr__` for lazy imports to avoid circular dependencies — follow this pattern when adding new exports -- `is_javascript()` returns `True` for both JavaScript and TypeScript +- Prefer `LanguageSupport` protocol dispatch over `is_python()`/`is_javascript()` guards — remaining guards are being migrated to protocol methods +- `is_javascript()` returns `True` for both JavaScript and TypeScript (still used in ~15 call sites pending migration) diff --git a/.claude/rules/optimization-patterns.md b/.claude/rules/optimization-patterns.md index f677d48de..7a1e90dea 100644 --- a/.claude/rules/optimization-patterns.md +++ b/.claude/rules/optimization-patterns.md @@ -3,7 +3,7 @@ paths: - "codeflash/optimization/**/*.py" - "codeflash/verification/**/*.py" - "codeflash/benchmarking/**/*.py" - - "codeflash/context/**/*.py" + - "codeflash/languages/*/context/**/*.py" --- # Optimization Pipeline Patterns diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 6b17da886..4c4374b66 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -1,8 +1,20 @@ name: Claude Code on: + workflow_dispatch: pull_request: types: [opened, synchronize, ready_for_review, reopened] + paths-ignore: + - '.github/workflows/**' + - '*.md' + - 'docs/**' + - 'demos/**' + - 'experiments/**' + - 'LICENSE' + - '.tessl/**' + - 'code_to_optimize/**' + - 'codeflash.code-workspace' + - 'uv.lock' issue_comment: types: [created] pull_request_review_comment: @@ -16,10 +28,16 @@ jobs: # Automatic PR review (can fix linting issues and push) # Blocked for fork PRs to prevent malicious code execution pr-review: + concurrency: + group: pr-review-${{ github.head_ref || github.run_id }} + cancel-in-progress: true if: | - github.event_name == 'pull_request' && - github.actor != 'claude[bot]' && - github.event.pull_request.head.repo.full_name == github.repository + ( + github.event_name == 'pull_request' && + github.event.sender.login != 'claude[bot]' && + github.event.pull_request.head.repo.full_name == github.repository + ) || + github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest permissions: contents: write @@ -32,7 +50,7 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - ref: ${{ github.event.pull_request.head.ref }} + ref: ${{ github.event.pull_request.head.ref || github.ref }} - name: Install uv uses: astral-sh/setup-uv@v6 @@ -54,137 +72,162 @@ jobs: with: use_bedrock: "true" use_sticky_comment: true + track_progress: true allowed_bots: "claude[bot],codeflash-ai[bot]" + exclude_comments_by_actor: "*[bot]" prompt: | - REPO: ${{ github.repository }} - PR NUMBER: ${{ github.event.pull_request.number }} - EVENT: ${{ github.event.action }} - - ## STEP 1: Run prek and mypy checks, fix issues - - First, run these checks on files changed in this PR: - 1. `uv run prek run --from-ref origin/main` - linting/formatting issues - 2. `uv run mypy ` - type checking issues - - If there are prek issues: - - For SAFE auto-fixable issues (formatting, import sorting, trailing whitespace, etc.), run `uv run prek run --from-ref origin/main` again to auto-fix them - - For issues that prek cannot auto-fix, do NOT attempt to fix them manually — report them as remaining issues in your summary - - If there are mypy issues: - - Fix type annotation issues (missing return types, Optional/None unions, import errors for type hints, incorrect types) - - Do NOT add `type: ignore` comments - always fix the root cause - - After fixing issues: - - Stage the fixed files with `git add` - - Commit with message "style: auto-fix linting issues" or "fix: resolve mypy type errors" as appropriate - - Push the changes with `git push` - - IMPORTANT - Verification after fixing: - - After committing fixes, run `uv run prek run --from-ref origin/main` ONE MORE TIME to verify all issues are resolved - - If errors remain, either fix them or report them honestly as unfixed in your summary - - NEVER claim issues are fixed without verifying. If you cannot fix an issue, say so - - Do NOT attempt to fix: - - Type errors that require logic changes or refactoring - - Complex generic type issues - - Anything that could change runtime behavior - - ## STEP 2: Review the PR - - ${{ github.event.action == 'synchronize' && 'This is a RE-REVIEW after new commits. First, get the list of changed files in this latest push using `gh pr diff`. Review ONLY the changed files. Check ALL existing review comments and resolve ones that are now fixed.' || 'This is the INITIAL REVIEW.' }} - - Review this PR focusing ONLY on: - 1. Critical bugs or logic errors + + repo: ${{ github.repository }} + pr_number: ${{ github.event.pull_request.number }} + event: ${{ github.event.action }} + is_re_review: ${{ github.event.action == 'synchronize' }} + + + + Execute these steps in order. If a step has no work, state that and continue to the next step. + Post all review findings in a single summary comment only — never as inline PR review comments. + + + + Before doing any work, assess the PR scope: + + 1. Run `gh pr diff ${{ github.event.pull_request.number }} --name-only` to get changed files. + 2. Classify as TRIVIAL if ALL changed files are: + - Config/CI files (.github/, .tessl/, *.toml, *.lock, *.json, *.yml, *.yaml) + - Documentation (*.md, docs/) + - Non-production code (demos/, experiments/, code_to_optimize/) + - Only whitespace, formatting, or comment changes + + If TRIVIAL: post a single comment "No substantive code changes to review." and stop — do not execute any further steps. + Otherwise: continue with the full review below. + + + + Run checks on files changed in this PR and auto-fix what you can. + + 1. Run `uv run prek run --from-ref origin/main` to check linting/formatting. + If there are auto-fixable issues, run it again to fix them. + Report any issues prek cannot auto-fix in your summary. + + 2. Run `uv run mypy ` to check types. + Fix type annotation issues (missing return types, Optional unions, import errors). + Always fix the root cause instead of adding `type: ignore` comments. + Leave alone: type errors requiring logic changes, complex generics, anything changing runtime behavior. + + 3. After fixes: stage with `git add`, commit ("style: auto-fix linting issues" or "fix: resolve mypy type errors"), push. + + 4. Verify by running `uv run prek run --from-ref origin/main` one more time. Report honestly if issues remain. + + + + Before reviewing, resolve any stale review threads from previous runs. + + 1. Fetch unresolved threads you created: + `gh api graphql -f query='{ repository(owner: "${{ github.repository_owner }}", name: "${{ github.event.repository.name }}") { pullRequest(number: ${{ github.event.pull_request.number }}) { reviewThreads(first: 100) { nodes { id isResolved path comments(first: 1) { nodes { body author { login } } } } } } } }' --jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false) | select(.comments.nodes[0].author.login == "claude") | {id: .id, path: .path, body: .comments.nodes[0].body}'` + + 2. For each unresolved thread: + a. Read the file at that path to check if the issue still exists + b. If fixed → resolve it: `gh api graphql -f query='mutation { resolveReviewThread(input: {threadId: ""}) { thread { isResolved } } }'` + c. If still present → leave it + + Read the actual code before deciding. If there are no unresolved threads, skip to the next step. + + + + Review the diff (`gh pr diff ${{ github.event.pull_request.number }}`) for: + 1. Bugs that will crash at runtime 2. Security vulnerabilities 3. Breaking API changes - 4. Test failures (methods with typos that wont run) - IMPORTANT: - - First check existing review comments using `gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/comments`. For each existing comment, check if the issue still exists in the current code. - - If an issue is fixed, use `gh api --method PATCH repos/${{ github.repository }}/pulls/comments/COMMENT_ID -f body="✅ Fixed in latest commit"` to resolve it. - - Only create NEW inline comments for HIGH-PRIORITY issues found in changed files. - - Limit to 5-7 NEW comments maximum per review. - - Use CLAUDE.md for project-specific guidance. - - Use `mcp__github_inline_comment__create_inline_comment` sparingly for critical code issues only. + Ignore style issues, type hints, and log message wording. + Record findings for the summary comment. Refer to CLAUDE.md for project conventions. + - ## STEP 3: Coverage analysis + + Check whether this PR introduces code that duplicates logic already present elsewhere in the repository — including across languages. Focus on finding true duplicates, not just similar-looking code. - Analyze test coverage for changed files: + 1. Get changed source files (excluding tests and config): + `git diff --name-only origin/main...HEAD -- '*.py' '*.js' '*.ts' '*.java' | grep -v -E '(test_|_test\.(py|js|ts)|\.test\.(js|ts)|\.spec\.(js|ts)|conftest\.py|/tests/|/test/|/__tests__/)' | grep -v -E '^(\.github/|code_to_optimize/|\.tessl/|node_modules/)'` - 1. Get the list of Python files changed in this PR (excluding tests): - `git diff --name-only origin/main...HEAD -- '*.py' | grep -v test` + 2. For each changed file, read it and identify functions/methods added or substantially modified (longer than 5 lines). - 2. Run tests with coverage on the PR branch: - `uv run coverage run -m pytest tests/ -q --tb=no` - `uv run coverage json -o coverage-pr.json` + 3. Search for duplicates using Grep: + - Same function name defined elsewhere + - 2-3 distinctive operations from the body (specific API calls, algorithm patterns, string literals) - 3. Get coverage for changed files only: - `uv run coverage report --include=""` + 4. Cross-module check: this codebase has parallel modules under `languages/python/`, `languages/javascript/`, and `languages/java/` plus runtimes under `packages/codeflash/runtime/` and `codeflash-java-runtime/`. When a changed file is under one of these areas, search the others for equivalent logic. Only flag cases where the logic is genuinely shared or one module could import from the other. - 4. Compare with main branch coverage: - - Checkout main: `git checkout origin/main` - - Run coverage: `uv run coverage run -m pytest tests/ -q --tb=no && uv run coverage json -o coverage-main.json` - - Checkout back: `git checkout -` + 5. When a Grep hit looks promising, read the full function and compare semantics. Flag only: + - Same function with same/very similar body in another module + - Same helper logic repeated in sibling files + - Same logic implemented inline across multiple classes + - Same algorithm reimplemented across language modules (Python code, not target-language differences) - 5. Analyze the diff to identify: - - NEW FILES: Files that don't exist on main (require good test coverage) - - MODIFIED FILES: Files with changes (changes must be covered by tests) + Report at most 5 findings with confidence (HIGH/MEDIUM), locations, what's duplicated, and suggestion. - 6. Report in PR comment with a markdown table: - - Coverage % for each changed file (PR vs main) - - Overall coverage change - - For NEW files: Flag if coverage is below 75% - - For MODIFIED files: Flag if the changed lines are not covered by tests - - Flag if overall coverage decreased + DO NOT report: boilerplate, functions under 5 lines, config/setup, intentional polymorphism, test files, imports, code that must differ due to target-language semantics. - Coverage requirements: - - New implementations/files: Must have ≥75% test coverage - - Modified code: Changed lines should be exercised by existing or new tests - - No coverage regressions: Overall coverage should not decrease + If no duplicates found, include "No duplicates detected" in the summary. + - ## STEP 4: Post ONE consolidated summary comment + + Analyze test coverage for changed files: - CRITICAL: You must post exactly ONE summary comment containing ALL results (pre-commit, review, coverage). - DO NOT post multiple separate comments. Use this format: + 1. Get changed Python files (excluding tests): `git diff --name-only origin/main...HEAD -- '*.py' | grep -v test` + 2. Run coverage on PR branch: `uv run coverage run -m pytest tests/ -q --tb=no` then `uv run coverage json -o coverage-pr.json` + 3. Get per-file coverage: `uv run coverage report --include=""` + 4. Compare with main: checkout main, run coverage, checkout back + 5. Flag: new files below 75%, decreased coverage, untested changed lines + - ``` - ## PR Review Summary + + Post exactly one summary comment containing all results from previous steps using this format: + ## PR Review Summary ### Prek Checks - [status and any fixes made] - ### Code Review - [critical issues found, if any] - + ### Duplicate Detection ### Test Coverage - [coverage table and analysis] - --- *Last updated: * - ``` - - To ensure only ONE comment exists: - 1. Find existing claude[bot] comment: `gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments --jq '.[] | select(.user.login == "claude[bot]") | .id' | head -1` - 2. If found, UPDATE it: `gh api --method PATCH repos/${{ github.repository }}/issues/comments/ -f body=""` - 3. If not found, CREATE: `gh pr comment ${{ github.event.pull_request.number }} --body ""` - 4. Delete any OTHER claude[bot] comments to clean up duplicates: `gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments --jq '.[] | select(.user.login == "claude[bot]") | .id' | tail -n +2 | xargs -I {} gh api --method DELETE repos/${{ github.repository }}/issues/comments/{}` - - ## STEP 5: Merge pending codeflash optimization PRs - - Check for open optimization PRs from codeflash and merge if CI passes: - - 1. List open PRs from codeflash bot: - `gh pr list --author "codeflash-ai[bot]" --state open --json number,title,headRefName` - - 2. For each optimization PR: - - Check if CI is passing: `gh pr checks ` - - If all checks pass, merge it: `gh pr merge --squash --delete-branch` - claude_args: '--model us.anthropic.claude-opus-4-6-v1 --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*),Bash(gh pr checks:*),Bash(gh pr merge:*),Bash(gh issue view:*),Bash(gh issue list:*),Bash(gh api:*),Bash(uv run prek *),Bash(uv run mypy *),Bash(uv run coverage *),Bash(uv run pytest *),Bash(git status*),Bash(git add *),Bash(git commit *),Bash(git push*),Bash(git diff *),Bash(git checkout *),Read,Glob,Grep,Edit"' + + + + Run /simplify to review recently changed code for reuse, quality, and efficiency opportunities. + If improvements are found, commit with "refactor: simplify " and push. + Only make behavior-preserving changes. + + + + Check for open PRs from codeflash-ai[bot]: + `gh pr list --author "codeflash-ai[bot]" --state open --json number,title,headRefName,createdAt,mergeable` + + For each PR: + - If CI passes and the PR is mergeable → merge with `--squash --delete-branch` + - Close the PR as stale if ANY of these apply: + - Older than 7 days + - Has merge conflicts (mergeable state is "CONFLICTING") + - CI is failing + - The optimized function no longer exists in the target file (check the diff) + Close with: `gh pr close --comment "Closing stale optimization PR." --delete-branch` + + + + Before finishing, confirm: + - All steps were attempted (even if some had no work) + - Stale review threads were checked and resolved where appropriate + - All findings are in a single summary comment (no inline review comments were created) + - If fixes were made, they were verified with prek + + claude_args: '--model us.anthropic.claude-sonnet-4-6 --max-turns 25 --allowedTools "Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*),Bash(gh pr checks:*),Bash(gh pr merge:*),Bash(gh issue view:*),Bash(gh issue list:*),Bash(gh api:*),Bash(uv run prek *),Bash(uv run mypy *),Bash(uv run coverage *),Bash(uv run pytest *),Bash(git status*),Bash(git add *),Bash(git commit *),Bash(git push*),Bash(git diff *),Bash(git checkout *),Read,Glob,Grep,Edit,Skill"' additional_permissions: | actions: read # @claude mentions (can edit and push) - restricted to maintainers only claude-mention: + concurrency: + group: claude-mention-${{ github.event.issue.number || github.event.pull_request.number || github.run_id }} + cancel-in-progress: false if: | ( github.event_name == 'issue_comment' && @@ -254,6 +297,6 @@ jobs: uses: anthropics/claude-code-action@v1 with: use_bedrock: "true" - claude_args: '--model us.anthropic.claude-opus-4-6-v1 --allowedTools "Read,Edit,Write,Glob,Grep,Bash(git status*),Bash(git diff*),Bash(git add *),Bash(git commit *),Bash(git push*),Bash(git log*),Bash(git merge*),Bash(git fetch*),Bash(git checkout*),Bash(git branch*),Bash(uv run prek *),Bash(prek *),Bash(uv run ruff *),Bash(uv run pytest *),Bash(uv run mypy *),Bash(uv run coverage *),Bash(gh pr comment*),Bash(gh pr view*),Bash(gh pr diff*),Bash(gh pr merge*),Bash(gh pr close*)"' + claude_args: '--model us.anthropic.claude-sonnet-4-6 --allowedTools "Read,Edit,Write,Glob,Grep,Bash(git status*),Bash(git diff*),Bash(git add *),Bash(git commit *),Bash(git push*),Bash(git log*),Bash(git merge*),Bash(git fetch*),Bash(git checkout*),Bash(git branch*),Bash(uv run prek *),Bash(prek *),Bash(uv run ruff *),Bash(uv run pytest *),Bash(uv run mypy *),Bash(uv run coverage *),Bash(gh pr comment*),Bash(gh pr view*),Bash(gh pr diff*),Bash(gh pr merge*),Bash(gh pr close*)"' additional_permissions: | actions: read diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index a73c2ea47..05ca30752 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -40,19 +40,6 @@ jobs: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - - name: Set up JDK 11 - uses: actions/setup-java@v4 - with: - java-version: '11' - distribution: 'temurin' - cache: maven - - - name: Build and install codeflash-runtime JAR - run: | - cd codeflash-java-runtime - mvn clean package -q -DskipTests - mvn install -q -DskipTests - - name: Install uv uses: astral-sh/setup-uv@v6 with: diff --git a/CLAUDE.md b/CLAUDE.md index c4628e91a..041dd7c74 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,7 +2,7 @@ ## Project Overview -CodeFlash is an AI-powered Python code optimizer that automatically improves code performance while maintaining correctness. It uses LLMs to generate optimization candidates, verifies correctness through test execution, and benchmarks performance improvements. +CodeFlash is an AI-powered code optimizer that automatically improves performance while maintaining correctness. It supports Python, JavaScript, and TypeScript, with more languages planned. It uses LLMs to generate optimization candidates, verifies correctness through test execution, and benchmarks performance improvements. ## Optimization Pipeline @@ -12,7 +12,7 @@ Discovery → Ranking → Context Extraction → Test Gen + Optimization → Bas 1. **Discovery** (`discovery/`): Find optimizable functions across the codebase 2. **Ranking** (`benchmarking/function_ranker.py`): Rank functions by addressable time using trace data -3. **Context** (`context/`): Extract code dependencies (read-writable code + read-only imports) +3. **Context** (`languages//context/`): Extract code dependencies (read-writable code + read-only imports) 4. **Optimization** (`optimization/`, `api/`): Generate candidates via AI service, run in parallel with test generation 5. **Verification** (`verification/`): Run candidates against tests, compare outputs via custom pytest plugin 6. **Benchmarking** (`benchmarking/`): Measure performance, select best candidate by speedup diff --git a/docs/FRICTIONLESS_SETUP_PLAN.md b/docs/FRICTIONLESS_SETUP_PLAN.md new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/docs/FRICTIONLESS_SETUP_PLAN.md @@ -0,0 +1 @@ + diff --git a/docs/JS_PROMPT_PARITY_RECOMMENDATIONS.md b/docs/JS_PROMPT_PARITY_RECOMMENDATIONS.md new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/docs/JS_PROMPT_PARITY_RECOMMENDATIONS.md @@ -0,0 +1 @@ + diff --git a/docs/codeflash-concepts/how-codeflash-works.mdx b/docs/codeflash-concepts/how-codeflash-works.mdx index 4456cd3d0..b9ab9a060 100644 --- a/docs/codeflash-concepts/how-codeflash-works.mdx +++ b/docs/codeflash-concepts/how-codeflash-works.mdx @@ -3,25 +3,31 @@ title: "How Codeflash Works" description: "Understand Codeflash's generate-and-verify approach to code optimization and correctness verification" icon: "gear" sidebarTitle: "How It Works" -keywords: ["architecture", "verification", "correctness", "testing", "optimization", "LLM", "benchmarking"] +keywords: ["architecture", "verification", "correctness", "testing", "optimization", "LLM", "benchmarking", "javascript", "typescript", "python"] --- # How Codeflash Works Codeflash follows a "generate and verify" approach to optimize code. It uses LLMs to generate optimizations, then it rigorously verifies if those optimizations are indeed faster and if they have the same behavior. The basic unit of optimization is a function—Codeflash tries to speed up the function, and tries to ensure that it still behaves the same way. This way if you merge the optimized code, it simply runs faster without breaking any functionality. +Codeflash supports **Python**, **JavaScript**, and **TypeScript** projects. + ## Analysis of your code Codeflash scans your codebase to identify all available functions. It locates existing unit tests in your projects and maps which functions they test. When optimizing a function, Codeflash runs these discovered tests to verify nothing has broken. +For Python, code analysis uses `libcst` and `jedi`. For JavaScript/TypeScript, it uses `tree-sitter` for AST parsing. + #### What kind of functions can Codeflash optimize? Codeflash works best with self-contained functions that have minimal side effects (like communicating with external systems or sending network requests). Codeflash optimizes a group of functions - consisting of an entry point function and any other functions it directly calls. -Codeflash supports optimizing async functions. +Codeflash supports optimizing async functions in all supported languages. #### Test Discovery -Codeflash currently only runs tests that directly call the target function in their test body. To discover tests that indirectly call the function, you can use the Codeflash Tracer. The Tracer analyzes your test suite and identifies all tests that eventually call a function. +Codeflash discovers tests that directly call the target function in their test body. For Python, it finds pytest and unittest tests. For JavaScript/TypeScript, it finds Jest and Vitest test files. + +To discover tests that indirectly call the function, you can use the Codeflash Tracer. The Tracer analyzes your test suite and identifies all tests that eventually call a function. ## Optimization Generation @@ -48,12 +54,12 @@ We recommend manually reviewing the optimized code since there might be importan Codeflash generates two types of tests: -- LLM Generated tests - Codeflash uses LLMs to create several regression test cases that cover typical function usage, edge cases, and large-scale inputs to verify both correctness and performance. -- Concolic coverage tests - Codeflash uses state-of-the-art concolic testing with an SMT Solver (a theorem prover) to explore execution paths and generate function arguments. This aims to maximize code coverage for the function being optimized. Codeflash runs the resulting test file to verify correctness. Currently, this feature only supports pytest. +- **LLM Generated tests** - Codeflash uses LLMs to create several regression test cases that cover typical function usage, edge cases, and large-scale inputs to verify both correctness and performance. This works for Python, JavaScript, and TypeScript. +- **Concolic coverage tests** - Codeflash uses state-of-the-art concolic testing with an SMT Solver (a theorem prover) to explore execution paths and generate function arguments. This aims to maximize code coverage for the function being optimized. Currently, this feature only supports Python (pytest). ## Code Execution -Codeflash runs tests for the target function using either pytest or unittest frameworks. The tests execute on your machine, ensuring access to the Python environment and any other dependencies associated to let Codeflash run your code properly. Running on your machine also ensures accurate performance measurements since runtime varies by system. +Codeflash runs tests for the target function on your machine. For Python, it uses pytest or unittest. For JavaScript/TypeScript, it uses Jest or Vitest. Running on your machine ensures access to your environment and dependencies, and provides accurate performance measurements since runtime varies by system. #### Performance benchmarking diff --git a/docs/configuration.mdx b/docs/configuration.mdx index 3f388a531..29506b952 100644 --- a/docs/configuration.mdx +++ b/docs/configuration.mdx @@ -1,83 +1,26 @@ --- title: "Manual Configuration" -description: "Configure Codeflash for your project with pyproject.toml settings and advanced options" +description: "Configure Codeflash for your project" icon: "gear" sidebarTitle: "Manual Configuration" keywords: [ "configuration", - "pyproject.toml", "setup", "settings", - "pytest", - "formatter", ] --- # Manual Configuration Codeflash is installed and configured on a per-project basis. -`codeflash init` should guide you through the configuration process, but if you need to manually configure Codeflash or set advanced settings, you can do so by editing the `pyproject.toml` file in the root directory of your project. - -## Configuration Options - -Codeflash config looks like the following - -```toml -[tool.codeflash] -module-root = "my_module" -tests-root = "tests" -formatter-cmds = ["black $file"] -# optional configuration -benchmarks-root = "tests/benchmarks" # Required when running with --benchmark -ignore-paths = ["my_module/build/"] -pytest-cmd = "pytest" -disable-imports-sorting = false -disable-telemetry = false -git-remote = "origin" -override-fixtures = false -``` - -All file paths are relative to the directory of the `pyproject.toml` file. - -Required Options: - -- `module-root`: The Python module you want Codeflash to optimize going forward. Only code under this directory will be optimized. It should also have an `__init__.py` file to make the module importable. -- `tests-root`: The directory where your tests are located. Codeflash will use this directory to discover existing tests as well as generate new tests. - -Optional Configuration: - -- `benchmarks-root`: The directory where your benchmarks are located. Codeflash will use this directory to discover existing benchmarks. Note that this option is required when running with `--benchmark`. -- `ignore-paths`: A list of paths within the `module-root` to ignore when optimizing code. Codeflash will not optimize code in these paths. Useful for ignoring build directories or other generated code. You can also leave this empty if not needed. -- `pytest-cmd`: The command to run your tests. Defaults to `pytest`. You can specify extra commandline arguments here for pytest. -- `formatter-cmds`: The command line to run your code formatter or linter. Defaults to `["black $file"]`. In the command line `$file` refers to the current file being optimized. The assumption with using tools here is that they overwrite the same file and returns a zero exit code. You can also specify multiple tools here that run in a chain as a toml array. You can also disable code formatting by setting this to `["disabled"]`. - - `ruff` - A recommended way to run ruff linting and formatting is `["ruff check --exit-zero --fix $file", "ruff format $file"]`. To make `ruff check --fix` return a 0 exit code please add a `--exit-zero` argument. -- `disable-imports-sorting`: By default, codeflash uses isort to organize your imports before creating suggestions. You can disable this by setting this field to `true`. This could be useful if you don't sort your imports or while using linters like ruff that sort imports too. -- `disable-telemetry`: Disable telemetry data collection. Defaults to `false`. Set this to `true` to disable telemetry data collection. Codeflash collects anonymized telemetry data to understand how users are using Codeflash and to improve the product. Telemetry does not collect any code data. -- `git-remote`: The git remote to use for pull requests. Defaults to `"origin"`. -- `override-fixtures`: Override pytest fixtures during optimization. Defaults to `false`. - -## Example Configuration - -Here's an example project with the following structure: - -```text -acme-project/ -|- foo_module/ -| |- __init__.py -| |- foo.py -| |- main.py -|- tests/ -| |- __init__.py -| |- test_script.py -|- pyproject.toml -``` - -Here's a sample `pyproject.toml` file for the above project: - -```toml -[tool.codeflash] -module-root = "foo_module" -tests-root = "tests" -ignore-paths = [] -``` +`codeflash init` should guide you through the configuration process, but if you need to manually configure Codeflash or set advanced settings, follow the guide for your language: + + + + Configure via `pyproject.toml` + + + Configure via `package.json` + + \ No newline at end of file diff --git a/docs/configuration/javascript.mdx b/docs/configuration/javascript.mdx new file mode 100644 index 000000000..1195d692d --- /dev/null +++ b/docs/configuration/javascript.mdx @@ -0,0 +1,220 @@ +--- +title: "JavaScript / TypeScript Configuration" +description: "Configure Codeflash for JavaScript and TypeScript projects using package.json" +icon: "js" +sidebarTitle: "JavaScript / TypeScript" +keywords: + [ + "configuration", + "package.json", + "javascript", + "typescript", + "jest", + "vitest", + "prettier", + "eslint", + "monorepo", + ] +--- + +# JavaScript / TypeScript Configuration + +Codeflash stores its configuration in `package.json` under the `"codeflash"` key. + +## Full Reference + +```json +{ + "name": "my-project", + "codeflash": { + "moduleRoot": "src", + "testsRoot": "tests", + "testRunner": "jest", + "formatterCmds": ["prettier --write $file"], + "ignorePaths": ["src/generated/"], + "disableTelemetry": false, + "gitRemote": "origin" + } +} +``` + +All file paths are relative to the directory containing `package.json`. + + +Codeflash auto-detects most settings from your project structure. Running `codeflash init` will set up the correct config — manual configuration is usually not needed. + + +## Auto-Detection + +When you run `codeflash init`, Codeflash inspects your project and auto-detects: + +| Setting | Detection logic | +|---------|----------------| +| `moduleRoot` | Looks for `src/`, `lib/`, or the main source directory | +| `testsRoot` | Looks for `tests/`, `test/`, `__tests__/`, or files matching `*.test.js` / `*.spec.js` | +| `testRunner` | Checks `devDependencies` for `jest` or `vitest` | +| `formatterCmds` | Checks for `prettier`, `eslint`, or `biome` in dependencies and config files | +| Module system | Reads `"type"` field in `package.json` (ESM vs CommonJS) | +| TypeScript | Detects `tsconfig.json` | + +You can always override any auto-detected value in the `"codeflash"` section. + +## Required Options + +- `moduleRoot`: The source directory to optimize. Only code under this directory will be optimized. +- `testsRoot`: The directory where your tests are located. Codeflash discovers existing tests and generates new ones here. + +## Optional Options + +- `testRunner`: Test framework to use. Auto-detected from your dependencies. Supported values: `"jest"`, `"vitest"`. +- `formatterCmds`: Formatter commands. `$file` refers to the file being optimized. Disable with `["disabled"]`. + - **Prettier**: `["prettier --write $file"]` + - **ESLint + Prettier**: `["eslint --fix $file", "prettier --write $file"]` + - **Biome**: `["biome check --write $file"]` +- `ignorePaths`: Paths within `moduleRoot` to skip during optimization. +- `disableTelemetry`: Disable anonymized telemetry. Defaults to `false`. +- `gitRemote`: Git remote for pull requests. Defaults to `"origin"`. + +## Module Systems + +Codeflash handles both ES Modules and CommonJS automatically. It detects the module system from your `package.json`: + +```json +{ + "type": "module" +} +``` + +- `"type": "module"` — Files are treated as ESM (`import`/`export`) +- `"type": "commonjs"` or omitted — Files are treated as CommonJS (`require`/`module.exports`) + +No additional configuration is needed. Codeflash respects `.mjs`/`.cjs` extensions as well. + +## TypeScript + +TypeScript projects work out of the box. Codeflash detects TypeScript from the presence of `tsconfig.json` and handles `.ts`/`.tsx` files automatically. + +No separate configuration is needed for TypeScript vs JavaScript. + +## Test Framework Support + +| Framework | Auto-detected from | Notes | +|-----------|-------------------|-------| +| **Jest** | `jest` in dependencies | Default for most projects | +| **Vitest** | `vitest` in dependencies | ESM-native support | + + +**Functions must be exported** to be optimizable. Codeflash uses tree-sitter AST analysis to discover functions and check export status. Supported export patterns: + +- `export function foo() {}` +- `export const foo = () => {}` +- `export default function foo() {}` +- `const foo = () => {}; export { foo };` +- `module.exports = { foo }` +- `const utils = { foo() {} }; module.exports = utils;` + + +## Monorepo Configuration + +For monorepo projects (Yarn workspaces, pnpm workspaces, Lerna, Nx, Turborepo), configure each package individually: + +```text +my-monorepo/ +|- packages/ +| |- core/ +| | |- src/ +| | |- tests/ +| | |- package.json <-- "codeflash" config here +| |- utils/ +| | |- src/ +| | |- __tests__/ +| | |- package.json <-- "codeflash" config here +|- package.json <-- workspace root (no codeflash config) +``` + +Run `codeflash init` from within each package: + +```bash +cd packages/core +npx codeflash init +``` + + +**Always run codeflash from the package directory**, not the monorepo root. Codeflash needs to find the `package.json` with the `"codeflash"` config in the current working directory. + + +### Hoisted dependencies + +If your monorepo hoists `node_modules` to the root (Yarn Berry with `nodeLinker: node-modules`, pnpm with `shamefully-hoist`), Codeflash resolves modules using Node.js standard resolution. This works automatically. + +For **pnpm strict mode** (non-hoisted), ensure `codeflash` is a direct dependency of the package: + +```bash +pnpm add --filter @my-org/core --save-dev codeflash +``` + +## Example + +### Standard project + +```text +my-app/ +|- src/ +| |- utils.js +| |- index.js +|- tests/ +| |- utils.test.js +|- package.json +``` + +```json +{ + "name": "my-app", + "codeflash": { + "moduleRoot": "src", + "testsRoot": "tests" + } +} +``` + +### Project with co-located tests + +```text +my-app/ +|- src/ +| |- utils.js +| |- utils.test.js +| |- index.js +|- package.json +``` + +```json +{ + "name": "my-app", + "codeflash": { + "moduleRoot": "src", + "testsRoot": "src" + } +} +``` + +### CommonJS library with no separate test directory + +```text +my-lib/ +|- lib/ +| |- helpers.js +|- test/ +| |- helpers.spec.js +|- package.json +``` + +```json +{ + "name": "my-lib", + "codeflash": { + "moduleRoot": "lib", + "testsRoot": "test" + } +} +``` diff --git a/docs/configuration/python.mdx b/docs/configuration/python.mdx new file mode 100644 index 000000000..765a7ac82 --- /dev/null +++ b/docs/configuration/python.mdx @@ -0,0 +1,80 @@ +--- +title: "Python Configuration" +description: "Configure Codeflash for Python projects using pyproject.toml" +icon: "python" +sidebarTitle: "Python" +keywords: + [ + "configuration", + "pyproject.toml", + "python", + "pytest", + "formatter", + "ruff", + "black", + ] +--- + +# Python Configuration + +Codeflash stores its configuration in `pyproject.toml` under the `[tool.codeflash]` section. + +## Full Reference + +```toml +[tool.codeflash] +# Required +module-root = "my_module" +tests-root = "tests" + +# Optional +formatter-cmds = ["black $file"] +benchmarks-root = "tests/benchmarks" +ignore-paths = ["my_module/build/"] +pytest-cmd = "pytest" +disable-imports-sorting = false +disable-telemetry = false +git-remote = "origin" +override-fixtures = false +``` + +All file paths are relative to the directory of the `pyproject.toml` file. + +## Required Options + +- `module-root`: The Python module to optimize. Only code under this directory will be optimized. It should have an `__init__.py` file to make the module importable. +- `tests-root`: The directory where your tests are located. Codeflash discovers existing tests and generates new ones here. + +## Optional Options + +- `benchmarks-root`: Directory for benchmarks. Required when running with `--benchmark`. +- `ignore-paths`: Paths within `module-root` to skip. Useful for build directories or generated code. +- `pytest-cmd`: Command to run your tests. Defaults to `pytest`. You can add extra arguments here. +- `formatter-cmds`: Formatter/linter commands. `$file` refers to the file being optimized. Disable with `["disabled"]`. + - **ruff** (recommended): `["ruff check --exit-zero --fix $file", "ruff format $file"]` + - **black**: `["black $file"]` +- `disable-imports-sorting`: Disable isort import sorting. Defaults to `false`. +- `disable-telemetry`: Disable anonymized telemetry. Defaults to `false`. +- `git-remote`: Git remote for pull requests. Defaults to `"origin"`. +- `override-fixtures`: Override pytest fixtures during optimization. Defaults to `false`. + +## Example + +```text +acme-project/ +|- foo_module/ +| |- __init__.py +| |- foo.py +| |- main.py +|- tests/ +| |- __init__.py +| |- test_script.py +|- pyproject.toml +``` + +```toml +[tool.codeflash] +module-root = "foo_module" +tests-root = "tests" +ignore-paths = [] +``` \ No newline at end of file diff --git a/docs/getting-started/javascript-installation.mdx b/docs/getting-started/javascript-installation.mdx index abaa2d43d..a19d6cca6 100644 --- a/docs/getting-started/javascript-installation.mdx +++ b/docs/getting-started/javascript-installation.mdx @@ -1,38 +1,51 @@ --- -title: "JavaScript Installation" +title: "JavaScript / TypeScript Installation" description: "Install and configure Codeflash for your JavaScript/TypeScript project" icon: "node-js" +keywords: + [ + "installation", + "javascript", + "typescript", + "npm", + "yarn", + "pnpm", + "bun", + "jest", + "vitest", + "monorepo", + ] --- -Codeflash now supports JavaScript and TypeScript projects with optimized test data serialization using V8 native serialization. +Codeflash supports JavaScript and TypeScript projects. It uses V8 native serialization for test data capture and works with Jest and Vitest test frameworks. ### Prerequisites -Before installing Codeflash for JavaScript, ensure you have: +Before installing Codeflash, ensure you have: -1. **Node.js 16 or above** installed +1. **Node.js 18 or above** installed 2. **A JavaScript/TypeScript project** with a package manager (npm, yarn, pnpm, or bun) 3. **Project dependencies installed** Good to have (optional): -1. **Unit Tests** that Codeflash uses to ensure correctness of the optimizations +1. **Unit tests** (Jest or Vitest) — Codeflash uses them to verify correctness of optimizations -**Node.js Runtime Required** +**Node.js 18+ Required** -Codeflash JavaScript support uses V8 serialization API, which is available natively in Node.js. Make sure you're running on Node.js 16+ for optimal compatibility. +Codeflash requires Node.js 18 or above. Check your version: ```bash -node --version # Should show v16.0.0 or higher +node --version # Should show v18.0.0 or higher ``` - + -Install Codeflash globally or as a development dependency in your project: +Install Codeflash as a development dependency in your project: ```bash npm @@ -50,321 +63,285 @@ pnpm add --save-dev codeflash ```bash bun bun add --dev codeflash ``` - -```bash global -npm install -g codeflash -``` -**Development Dependency Recommended** - -Codeflash is intended for development and CI workflows. Installing as a dev dependency keeps your production bundle clean. - +**Dev dependency recommended** — Codeflash is for development and CI workflows. Installing as a dev dependency keeps your production bundle clean. - - -Navigate to your project's root directory (where your `package.json` file is) and run: + +**Codeflash also requires a Python installation** (3.9+) to run the CLI optimizer. Install the Python CLI globally: ```bash -codeflash init +pip install codeflash +# or +uv tool install codeflash ``` -When running `codeflash init`, you will see the following prompts: +The Python CLI orchestrates the optimization pipeline, while the npm package provides the JavaScript runtime (test runners, serialization, reporters). + + -```text -1. Enter your Codeflash API key (or login with Codeflash) -2. Which JavaScript/TypeScript module do you want me to optimize? (e.g. src/) -3. Where are your tests located? (e.g. tests/, __tests__/, *.test.js) -4. Which test framework do you use? (jest/vitest/mocha/ava/other) -5. Which code formatter do you use? (prettier/eslint/biome/disabled) -6. Which git remote should Codeflash use for Pull Requests? (if multiple remotes exist) -7. Help us improve Codeflash by sharing anonymous usage data? -8. Install the GitHub app -9. Install GitHub actions for Continuous optimization? -``` + +Codeflash uses cloud-hosted AI models. You need an API key: -After you have answered these questions, the Codeflash configuration will be saved in a `codeflash.config.js` file. +1. Visit the [Codeflash Web App](https://app.codeflash.ai/) +2. Sign up with your GitHub account (free tier available) +3. Navigate to the [API Key](https://app.codeflash.ai/app/apikeys) page to generate your key - -**Test Data Serialization Strategy** +Set it as an environment variable: -Codeflash uses **V8 serialization** for JavaScript test data capture. This provides: -- ⚡ **Best performance**: 2-3x faster than alternatives -- 🎯 **Perfect type preservation**: Maintains Date, Map, Set, TypedArrays, and more -- 📦 **Compact binary storage**: Smallest file sizes -- 🔄 **Framework agnostic**: Works with React, Vue, Angular, Svelte, and vanilla JS +```bash +export CODEFLASH_API_KEY="your-api-key-here" +``` - +Or add it to your shell profile (`~/.bashrc`, `~/.zshrc`) for persistence. - -Codeflash uses cloud-hosted AI models and integrations with GitHub. If you haven't created one already, you'll need to create an API key to authorize your access. + +Navigate to your project root (where `package.json` is) and run: -1. Visit the [Codeflash Web App](https://app.codeflash.ai/) -2. Sign up with your GitHub account (free) -3. Navigate to the [API Key](https://app.codeflash.ai/app/apikeys) page to generate your API key + +```bash npm / yarn / pnpm +npx codeflash init +``` + +```bash bun +bunx codeflash init +``` + +```bash Global install +codeflash init +``` + - -**Free Tier Available** +### What `codeflash init` does + +Codeflash **auto-detects** most settings from your project: + +| Setting | How it's detected | +|---------|------------------| +| **Module root** | Looks for `src/`, `lib/`, or the directory containing your source files | +| **Tests root** | Looks for `tests/`, `test/`, `__tests__/`, or files matching `*.test.js` / `*.spec.js` | +| **Test framework** | Checks `devDependencies` for `jest` or `vitest` | +| **Formatter** | Checks for `prettier`, `eslint`, or `biome` in dependencies and config files | +| **Module system** | Reads `"type"` field in `package.json` (ESM vs CommonJS) | +| **TypeScript** | Detects `tsconfig.json` presence | + +You'll be prompted to confirm or override the detected values. The configuration is saved in your `package.json` under the `"codeflash"` key: + +```json +{ + "name": "my-project", + "codeflash": { + "moduleRoot": "src", + "testsRoot": "tests" + } +} +``` -Codeflash offers a **free tier** with a limited number of optimizations. Perfect for trying it out on small projects! + +**No separate config file needed.** Codeflash stores all configuration inside your existing `package.json`, not in a separate config file. + - - + -Finally, if you have not done so already, Codeflash will ask you to install the GitHub App in your repository. -The Codeflash GitHub App allows the codeflash-ai bot to open PRs, review code, and provide optimization suggestions. +To receive optimization PRs automatically, install the Codeflash GitHub App: -Please [install the Codeflash GitHub -app](https://github.com/apps/codeflash-ai/installations/select_target) by choosing the repository you want to install -Codeflash on. +[Install Codeflash GitHub App](https://github.com/apps/codeflash-ai/installations/select_target) + +This enables the codeflash-ai bot to open PRs with optimization suggestions. If you skip this step, you can still optimize locally using `--no-pr`. -## Framework Support - -Codeflash JavaScript support works seamlessly with all major frameworks and testing libraries: - - - - - React - - Vue.js - - Angular - - Svelte - - Solid.js - - - - - Jest - - Vitest - - Mocha - - AVA - - Playwright - - Cypress - - - - - Express - - NestJS - - Fastify - - Koa - - Hono - - - - - Node.js ✅ (Recommended) - - Bun (Coming soon) - - Deno (Coming soon) - - - -## Understanding V8 Serialization - -Codeflash uses Node.js's native V8 serialization API to capture and compare test data. Here's what makes it powerful: - -### Type Preservation - -Unlike JSON serialization, V8 serialization preserves JavaScript-specific types: - -```javascript -// These types are preserved perfectly: -const testData = { - date: new Date(), // ✅ Date objects - map: new Map([['key', 'value']]), // ✅ Map instances - set: new Set([1, 2, 3]), // ✅ Set instances - buffer: Buffer.from('hello'), // ✅ Buffers - typed: new Uint8Array([1, 2, 3]), // ✅ TypedArrays - bigint: 9007199254740991n, // ✅ BigInt - regex: /pattern/gi, // ✅ RegExp - undef: undefined, // ✅ undefined (not null!) - circular: {} // ✅ Circular references -}; -testData.circular.self = testData.circular; -``` +## Monorepo Setup - -**Why Not JSON?** +For monorepos (Yarn workspaces, pnpm workspaces, Lerna, Nx, Turborepo), run `codeflash init` from within each package you want to optimize: -JSON serialization would cause bugs to slip through: -- `Date` becomes string → date arithmetic fails silently -- `Map` becomes `{}` → `.get()` calls return undefined -- `undefined` becomes `null` → type checks break -- TypedArrays become plain objects → binary operations fail +```bash +# Navigate to the specific package +cd packages/my-library -V8 serialization catches these issues during optimization verification. - +# Run init from the package directory +npx codeflash init +``` -## Try It Out! +Each package gets its own `"codeflash"` section in its `package.json`. The `moduleRoot` and `testsRoot` paths are relative to that package's `package.json`. - - -Once configured, you can start optimizing your JavaScript/TypeScript code immediately: +### Example: Yarn workspaces monorepo -```bash -# Optimize a specific function -codeflash --file path/to/your/file.js --function functionName +```text +my-monorepo/ +|- packages/ +| |- core/ +| | |- src/ +| | |- tests/ +| | |- package.json <-- codeflash config here +| |- utils/ +| | |- src/ +| | |- __tests__/ +| | |- package.json <-- codeflash config here +|- package.json <-- root workspace (no codeflash config needed) +``` -# Or optimize all functions in your codebase -codeflash --all +```json +// packages/core/package.json +{ + "name": "@my-org/core", + "codeflash": { + "moduleRoot": "src", + "testsRoot": "tests" + } +} ``` - + +**Run codeflash from the package directory**, not the monorepo root. Codeflash needs to find the `package.json` with the `"codeflash"` config in the current working directory. + - -Codeflash fully supports TypeScript projects: + +**Hoisted dependencies work fine.** If your monorepo hoists `node_modules` to the root (common in Yarn Berry, pnpm with `shamefully-hoist`), Codeflash resolves modules using Node.js standard resolution and will find them correctly. + -```bash -# Optimize TypeScript files directly -codeflash --file src/utils.ts --function processData +## Test Framework Support -# Works with TSX for React components -codeflash --file src/components/DataTable.tsx --function DataTable -``` +| Framework | Status | Auto-detected from | +|-----------|--------|-------------------| +| **Jest** | Supported | `jest` in dependencies | +| **Vitest** | Supported | `vitest` in dependencies | +| **Mocha** | Coming soon | — | -Codeflash preserves TypeScript types during optimization. Your type annotations and interfaces remain intact. +**Functions must be exported** to be optimizable. Codeflash can only discover and optimize functions that are exported from their module (via `export`, `export default`, or `module.exports`). - - - +## Try It Out - -```javascript -// sum.test.js -test('adds 1 + 2 to equal 3', () => { - expect(sum(1, 2)).toBe(3); -}); +Once configured, optimize your code: -// Optimize the sum function -codeflash --file sum.js --function sum + +```bash Optimize a function +codeflash --file src/utils.js --function processData ``` - - - -```javascript -// calculator.test.js -import { describe, it, expect } from 'vitest'; -describe('calculator', () => { - it('should multiply correctly', () => { - expect(multiply(2, 3)).toBe(6); - }); -}); +```bash Optimize locally (no PR) +codeflash --file src/utils.ts --function processData --no-pr +``` -// Optimize the multiply function -codeflash --file calculator.js --function multiply +```bash Optimize entire codebase +codeflash --all ``` - - - +```bash Trace and optimize +codeflash optimize --jest +``` + ## Troubleshooting - - Make sure: - - ✅ All project dependencies are installed - - ✅ Your `node_modules` directory exists + + Codeflash only optimizes **exported** functions. Make sure your function is exported: - ```bash - # Reinstall dependencies - npm install - # or - yarn install + ```javascript + // ES Modules + export function processData(data) { ... } + // or + const processData = (data) => { ... }; + export { processData }; + + // CommonJS + function processData(data) { ... } + module.exports = { processData }; ``` + + If codeflash reports the function exists but is not exported, add an export statement. - - If you encounter serialization errors: + + Ensure the codeflash npm package is installed in your project: - **Functions and classes** cannot be serialized: - ```javascript - // ❌ Won't work - contains function - const data = { callback: () => {} }; - - // ✅ Works - pure data - const data = { value: 42, items: [1, 2, 3] }; + + ```bash npm + npm install --save-dev codeflash ``` - - **Symbols** are not serializable: - ```javascript - // ❌ Won't work - const data = { [Symbol('key')]: 'value' }; - - // ✅ Use string keys - const data = { key: 'value' }; + ```bash yarn + yarn add --dev codeflash + ``` + ```bash pnpm + pnpm add --save-dev codeflash ``` + + + For **monorepos**, make sure it's installed in the package you're optimizing, or at the workspace root if dependencies are hoisted. - - Not all functions can be optimized - some code is already optimal. This is expected. + + Codeflash auto-detects the test framework from your `devDependencies`. If detection fails: + + 1. Verify your test framework is in `devDependencies`: + ```bash + npm ls jest # or: npm ls vitest + ``` + 2. Or set it manually in `package.json`: + ```json + { + "codeflash": { + "testRunner": "jest" + } + } + ``` + - Use the `--verbose` flag for detailed output: - ```bash - codeflash optimize --verbose - ``` + + If Jest tests take too long, Codeflash has a default timeout. For large test suites: - This will show: - - 🔍 Which functions are being analyzed - - 🚫 Why certain functions were skipped - - ⚠️ Detailed error messages - - 📊 Performance analysis results + - Use `--file` and `--function` to target specific functions instead of `--all` + - Ensure your tests don't have expensive setup/teardown that runs for every test file + - Check if `jest.config.js` has a `setupFiles` that takes a long time - - Verify: - - 📁 Your test directory path is correct in `codeflash.config.js` - - 🔍 Tests are discoverable by your test framework - - 📝 Test files follow naming conventions (`*.test.js`, `*.spec.js`) + + Codeflash uses your project's TypeScript configuration. If you see TS errors: - ```bash - # Test if your test framework can discover tests - npm test -- --listTests # Jest - # or - npx vitest list # Vitest - ``` + 1. Verify `npx tsc --noEmit` passes on its own + 2. Check that `tsconfig.json` is in the project root or the module root + 3. For projects using `moduleResolution: "bundler"`, Codeflash creates a temporary tsconfig overlay — this is expected behavior - - -## Configuration -Your `codeflash.config.js` file controls how Codeflash analyzes your JavaScript project: + + Run codeflash from the correct package directory: -```javascript -module.exports = { - // Source code to optimize - module: 'src', + ```bash + cd packages/my-library + codeflash --file src/utils.ts --function myFunc + ``` - // Test location - tests: 'tests', + If your monorepo tool hoists dependencies, you may need to ensure the `codeflash` npm package is accessible from the package directory. For pnpm, add `.npmrc` with `shamefully-hoist=true` or use `pnpm add --filter my-library --save-dev codeflash`. + - // Test framework - testFramework: 'jest', + + Not all functions can be optimized — some code is already efficient. This is normal. - // Serialization strategy (automatically set to 'v8') - serialization: 'v8', + For better results: + - Target functions with loops, string manipulation, or data transformations + - Ensure the function has existing tests for correctness verification + - Use `codeflash optimize --jest` to trace real execution and capture realistic inputs + + - // Formatter - formatter: 'prettier', +## Configuration Reference - // Additional options - exclude: ['node_modules', 'dist', 'build'], - verbose: false -}; -``` +See [JavaScript / TypeScript Configuration](/configuration/javascript) for the full list of options. ### Next Steps -- Learn about [Codeflash Concepts](/codeflash-concepts/how-codeflash-works) -- Explore [Optimization workflows](/optimizing-with-codeflash/one-function) +- Learn [how Codeflash works](/codeflash-concepts/how-codeflash-works) +- [Optimize a single function](/optimizing-with-codeflash/one-function) - Set up [Pull Request Optimization](/optimizing-with-codeflash/codeflash-github-actions) -- Read [configuration options](/configuration) for advanced setups \ No newline at end of file +- Explore [Trace and Optimize](/optimizing-with-codeflash/trace-and-optimize) for workflow optimization diff --git a/docs/index.mdx b/docs/index.mdx index 24d0d1561..b94258ed3 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -1,27 +1,38 @@ --- -title: "Codeflash is an AI performance optimizer for Python code" +title: "Codeflash is an AI performance optimizer for your code" icon: "rocket" sidebarTitle: "Overview" -keywords: ["python", "performance", "optimization", "AI", "code analysis", "benchmarking"] +keywords: ["python", "javascript", "typescript", "performance", "optimization", "AI", "code analysis", "benchmarking"] --- -Codeflash speeds up any Python code by figuring out the best way to rewrite it while verifying that the behavior of the code is unchanged, and verifying real speed -gains through performance benchmarking. +Codeflash speeds up your code by figuring out the best way to rewrite it while verifying that the behavior is unchanged, and verifying real speed +gains through performance benchmarking. It supports **Python**, **JavaScript**, and **TypeScript**. The optimizations Codeflash finds are generally better algorithms, opportunities to remove wasteful compute, better logic, utilizing caching and utilization of more efficient library methods. Codeflash does not modify the system architecture of your code, but it tries to find the most efficient implementation of your current architecture. +### Get Started + + + + Install via pip, uv, or poetry + + + Install via npm, yarn, pnpm, or bun + + + ### How to use Codeflash - Target and optimize individual Python functions for maximum performance gains. + Target and optimize individual functions for maximum performance gains. ```bash - codeflash --file path.py --function my_function + codeflash --file path/to/file --function my_function ``` - + Automatically find optimizations for Pull Requests with GitHub Actions integration. ```bash codeflash init-actions @@ -29,7 +40,7 @@ does not modify the system architecture of your code, but it tries to find the m - End-to-end optimization of entire Python workflows with execution tracing. + End-to-end optimization of entire workflows with execution tracing. ```bash codeflash optimize myscript.py ``` @@ -42,7 +53,6 @@ does not modify the system architecture of your code, but it tries to find the m ``` - ### How does Codeflash verify correctness? diff --git a/docs/optimizing-with-codeflash/benchmarking.mdx b/docs/optimizing-with-codeflash/benchmarking.mdx index ade7eb023..f373cce81 100644 --- a/docs/optimizing-with-codeflash/benchmarking.mdx +++ b/docs/optimizing-with-codeflash/benchmarking.mdx @@ -1,6 +1,6 @@ --- title: "Optimize Performance Benchmarks with every Pull Request" -description: "Configure and use pytest-benchmark integration for performance-critical code optimization" +description: "Configure and use benchmark integration for performance-critical code optimization" icon: "chart-line" sidebarTitle: Setup Benchmarks to Optimize keywords: @@ -26,6 +26,10 @@ It will then try to optimize the new code for the benchmark and calculate the im ## Using Codeflash in Benchmark Mode + + Benchmark mode currently supports Python projects using pytest-benchmark. JavaScript/TypeScript benchmark support is coming soon. + + 1. **Create a benchmarks root:** Create a directory for benchmarks if it does not already exist. @@ -44,7 +48,7 @@ It will then try to optimize the new code for the benchmark and calculate the im 2. **Define your benchmarks:** - Currently, Codeflash only supports benchmarks written as pytest-benchmarks. Check out the [pytest-benchmark](https://pytest-benchmark.readthedocs.io/en/stable/index.html) documentation for more information on syntax. + Codeflash supports benchmarks written as pytest-benchmarks. Check out the [pytest-benchmark](https://pytest-benchmark.readthedocs.io/en/stable/index.html) documentation for more information on syntax. For example: @@ -58,7 +62,7 @@ It will then try to optimize the new code for the benchmark and calculate the im Note that these benchmarks should be defined in such a way that they don't take a long time to run. - The pytest-benchmark format is simply used as an interface. The plugin is actually not used - Codeflash will run these benchmarks with its own pytest plugin + The pytest-benchmark format is simply used as an interface. The plugin is actually not used - Codeflash will run these benchmarks with its own pytest plugin. 3. **Run and Test Codeflash:** @@ -74,7 +78,7 @@ It will then try to optimize the new code for the benchmark and calculate the im codeflash --file test_file.py --benchmark --benchmarks-root path/to/benchmarks ``` -4. **Run Codeflash :** +4. **Run Codeflash with GitHub Actions:** Benchmark mode is best used together with Codeflash as a GitHub Action. This way, Codeflash will trace through your benchmark and optimize the functions modified in your Pull Request to speed up the benchmark. diff --git a/docs/optimizing-with-codeflash/codeflash-all.mdx b/docs/optimizing-with-codeflash/codeflash-all.mdx index 92a232e67..7749817c7 100644 --- a/docs/optimizing-with-codeflash/codeflash-all.mdx +++ b/docs/optimizing-with-codeflash/codeflash-all.mdx @@ -3,13 +3,13 @@ title: "Optimize Your Entire Codebase" description: "Automatically optimize all codepaths in your project with Codeflash's comprehensive analysis" icon: "database" sidebarTitle: "Optimize Entire Codebase" -keywords: ["codebase optimization", "all functions", "batch optimization", "github app", "checkpoint", "recovery"] +keywords: ["codebase optimization", "all functions", "batch optimization", "github app", "checkpoint", "recovery", "javascript", "typescript", "python"] --- # Optimize your entire codebase Codeflash can optimize your entire codebase by analyzing all the functions in your project and generating optimized versions of them. -It iterates through all the functions in your codebase and optimizes them one by one. +It iterates through all the functions in your codebase and optimizes them one by one. This works for Python, JavaScript, and TypeScript projects. To optimize your entire codebase, run the following command in your project directory: @@ -30,15 +30,27 @@ codeflash --all path/to/dir ``` - If your project has a good number of unit tests, we can trace those to achieve higher quality results. - The following approach is recommended instead: + If your project has a good number of unit tests, tracing them achieves higher quality results. + + + ```bash codeflash optimize --trace-only -m pytest tests/ ; codeflash --all ``` - This will run your test suite, trace all the code covered by your tests, ensuring higher correctness guarantees - and better performance benchmarking, and help create optimizations for code where the LLMs struggle to generate and run tests. + + + ```bash + codeflash optimize --trace-only --jest ; codeflash --all + # or for Vitest projects + codeflash optimize --trace-only --vitest ; codeflash --all + ``` + + + + This runs your test suite, traces all the code covered by your tests, ensuring higher correctness guarantees + and better performance benchmarking, and helps create optimizations for code where the LLMs struggle to generate and run tests. - Even though `codeflash --all` discovers any existing unit tests. It currently can only discover any test that directly calls the + `codeflash --all` discovers any existing unit tests, but it currently can only discover tests that directly call the function under optimization. Tracing all the tests helps ensure correctness for code that may be indirectly called by your tests. diff --git a/docs/optimizing-with-codeflash/codeflash-github-actions.mdx b/docs/optimizing-with-codeflash/codeflash-github-actions.mdx index b8da4ebac..dc6418104 100644 --- a/docs/optimizing-with-codeflash/codeflash-github-actions.mdx +++ b/docs/optimizing-with-codeflash/codeflash-github-actions.mdx @@ -26,9 +26,9 @@ We highly recommend setting this up, since once you set it up all your new code ✅ A Codeflash API key from the [Codeflash Web App](https://app.codeflash.ai/) -✅ Completed [local installation](/getting-started/local-installation) with `codeflash init` +✅ Completed local installation with `codeflash init` ([Python](/getting-started/local-installation) or [JavaScript/TypeScript](/getting-started/javascript-installation)) -✅ A Python project with a configured `pyproject.toml` file +✅ A configured project (`pyproject.toml` for Python, `package.json` for JavaScript/TypeScript) ## Setup Options @@ -113,7 +113,7 @@ jobs: -Customize the dependency installation based on your Python package manager: +Customize the dependency installation based on your package manager: The workflow will need to be set up in such a way the Codeflash can create and run tests for functionality and speed, so the stock YAML may need to be altered to @@ -121,7 +121,7 @@ suit the specific codebase. Typically the setup steps for a unit test workflow c be copied. -```yaml Poetry +```yaml Poetry (Python) - name: Install Project Dependencies run: | python -m pip install --upgrade pip @@ -129,11 +129,11 @@ be copied. poetry install --with dev - name: Run Codeflash to optimize code run: | - poetry env use python + poetry env use python poetry run codeflash ``` -```yaml uv +```yaml uv (Python) - uses: astral-sh/setup-uv@v6 with: enable-cache: true @@ -142,7 +142,7 @@ be copied. run: uv run codeflash ``` -```yaml pip +```yaml pip (Python) - name: Install Project Dependencies run: | python -m pip install --upgrade pip @@ -151,7 +151,50 @@ be copied. - name: Run Codeflash to optimize code run: codeflash ``` + +```yaml npm (JavaScript/TypeScript) +- uses: actions/setup-node@v4 + with: + node-version: '18' +- name: Install Project Dependencies + run: npm ci +- name: Run Codeflash to optimize code + run: npx codeflash +``` + +```yaml yarn (JavaScript/TypeScript) +- uses: actions/setup-node@v4 + with: + node-version: '18' +- name: Install Project Dependencies + run: yarn install --immutable +- name: Run Codeflash to optimize code + run: yarn codeflash +``` + +```yaml pnpm (JavaScript/TypeScript) +- uses: pnpm/action-setup@v4 + with: + version: 9 +- uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'pnpm' +- name: Install Project Dependencies + run: pnpm install --frozen-lockfile +- name: Run Codeflash to optimize code + run: pnpm codeflash +``` + + +**Monorepo?** If your codeflash config is in a subdirectory, add `working-directory` to the steps: +```yaml +- name: Run Codeflash to optimize code + run: npx codeflash + working-directory: packages/my-library +``` + diff --git a/docs/optimizing-with-codeflash/one-function.mdx b/docs/optimizing-with-codeflash/one-function.mdx index 8f60db0a7..194531198 100644 --- a/docs/optimizing-with-codeflash/one-function.mdx +++ b/docs/optimizing-with-codeflash/one-function.mdx @@ -1,6 +1,6 @@ --- title: "Optimize a Single Function" -description: "Target and optimize individual Python functions for maximum performance gains" +description: "Target and optimize individual functions for maximum performance gains" icon: "bullseye" sidebarTitle: "Optimize Single Function" keywords: @@ -10,6 +10,9 @@ keywords: "class methods", "performance", "targeted optimization", + "javascript", + "typescript", + "python", ] --- @@ -24,23 +27,55 @@ your mileage may vary. ## How to optimize a function -To optimize a function, you can run the following command in your project: +To optimize a function, run the following command in your project: + + ```bash codeflash --file path/to/your/file.py --function function_name ``` + + +```bash +codeflash --file path/to/your/file.js --function functionName +``` + + +```bash +codeflash --file path/to/your/file.ts --function functionName +``` + + If you have installed the GitHub App to your repository, the above command will open a pull request with the optimized function. -If you want to optimize a function locally, you can add a `--no-pr` argument as follows: +If you want to optimize a function locally, add a `--no-pr` argument: + + ```bash codeflash --file path/to/your/file.py --function function_name --no-pr ``` + + +```bash +codeflash --file path/to/your/file.ts --function functionName --no-pr +``` + + ### Optimizing class methods -To optimize a method `method_name` in a class `ClassName`, you can run the following command: +To optimize a method `methodName` in a class `ClassName`: + + ```bash codeflash --file path/to/your/file.py --function ClassName.method_name ``` + + +```bash +codeflash --file path/to/your/file.ts --function ClassName.methodName +``` + + diff --git a/docs/optimizing-with-codeflash/trace-and-optimize.mdx b/docs/optimizing-with-codeflash/trace-and-optimize.mdx index 3f4e23465..fb62ea1c1 100644 --- a/docs/optimizing-with-codeflash/trace-and-optimize.mdx +++ b/docs/optimizing-with-codeflash/trace-and-optimize.mdx @@ -1,6 +1,6 @@ --- title: "Trace & Optimize E2E Workflows" -description: "End-to-end optimization of entire Python workflows with execution tracing" +description: "End-to-end optimization of entire workflows with execution tracing" icon: "route" sidebarTitle: "Optimize E2E Workflows" keywords: @@ -11,28 +11,50 @@ keywords: "end-to-end", "script optimization", "context manager", + "javascript", + "typescript", + "jest", + "vitest", ] --- -Codeflash can optimize an entire Python script end-to-end by tracing the script's execution and generating Replay Tests. -Tracing follows the execution of a script, profiles it and captures inputs to all functions it called, allowing them to be replayed during optimization. -Codeflash uses these Replay Tests to optimize the most important functions called in the script, delivering the best performance for your workflow. +Codeflash can optimize an entire script or test suite end-to-end by tracing its execution and generating Replay Tests. +Tracing follows the execution of your code, profiles it and captures inputs to all functions it called, allowing them to be replayed during optimization. +Codeflash uses these Replay Tests to optimize the most important functions called in the workflow, delivering the best performance. ![Function Optimization](/images/priority-order.png) -To optimize a script, `python myscript.py`, simply replace `python` with `codeflash optimize` and run the following command: + + +To optimize a script, `python myscript.py`, simply replace `python` with `codeflash optimize`: ```bash codeflash optimize myscript.py ``` -You can also optimize code called by pytest tests that you could normally run like `python -m pytest tests/`, this provides for a good workload to optimize. Run this command: +You can also optimize code called by pytest tests: ```bash codeflash optimize -m pytest tests/ ``` + + +To trace and optimize your Jest or Vitest tests: -The powerful `codeflash optimize` command creates high-quality optimizations, making it ideal when you need to optimize a workflow or script. The initial tracing process can be slow, so try to limit your script's runtime to under 1 minute for best results. If your workflow is longer, consider tracing it into smaller sections by using the Codeflash tracer as a context manager (point 3 below). +```bash +# Jest +codeflash optimize --jest + +# Vitest +codeflash optimize --vitest + +# Or trace a specific script +codeflash optimize --language javascript script.js +``` + + + +The `codeflash optimize` command creates high-quality optimizations, making it ideal when you need to optimize a workflow or script. The initial tracing process can be slow, so try to limit your script's runtime to under 1 minute for best results. The generated replay tests and the trace file are for the immediate optimization use, don't add them to git. @@ -61,6 +83,9 @@ This way you can be _sure_ that the optimized function causes no changes of beha ## Using codeflash optimize + + + Codeflash script optimizer can be used in three ways: 1. **As an integrated command** @@ -100,10 +125,10 @@ Codeflash script optimizer can be used in three ways: - `--timeout`: The maximum time in seconds to trace the entire workflow. Default is indefinite. This is useful while tracing really long workflows. -3. **As a Context Manager -** +3. **As a Context Manager** - To trace only specific sections of your code, You can also use the Codeflash Tracer as a context manager. - You can wrap the code you want to trace in a `with` statement as follows - + To trace only specific sections of your code, you can use the Codeflash Tracer as a context manager. + You can wrap the code you want to trace in a `with` statement as follows: ```python from codeflash.tracer import Tracer @@ -128,3 +153,46 @@ Codeflash script optimizer can be used in three ways: - `output`: The file to save the trace to. Default is `codeflash.trace`. - `config_file_path`: The path to the `pyproject.toml` file which stores the Codeflash config. This is auto-discovered by default. You can also disable the tracer in the code by setting the `disable=True` option in the `Tracer` constructor. + + + + +The JavaScript tracer uses Babel instrumentation to capture function calls during your test suite execution. + +1. **Trace your test suite** + + ```bash + # Jest projects + codeflash optimize --jest + + # Vitest projects + codeflash optimize --vitest + + # Trace a specific script + codeflash optimize --language javascript src/main.js + ``` + +2. **Trace specific functions only** + + ```bash + codeflash optimize --jest --only-functions processData,transformInput + ``` + +3. **Trace and optimize as two separate steps** + + ```bash + # Step 1: Create trace file + codeflash optimize --trace-only --jest --output trace_file.sqlite + + # Step 2: Optimize with replay tests + codeflash --replay-test /path/to/test_replay_test_0.test.js + ``` + + More Options: + + - `--timeout`: Maximum tracing time in seconds. + - `--max-function-count`: Maximum traces per function (default: 256). + - `--only-functions`: Comma-separated list of function names to trace. + + +