diff --git a/codeflash/cli_cmds/cli.py b/codeflash/cli_cmds/cli.py
index 630311347..8cd205dab 100644
--- a/codeflash/cli_cmds/cli.py
+++ b/codeflash/cli_cmds/cli.py
@@ -130,9 +130,18 @@ def parse_args() -> Namespace:
"--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.",
+ )
args, unknown_args = parser.parse_known_args()
sys.argv[:] = [sys.argv[0], *unknown_args]
+ if args.subagent:
+ args.yes = True
+ args.no_pr = True
+ args.worktree = True
return process_and_validate_cmd_args(args)
@@ -237,7 +246,18 @@ def process_pyproject_config(args: Namespace) -> Namespace:
set_current_test_framework(pyproject_config["test_framework"])
if args.tests_root is None:
- if is_js_ts_project:
+ if is_java_project:
+ # Try standard Maven/Gradle test directories
+ for test_dir in ["src/test/java", "test", "tests"]:
+ test_path = Path(args.module_root).parent / test_dir if "/" in test_dir else Path(test_dir)
+ if not test_path.is_absolute():
+ test_path = Path.cwd() / test_path
+ if test_path.is_dir():
+ args.tests_root = str(test_path)
+ break
+ if args.tests_root is None:
+ args.tests_root = str(Path.cwd() / "src" / "test" / "java")
+ elif is_js_ts_project:
# Try common JS test directories at project root first
for test_dir in ["test", "tests", "__tests__"]:
if Path(test_dir).is_dir():
@@ -256,17 +276,6 @@ def process_pyproject_config(args: Namespace) -> Namespace:
# In such cases, the user should explicitly configure testsRoot in package.json
if args.tests_root is None:
args.tests_root = args.module_root
- elif is_java_project:
- # Try standard Maven/Gradle test directories
- for test_dir in ["src/test/java", "test", "tests"]:
- test_path = Path(args.module_root).parent / test_dir if "/" in test_dir else Path(test_dir)
- if not test_path.is_absolute():
- test_path = Path.cwd() / test_path
- if test_path.is_dir():
- args.tests_root = str(test_path)
- break
- if args.tests_root is None:
- args.tests_root = str(Path.cwd() / "src" / "test" / "java")
else:
raise AssertionError("--tests-root must be specified")
assert Path(args.tests_root).is_dir(), f"--tests-root {args.tests_root} must be a valid directory"
@@ -327,7 +336,6 @@ def project_root_from_module_root(module_root: Path, pyproject_file_path: Path)
return current.resolve()
if (current / "build.gradle").exists() or (current / "build.gradle.kts").exists():
return current.resolve()
- # Check for config file (pyproject.toml for Python, codeflash.toml for other languages)
if (current / "codeflash.toml").exists():
return current.resolve()
current = current.parent
@@ -378,32 +386,52 @@ def _handle_show_config() -> None:
from codeflash.setup.detector import detect_project, has_existing_config
project_root = Path.cwd()
- detected = detect_project(project_root)
+ config_exists, _ = has_existing_config(project_root)
- # Check if config exists or is auto-detected
- config_exists, config_file = has_existing_config(project_root)
- status = "Saved config" if config_exists else "Auto-detected (not saved)"
+ if config_exists:
+ from codeflash.code_utils.config_parser import parse_config_file
- console.print()
- console.print(f"[bold]Codeflash Configuration[/bold] ({status})")
- if config_exists and config_file:
- console.print(f"[dim]Config file: {project_root / config_file}[/dim]")
- console.print()
+ config, config_file_path = parse_config_file()
+ status = "Saved config"
- table = Table(show_header=True, header_style="bold cyan")
- table.add_column("Setting", style="dim")
- table.add_column("Value")
+ console.print()
+ console.print(f"[bold]Codeflash Configuration[/bold] ({status})")
+ console.print(f"[dim]Config file: {config_file_path}[/dim]")
+ console.print()
- table.add_row("Language", detected.language)
- table.add_row("Project root", str(detected.project_root))
- table.add_row("Module root", str(detected.module_root))
- table.add_row("Tests root", str(detected.tests_root) if detected.tests_root else "(not detected)")
- table.add_row("Test runner", detected.test_runner or "(not detected)")
- table.add_row("Formatter", ", ".join(detected.formatter_cmds) if detected.formatter_cmds else "(not detected)")
- table.add_row(
- "Ignore paths", ", ".join(str(p) for p in detected.ignore_paths) if detected.ignore_paths else "(none)"
- )
- table.add_row("Confidence", f"{detected.confidence:.0%}")
+ table = Table(show_header=True, header_style="bold cyan")
+ table.add_column("Setting", style="dim")
+ table.add_column("Value")
+
+ table.add_row("Project root", str(project_root))
+ table.add_row("Module root", config.get("module_root", "(not set)"))
+ table.add_row("Tests root", config.get("tests_root", "(not set)"))
+ table.add_row("Test runner", config.get("test_framework", config.get("pytest_cmd", "(not set)")))
+ table.add_row("Formatter", ", ".join(config["formatter_cmds"]) if config.get("formatter_cmds") else "(not set)")
+ ignore_paths = config.get("ignore_paths", [])
+ table.add_row("Ignore paths", ", ".join(str(p) for p in ignore_paths) if ignore_paths else "(none)")
+ else:
+ detected = detect_project(project_root)
+ status = "Auto-detected (not saved)"
+
+ console.print()
+ console.print(f"[bold]Codeflash Configuration[/bold] ({status})")
+ console.print()
+
+ table = Table(show_header=True, header_style="bold cyan")
+ table.add_column("Setting", style="dim")
+ table.add_column("Value")
+
+ table.add_row("Language", detected.language)
+ table.add_row("Project root", str(detected.project_root))
+ table.add_row("Module root", str(detected.module_root))
+ table.add_row("Tests root", str(detected.tests_root) if detected.tests_root else "(not detected)")
+ table.add_row("Test runner", detected.test_runner or "(not detected)")
+ table.add_row("Formatter", ", ".join(detected.formatter_cmds) if detected.formatter_cmds else "(not detected)")
+ table.add_row(
+ "Ignore paths", ", ".join(str(p) for p in detected.ignore_paths) if detected.ignore_paths else "(none)"
+ )
+ table.add_row("Confidence", f"{detected.confidence:.0%}")
console.print(table)
console.print()
@@ -436,7 +464,7 @@ def _handle_reset_config(confirm: bool = True) -> None:
console.print("[bold]This will remove Codeflash configuration from your project.[/bold]")
console.print()
- config_file = {"python": "pyproject.toml", "java": "codeflash.toml"}.get(detected.language, "package.json")
+ config_file = "pyproject.toml" if detected.language == "python" else "package.json"
console.print(f" Config file: {project_root / config_file}")
console.print()
diff --git a/codeflash/cli_cmds/console.py b/codeflash/cli_cmds/console.py
index 5ff215057..8b64cee18 100644
--- a/codeflash/cli_cmds/console.py
+++ b/codeflash/cli_cmds/console.py
@@ -22,7 +22,7 @@ from rich.progress import (
from codeflash.cli_cmds.console_constants import SPINNER_TYPES
from codeflash.cli_cmds.logging_config import BARE_LOGGING_FORMAT
-from codeflash.lsp.helpers import is_LSP_enabled
+from codeflash.lsp.helpers import is_LSP_enabled, is_subagent_mode
from codeflash.lsp.lsp_logger import enhanced_log
from codeflash.lsp.lsp_message import LspCodeMessage, LspTextMessage
@@ -35,42 +35,69 @@ if TYPE_CHECKING:
from codeflash.discovery.functions_to_optimize import FunctionToOptimize
from codeflash.languages.base import DependencyResolver, IndexResult
from codeflash.lsp.lsp_message import LspMessage
+ from codeflash.models.models import TestResults
DEBUG_MODE = logging.getLogger().getEffectiveLevel() == logging.DEBUG
console = Console(highlighter=NullHighlighter())
-if is_LSP_enabled():
+if is_LSP_enabled() or is_subagent_mode():
console.quiet = True
-logging.basicConfig(
- level=logging.INFO,
- handlers=[
- RichHandler(
- rich_tracebacks=True,
- markup=False,
- highlighter=NullHighlighter(),
- console=console,
- show_path=False,
- show_time=False,
- )
- ],
- format=BARE_LOGGING_FORMAT,
-)
+if is_subagent_mode():
+ import re
+ import sys
+
+ _lsp_prefix_re = re.compile(r"^(?:!?lsp,?|h[2-4]|loading)\|")
+ _subagent_drop_patterns = (
+ "Test log -",
+ "Test failed to load",
+ "Examining file ",
+ "Generated ",
+ "Add custom marker",
+ "Disabling all autouse",
+ "Reverting code and helpers",
+ )
+
+ class _AgentLogFilter(logging.Filter):
+ def filter(self, record: logging.LogRecord) -> bool:
+ record.msg = _lsp_prefix_re.sub("", str(record.msg))
+ msg = record.getMessage()
+ return not any(msg.startswith(p) for p in _subagent_drop_patterns)
+
+ _agent_handler = logging.StreamHandler(sys.stderr)
+ _agent_handler.addFilter(_AgentLogFilter())
+ logging.basicConfig(level=logging.INFO, handlers=[_agent_handler], format="%(levelname)s: %(message)s")
+else:
+ logging.basicConfig(
+ level=logging.INFO,
+ handlers=[
+ RichHandler(
+ rich_tracebacks=True,
+ markup=False,
+ highlighter=NullHighlighter(),
+ console=console,
+ show_path=False,
+ show_time=False,
+ )
+ ],
+ format=BARE_LOGGING_FORMAT,
+ )
logger = logging.getLogger("rich")
logging.getLogger("parso").setLevel(logging.WARNING)
# override the logger to reformat the messages for the lsp
-for level in ("info", "debug", "warning", "error"):
- real_fn = getattr(logger, level)
- setattr(
- logger,
- level,
- lambda msg, *args, _real_fn=real_fn, _level=level, **kwargs: enhanced_log(
- msg, _real_fn, _level, *args, **kwargs
- ),
- )
+if not is_subagent_mode():
+ for level in ("info", "debug", "warning", "error"):
+ real_fn = getattr(logger, level)
+ setattr(
+ logger,
+ level,
+ lambda msg, *args, _real_fn=real_fn, _level=level, **kwargs: enhanced_log(
+ msg, _real_fn, _level, *args, **kwargs
+ ),
+ )
class DummyTask:
@@ -97,6 +124,8 @@ def paneled_text(
text: str, panel_args: dict[str, str | bool] | None = None, text_args: dict[str, str] | None = None
) -> None:
"""Print text in a panel."""
+ if is_subagent_mode():
+ return
from rich.panel import Panel
from rich.text import Text
@@ -125,6 +154,8 @@ def code_print(
language: Programming language for syntax highlighting ('python', 'javascript', 'typescript')
"""
+ if is_subagent_mode():
+ return
if is_LSP_enabled():
lsp_log(
LspCodeMessage(code=code_str, file_name=file_name, function_name=function_name, message_id=lsp_message_id)
@@ -162,6 +193,10 @@ def progress_bar(
"""
global _progress_bar_active
+ if is_subagent_mode():
+ yield DummyTask().id
+ return
+
if is_LSP_enabled():
lsp_log(LspTextMessage(text=message, takes_time=True))
yield
@@ -193,6 +228,10 @@ def progress_bar(
@contextmanager
def test_files_progress_bar(total: int, description: str) -> Generator[tuple[Progress, TaskID], None, None]:
"""Progress bar for test files."""
+ if is_subagent_mode():
+ yield DummyProgress(), DummyTask().id
+ return
+
if is_LSP_enabled():
lsp_log(LspTextMessage(text=description, takes_time=True))
dummy_progress = DummyProgress()
@@ -226,6 +265,10 @@ def call_graph_live_display(
from rich.text import Text
from rich.tree import Tree
+ if is_subagent_mode():
+ yield lambda _: None
+ return
+
if is_LSP_enabled():
lsp_log(LspTextMessage(text="Building call graph", takes_time=True))
yield lambda _: None
@@ -333,6 +376,9 @@ def call_graph_summary(call_graph: DependencyResolver, file_to_funcs: dict[Path,
if not total_functions:
return
+ if is_subagent_mode():
+ return
+
# Build the mapping expected by the dependency resolver
file_items = file_to_funcs.items()
mapping = {file_path: {func.qualified_name for func in funcs} for file_path, funcs in file_items}
@@ -359,3 +405,92 @@ def call_graph_summary(call_graph: DependencyResolver, file_to_funcs: dict[Path,
return
console.print(Panel(summary, title="Call Graph Summary", border_style="cyan"))
+
+
+def subagent_log_optimization_result(
+ function_name: str,
+ file_path: Path,
+ perf_improvement_line: str,
+ original_runtime_ns: int,
+ best_runtime_ns: int,
+ raw_explanation: str,
+ original_code: dict[Path, str],
+ new_code: dict[Path, str],
+ review: str,
+ test_results: TestResults,
+) -> None:
+ import sys
+ from xml.sax.saxutils import escape
+
+ from codeflash.code_utils.code_utils import unified_diff_strings
+ from codeflash.code_utils.time_utils import humanize_runtime
+ from codeflash.models.test_type import TestType
+
+ diff_parts = []
+ for path in original_code:
+ old = original_code.get(path, "")
+ new = new_code.get(path, "")
+ if old != new:
+ diff = unified_diff_strings(old, new, fromfile=str(path), tofile=str(path))
+ if diff:
+ diff_parts.append(diff)
+
+ diff_str = "\n".join(diff_parts)
+
+ original_runtime = humanize_runtime(original_runtime_ns)
+ optimized_runtime = humanize_runtime(best_runtime_ns)
+
+ report = test_results.get_test_pass_fail_report_by_type()
+ verification_rows = []
+ for test_type in TestType:
+ if test_type is TestType.INIT_STATE_TEST:
+ continue
+ name = test_type.to_name()
+ if not name:
+ continue
+ passed = report[test_type]["passed"]
+ failed = report[test_type]["failed"]
+ if passed == 0 and failed == 0:
+ status = "None Found"
+ elif failed > 0:
+ status = f"{failed} Failed, {passed} Passed"
+ else:
+ status = f"{passed} Passed"
+ verification_rows.append(f' ')
+
+ xml = [
+ "",
+ f" {escape(function_name)}",
+ f" {escape(str(file_path))}",
+ f" {escape(perf_improvement_line)}",
+ f" {escape(original_runtime)}",
+ f" {escape(optimized_runtime)}",
+ ]
+ if review:
+ xml.append(f" {escape(review)}")
+ xml.append(f" {escape(raw_explanation.strip())}")
+ xml.append(" ")
+ xml.extend(verification_rows)
+ xml.append(" ")
+ if diff_str:
+ xml.append(f" {escape(diff_str)}")
+ for path in new_code:
+ if new_code[path] != original_code.get(path, ""):
+ xml.append(f' {escape(new_code[path])}')
+ xml.append(" ")
+ xml.append(" First, present a short summary: function name, performance improvement, and explanation.")
+ xml.append(" If you are running inside an IDE (VS Code, Cursor, Windsurf, etc.):")
+ xml.append(" For each optimized-code element, use the Write tool to write the optimized code to the file.")
+ xml.append(" This will surface the diff in the IDE's native diff view for the user to accept or reject.")
+ xml.append(" If you are running in a terminal without an IDE connection:")
+ xml.append(" Present this optimization to the user using AskUserQuestion.")
+ xml.append(" Show the function name, performance improvement, and explanation as the question text.")
+ xml.append(" Provide two options: 'Apply' (write the optimized code to the file) and 'Reject' (do nothing).")
+ xml.append(
+ " Put the full diff in the 'Apply' option's markdown preview so the user can review the exact changes."
+ )
+ xml.append(" If the user chooses 'Apply', write the content from optimized-code to the corresponding file.")
+ xml.append(" ")
+ xml.append("")
+
+ sys.stdout.write("\n".join(xml) + "\n")
diff --git a/codeflash/cli_cmds/logging_config.py b/codeflash/cli_cmds/logging_config.py
index dbb3663bd..296a0b0fa 100644
--- a/codeflash/cli_cmds/logging_config.py
+++ b/codeflash/cli_cmds/logging_config.py
@@ -5,8 +5,18 @@ BARE_LOGGING_FORMAT = "%(message)s"
def set_level(level: int, *, echo_setting: bool = True) -> None:
import logging
+ import sys
import time
+ from codeflash.lsp.helpers import is_subagent_mode
+
+ if is_subagent_mode():
+ logging.basicConfig(
+ level=level, handlers=[logging.StreamHandler(sys.stderr)], format="%(levelname)s: %(message)s", force=True
+ )
+ logging.getLogger().setLevel(level)
+ return
+
from rich.highlighter import NullHighlighter
from rich.logging import RichHandler