fix: share CLI flags between main parser and optimize subparser

30 CLI flags (--verbose, --no-pr, --file, --function, etc.) were defined
only on the main parser and silently dropped when using the `optimize`
subparser path. Uses argparse `parents=[]` pattern to share flags between
both parsers. Also adds --trace-only flag and aligns max_function_count
default to 256.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Mohamed Ashraf 2026-04-10 21:45:07 +00:00
parent 5ee642e35e
commit 0f128eb7d0
2 changed files with 201 additions and 97 deletions

View file

@ -374,14 +374,115 @@ def _handle_reset_config(confirm: bool = True) -> None:
@lru_cache(maxsize=1)
def _build_parser() -> ArgumentParser:
parser = ArgumentParser()
# Shared flags used by both the main parser and the optimize subparser.
# Using argparse parents= ensures flags like --verbose, --no-pr, --file are recognized
# regardless of whether the user runs "codeflash --verbose" or "codeflash optimize --verbose".
shared_flags = ArgumentParser(add_help=False)
shared_flags.add_argument("--file", help="Try to optimize only this file")
shared_flags.add_argument("--function", help="Try to optimize only this function within the given file path")
shared_flags.add_argument(
"--all",
help="Try to optimize all functions. Can take a really long time. Can pass an optional starting directory to"
" optimize code from. If no args specified (just --all), will optimize all code in the project.",
nargs="?",
const="",
default=SUPPRESS,
)
shared_flags.add_argument(
"--module-root",
type=str,
help="Path to the project's module that you want to optimize."
" This is the top-level root directory where all the source code is located.",
)
shared_flags.add_argument(
"--tests-root", type=str, help="Path to the test directory of the project, where all the tests are located."
)
shared_flags.add_argument("--config-file", type=str, help="Path to the pyproject.toml with codeflash configs.")
shared_flags.add_argument(
"--replay-test", type=str, nargs="+", help="Paths to replay test to optimize functions from"
)
shared_flags.add_argument(
"--rerun",
type=str,
help="Rerun a previous optimization by trace ID, using stored LLM results",
metavar="TRACE_ID",
)
shared_flags.add_argument(
"--no-pr", action="store_true", help="Do not create a PR for the optimization, only update the code locally."
)
shared_flags.add_argument(
"--no-gen-tests", action="store_true", help="Do not generate tests, use only existing tests for optimization."
)
shared_flags.add_argument(
"--no-jit-opts", action="store_true", help="Do not generate JIT-compiled optimizations for numerical code."
)
shared_flags.add_argument(
"--staging-review", action="store_true", help="Upload optimizations to staging for review"
)
shared_flags.add_argument(
"--verify-setup",
action="store_true",
help="Verify that codeflash is set up correctly by optimizing bubble sort as a test.",
)
shared_flags.add_argument("-v", "--verbose", action="store_true", help="Print verbose debug logs")
shared_flags.add_argument("--version", action="store_true", help="Print the version of codeflash")
shared_flags.add_argument(
"--benchmark", action="store_true", help="Trace benchmark tests and calculate optimization impact on benchmarks"
)
shared_flags.add_argument(
"--benchmarks-root",
type=str,
help="Path to the directory of the project, where all the pytest-benchmark tests are located.",
)
shared_flags.add_argument("--no-draft", default=False, action="store_true", help="Skip optimization for draft PRs")
shared_flags.add_argument("--worktree", default=False, action="store_true", help="Use worktree for optimization")
shared_flags.add_argument(
"--testgen-review", default=False, action="store_true", help="Enable AI review and repair of generated tests"
)
shared_flags.add_argument(
"--testgen-review-turns", type=int, default=None, help="Number of review/repair cycles (default: 2)"
)
shared_flags.add_argument(
"--async",
default=False,
action="store_true",
help="(Deprecated) Async function optimization is now enabled by default. This flag is ignored.",
)
shared_flags.add_argument(
"--server",
type=str,
choices=["local", "prod"],
help="AI service server to use: 'local' for localhost:8000, 'prod' for app.codeflash.ai",
)
shared_flags.add_argument(
"--effort", type=str, help="Effort level for optimization", choices=["low", "medium", "high"], default="medium"
)
shared_flags.add_argument(
"--show-config", action="store_true", help="Show current or auto-detected configuration and exit."
)
shared_flags.add_argument(
"--reset-config", action="store_true", help="Remove codeflash configuration from project config file."
)
shared_flags.add_argument(
"-y", "--yes", action="store_true", help="Skip confirmation prompts (useful for CI/scripts)."
)
shared_flags.add_argument(
"--subagent",
action="store_true",
help="Subagent mode: skip all interactive prompts with sensible defaults. Designed for AI agent integrations.",
)
shared_flags.add_argument("--trace-only", action="store_true", help="Only trace, do not optimize")
parser = ArgumentParser(parents=[shared_flags])
subparsers = parser.add_subparsers(dest="command", help="Sub-commands")
subparsers.add_parser("init", help="Initialize Codeflash for your project.")
subparsers.add_parser("vscode-install", help="Install the Codeflash VSCode extension")
subparsers.add_parser("init-actions", help="Initialize GitHub Actions workflow")
trace_optimize = subparsers.add_parser("optimize", help="Trace and optimize your project.", add_help=False)
trace_optimize = subparsers.add_parser(
"optimize", help="Trace and optimize your project.", add_help=False, parents=[shared_flags]
)
auth_parser = subparsers.add_parser("auth", help="Authentication commands")
auth_subparsers = auth_parser.add_subparsers(dest="auth_command", help="Auth sub-commands")
auth_subparsers.add_parser("login", help="Log in to Codeflash via OAuth")
@ -419,13 +520,11 @@ def _build_parser() -> ArgumentParser:
trace_optimize.add_argument(
"--max-function-count",
type=int,
default=100,
help="The maximum number of times to trace a single function. More calls to a function will not be traced. Default is 100.",
default=256,
help="The maximum number of times to trace a single function. Default is 256.",
)
trace_optimize.add_argument(
"--timeout",
type=int,
help="The maximum time in seconds to trace the entire workflow. Default is indefinite. This is useful while tracing really long workflows, to not wait indefinitely.",
"--timeout", type=int, help="The maximum time in seconds to trace the entire workflow. Default is indefinite."
)
trace_optimize.add_argument(
"--output",
@ -439,94 +538,4 @@ def _build_parser() -> ArgumentParser:
help="The path to the pyproject.toml file which stores the Codeflash config. This is auto-discovered by default.",
)
parser.add_argument("--file", help="Try to optimize only this file")
parser.add_argument("--function", help="Try to optimize only this function within the given file path")
parser.add_argument(
"--all",
help="Try to optimize all functions. Can take a really long time. Can pass an optional starting directory to"
" optimize code from. If no args specified (just --all), will optimize all code in the project.",
nargs="?",
const="",
default=SUPPRESS,
)
parser.add_argument(
"--module-root",
type=str,
help="Path to the project's module that you want to optimize."
" This is the top-level root directory where all the source code is located.",
)
parser.add_argument(
"--tests-root", type=str, help="Path to the test directory of the project, where all the tests are located."
)
parser.add_argument("--config-file", type=str, help="Path to the pyproject.toml with codeflash configs.")
parser.add_argument("--replay-test", type=str, nargs="+", help="Paths to replay test to optimize functions from")
parser.add_argument(
"--rerun",
type=str,
help="Rerun a previous optimization by trace ID, using stored LLM results",
metavar="TRACE_ID",
)
parser.add_argument(
"--no-pr", action="store_true", help="Do not create a PR for the optimization, only update the code locally."
)
parser.add_argument(
"--no-gen-tests", action="store_true", help="Do not generate tests, use only existing tests for optimization."
)
parser.add_argument(
"--no-jit-opts", action="store_true", help="Do not generate JIT-compiled optimizations for numerical code."
)
parser.add_argument("--staging-review", action="store_true", help="Upload optimizations to staging for review")
parser.add_argument(
"--verify-setup",
action="store_true",
help="Verify that codeflash is set up correctly by optimizing bubble sort as a test.",
)
parser.add_argument("-v", "--verbose", action="store_true", help="Print verbose debug logs")
parser.add_argument("--version", action="store_true", help="Print the version of codeflash")
parser.add_argument(
"--benchmark", action="store_true", help="Trace benchmark tests and calculate optimization impact on benchmarks"
)
parser.add_argument(
"--benchmarks-root",
type=str,
help="Path to the directory of the project, where all the pytest-benchmark tests are located.",
)
parser.add_argument("--no-draft", default=False, action="store_true", help="Skip optimization for draft PRs")
parser.add_argument("--worktree", default=False, action="store_true", help="Use worktree for optimization")
parser.add_argument(
"--testgen-review", default=False, action="store_true", help="Enable AI review and repair of generated tests"
)
parser.add_argument(
"--testgen-review-turns", type=int, default=None, help="Number of review/repair cycles (default: 2)"
)
parser.add_argument(
"--async",
default=False,
action="store_true",
help="(Deprecated) Async function optimization is now enabled by default. This flag is ignored.",
)
parser.add_argument(
"--server",
type=str,
choices=["local", "prod"],
help="AI service server to use: 'local' for localhost:8000, 'prod' for app.codeflash.ai",
)
parser.add_argument(
"--effort", type=str, help="Effort level for optimization", choices=["low", "medium", "high"], default="medium"
)
# Config management flags
parser.add_argument(
"--show-config", action="store_true", help="Show current or auto-detected configuration and exit."
)
parser.add_argument(
"--reset-config", action="store_true", help="Remove codeflash configuration from project config file."
)
parser.add_argument("-y", "--yes", action="store_true", help="Skip confirmation prompts (useful for CI/scripts).")
parser.add_argument(
"--subagent",
action="store_true",
help="Subagent mode: skip all interactive prompts with sensible defaults. Designed for AI agent integrations.",
)
return parser

95
tests/test_cli_args.py Normal file
View file

@ -0,0 +1,95 @@
"""Tests for CLI argument parsing, specifically the optimize subparser flag isolation."""
import sys
from unittest import mock
import pytest
@pytest.fixture(autouse=True)
def _clear_parser_cache():
"""Clear the lru_cache on _build_parser between tests."""
from codeflash.cli_cmds.cli import _build_parser
_build_parser.cache_clear()
yield
_build_parser.cache_clear()
class TestOptimizeSubparserFlags:
"""Test that flags defined on the main parser are also recognized by the optimize subparser."""
def _parse(self, argv: list[str]) -> tuple:
from codeflash.cli_cmds.cli import _build_parser
with mock.patch.object(sys, "argv", argv):
parser = _build_parser()
args, unknown = parser.parse_known_args()
return args, unknown
def test_verbose_flag_recognized_by_optimize_subparser(self) -> None:
args, unknown = self._parse(["codeflash", "optimize", "--verbose", "mvn", "test"])
assert args.verbose is True, f"--verbose should be True, got {args.verbose}"
assert "--verbose" not in unknown, f"--verbose leaked into unknown_args: {unknown}"
def test_no_pr_flag_recognized_by_optimize_subparser(self) -> None:
args, unknown = self._parse(["codeflash", "optimize", "--no-pr", "mvn", "test"])
assert args.no_pr is True, f"--no-pr should be True, got {args.no_pr}"
assert "--no-pr" not in unknown, f"--no-pr leaked into unknown_args: {unknown}"
def test_file_flag_recognized_by_optimize_subparser(self) -> None:
args, unknown = self._parse(["codeflash", "optimize", "--file", "Foo.java", "mvn", "test"])
assert args.file == "Foo.java", f"--file should be 'Foo.java', got {args.file}"
assert "--file" not in unknown, f"--file leaked into unknown_args: {unknown}"
assert "Foo.java" not in unknown, f"file value leaked into unknown_args: {unknown}"
def test_function_flag_recognized_by_optimize_subparser(self) -> None:
args, unknown = self._parse(["codeflash", "optimize", "--function", "bar", "mvn", "test"])
assert args.function == "bar", f"--function should be 'bar', got {args.function}"
assert "--function" not in unknown, f"--function leaked into unknown_args: {unknown}"
def test_multiple_flags_recognized_by_optimize_subparser(self) -> None:
args, unknown = self._parse(
["codeflash", "optimize", "--verbose", "--no-pr", "--file", "X.java", "--function", "foo", "mvn", "test"]
)
assert args.verbose is True
assert args.no_pr is True
assert args.file == "X.java"
assert args.function == "foo"
# Only the Java command should remain in unknown
assert unknown == ["mvn", "test"], f"Expected ['mvn', 'test'], got {unknown}"
def test_flags_after_java_command_not_leaked(self) -> None:
args, unknown = self._parse(["codeflash", "optimize", "mvn", "test"])
assert args.command == "optimize"
assert unknown == ["mvn", "test"]
def test_max_function_count_default_consistency(self) -> None:
args, _ = self._parse(["codeflash", "optimize", "mvn", "test"])
assert args.max_function_count == 256, (
f"max_function_count default should be 256 (matching tracer), got {args.max_function_count}"
)
class TestMainParserFlags:
"""Test that flags still work on the main parser (non-optimize path)."""
def _parse(self, argv: list[str]) -> tuple:
from codeflash.cli_cmds.cli import _build_parser
with mock.patch.object(sys, "argv", argv):
parser = _build_parser()
args, unknown = parser.parse_known_args()
return args, unknown
def test_verbose_on_main_parser(self) -> None:
args, _ = self._parse(["codeflash", "--verbose"])
assert args.verbose is True
def test_file_on_main_parser(self) -> None:
args, _ = self._parse(["codeflash", "--file", "test.java"])
assert args.file == "test.java"
def test_no_pr_on_main_parser(self) -> None:
args, _ = self._parse(["codeflash", "--no-pr"])
assert args.no_pr is True