fix: align multi-language discovery with zero-config Java and add monorepo subdirectory scanning
Some checks are pending
Claude Code / pr-review (pull_request) Waiting to run
Claude Code / claude-mention (pull_request) Waiting to run
CodeFlash / Optimize new Python code (pull_request) Waiting to run
E2E - Async / async-optimization (pull_request) Waiting to run
E2E - Bubble Sort Benchmark / benchmark-bubble-sort-optimization (pull_request) Waiting to run
E2E - Bubble Sort Pytest (No Git) / bubble-sort-optimization-pytest-no-git (pull_request) Waiting to run
E2E - Bubble Sort Unittest / bubble-sort-optimization-unittest (pull_request) Waiting to run
Coverage E2E / end-to-end-test-coverage (pull_request) Waiting to run
E2E - Futurehouse Structure / futurehouse-structure (pull_request) Waiting to run
E2E - Init Optimization / init-optimization (pull_request) Waiting to run
E2E - Java Fibonacci (No Git) / java-fibonacci-optimization-no-git (pull_request) Waiting to run
E2E - Java Tracer / java-tracer-e2e (pull_request) Waiting to run
E2E - JS CommonJS Function / js-cjs-function-optimization (pull_request) Waiting to run
E2E - JS ESM Async / js-esm-async-optimization (pull_request) Waiting to run
E2E - JS TypeScript Class / js-ts-class-optimization (pull_request) Waiting to run
E2E - Topological Sort (Worktree) / topological-sort-worktree-optimization (pull_request) Waiting to run
E2E - Tracer Replay / tracer-replay (pull_request) Waiting to run
Java E2E Tests / java-e2e (pull_request) Waiting to run
PR Labeler / label-workflow-changes (pull_request) Waiting to run
Mypy Type Checking for CLI / type-check-cli (pull_request) Waiting to run
Lint / prek (pull_request) Waiting to run
unit-tests / unit-tests (ubuntu-latest, 3.12) (pull_request) Waiting to run
unit-tests / unit-tests (ubuntu-latest, 3.13) (pull_request) Waiting to run
unit-tests / unit-tests (ubuntu-latest, 3.14) (pull_request) Waiting to run
unit-tests / unit-tests (ubuntu-latest, 3.9) (pull_request) Waiting to run
unit-tests / unit-tests (ubuntu-latest, 3.10) (pull_request) Waiting to run
unit-tests / unit-tests (ubuntu-latest, 3.11) (pull_request) Waiting to run
unit-tests / unit-tests (windows-latest, 3.13) (pull_request) Waiting to run

Adapt find_all_config_files() after rebasing on java-config-redesign (PR #1880):
- Java detected via pom.xml/build.gradle instead of codeflash.toml
- Add subdirectory scan for monorepo language subprojects (java/, js/ etc.)
- Extract _check_dir_for_configs() to eliminate duplicated detection logic
- Fix --all flag in multi-language mode (module_root wasn't available during resolution)
- Add Java project_root directory override in apply_language_config()
- Update all tests to use build-tool detection mocks and directory-based Java paths
- Add 5 new monorepo discovery tests (subdir Java, subdir JS, all-three, skip-hidden, root-wins)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Mohamed Ashraf 2026-03-25 21:17:16 +00:00
parent e41517d439
commit cdaa52608c
5 changed files with 215 additions and 65 deletions

View file

@ -328,6 +328,11 @@ def apply_language_config(args: Namespace, lang_config: LanguageConfig) -> Names
args.benchmarks_root = Path(args.benchmarks_root).resolve()
args.test_project_root = project_root_from_module_root(args.tests_root, config_path)
if is_java and config_path.is_dir():
# For Java projects, config_path IS the project root directory (from build-tool detection).
args.project_root = config_path.resolve()
args.test_project_root = config_path.resolve()
return args

View file

@ -157,33 +157,38 @@ def normalize_toml_config(config: dict[str, Any], config_file_path: Path) -> dic
return config
def find_all_config_files(start_dir: Path | None = None) -> list[LanguageConfig]:
if start_dir is None:
start_dir = Path.cwd()
def _parse_java_config_for_dir(dir_path: Path) -> dict[str, Any] | None:
from codeflash.languages.java.build_tools import parse_java_project_config
configs: list[LanguageConfig] = []
seen_languages: set[Language] = set()
return parse_java_project_config(dir_path)
toml_configs = {"pyproject.toml": Language.PYTHON, "codeflash.toml": Language.JAVA}
dir_path = start_dir.resolve()
while True:
for config_name, language in toml_configs.items():
if language in seen_languages:
continue
config_file = dir_path / config_name
if config_file.exists():
_SUBDIR_SKIP = frozenset({
".git", ".hg", ".svn", "node_modules", ".venv", "venv", "__pycache__",
"target", "build", "dist", ".tox", ".mypy_cache", ".ruff_cache", ".pytest_cache",
})
def _check_dir_for_configs(
dir_path: Path,
configs: list[LanguageConfig],
seen_languages: set[Language],
) -> None:
"""Check a single directory for language config files and append any found to *configs*."""
if Language.PYTHON not in seen_languages:
pyproject = dir_path / "pyproject.toml"
if pyproject.exists():
try:
with config_file.open("rb") as f:
with pyproject.open("rb") as f:
data = tomlkit.parse(f.read())
tool = data.get("tool", {})
if isinstance(tool, dict) and "codeflash" in tool:
raw_config = dict(tool["codeflash"])
normalized = normalize_toml_config(raw_config, config_file)
seen_languages.add(language)
configs.append(LanguageConfig(config=normalized, config_path=config_file, language=language))
normalized = normalize_toml_config(raw_config, pyproject)
seen_languages.add(Language.PYTHON)
configs.append(LanguageConfig(config=normalized, config_path=pyproject, language=Language.PYTHON))
except Exception:
continue
pass
if Language.JAVASCRIPT not in seen_languages:
package_json = dir_path / "package.json"
@ -197,11 +202,47 @@ def find_all_config_files(start_dir: Path | None = None) -> list[LanguageConfig]
except Exception:
pass
if Language.JAVA not in seen_languages:
if (
(dir_path / "pom.xml").exists()
or (dir_path / "build.gradle").exists()
or (dir_path / "build.gradle.kts").exists()
):
try:
java_config = _parse_java_config_for_dir(dir_path)
if java_config is not None:
seen_languages.add(Language.JAVA)
configs.append(LanguageConfig(config=java_config, config_path=dir_path, language=Language.JAVA))
except Exception:
pass
def find_all_config_files(start_dir: Path | None = None) -> list[LanguageConfig]:
if start_dir is None:
start_dir = Path.cwd()
configs: list[LanguageConfig] = []
seen_languages: set[Language] = set()
# Walk upward from start_dir to filesystem root (closest config wins per language)
dir_path = start_dir.resolve()
while True:
_check_dir_for_configs(dir_path, configs, seen_languages)
parent = dir_path.parent
if parent == dir_path:
break
dir_path = parent
# Scan immediate subdirectories for monorepo language subprojects
resolved_start = start_dir.resolve()
try:
subdirs = sorted(p for p in resolved_start.iterdir() if p.is_dir() and p.name not in _SUBDIR_SKIP)
except OSError:
subdirs = []
for subdir in subdirs:
_check_dir_for_configs(subdir, configs, seen_languages)
return configs

View file

@ -131,6 +131,11 @@ def main() -> None:
except UnsupportedLanguageError:
pass # Unknown extension, let all configs run
# Track whether --all was originally requested (before handle_optimize_all_arg_parsing
# resolves it — in multi-language mode, module_root isn't available yet so the resolution
# produces None; we re-resolve per language inside the loop)
optimize_all_requested = hasattr(args, "all") and args.all is not None
# Multi-language path: run git/GitHub checks ONCE before the loop
args = handle_optimize_all_arg_parsing(args)
@ -141,7 +146,7 @@ def main() -> None:
pass_args = copy.deepcopy(args)
pass_args = apply_language_config(pass_args, lang_config)
if hasattr(pass_args, "all") and pass_args.all is not None:
if optimize_all_requested:
pass_args.all = pass_args.module_root
if not env_utils.check_formatter_installed(pass_args.formatter_cmds):

View file

@ -2,10 +2,11 @@ from __future__ import annotations
import json
from pathlib import Path
from unittest.mock import patch
import tomlkit
from codeflash.code_utils.config_parser import LanguageConfig, find_all_config_files
from codeflash.code_utils.config_parser import find_all_config_files
from codeflash.languages.language_enum import Language
@ -22,18 +23,28 @@ class TestFindAllConfigFiles:
assert result[0].language == Language.PYTHON
assert result[0].config_path == tmp_path / "pyproject.toml"
def test_finds_codeflash_toml(self, tmp_path: Path, monkeypatch) -> None:
write_toml(tmp_path / "codeflash.toml", {"tool": {"codeflash": {"module-root": "src/main/java"}}})
def test_finds_java_via_build_tool_detection(self, tmp_path: Path, monkeypatch) -> None:
java_config = {"language": "java", "module_root": str(tmp_path / "src/main/java")}
(tmp_path / "pom.xml").write_text("<project/>", encoding="utf-8")
monkeypatch.chdir(tmp_path)
with patch(
"codeflash.code_utils.config_parser._parse_java_config_for_dir",
return_value=java_config,
):
result = find_all_config_files()
assert len(result) == 1
assert result[0].language == Language.JAVA
assert result[0].config_path == tmp_path / "codeflash.toml"
assert result[0].config_path == tmp_path
def test_finds_multiple_configs(self, tmp_path: Path, monkeypatch) -> None:
def test_finds_multiple_configs_python_and_java(self, tmp_path: Path, monkeypatch) -> None:
write_toml(tmp_path / "pyproject.toml", {"tool": {"codeflash": {"module-root": "src"}}})
write_toml(tmp_path / "codeflash.toml", {"tool": {"codeflash": {"module-root": "src/main/java"}}})
java_config = {"language": "java", "module_root": str(tmp_path / "src/main/java")}
(tmp_path / "pom.xml").write_text("<project/>", encoding="utf-8")
monkeypatch.chdir(tmp_path)
with patch(
"codeflash.code_utils.config_parser._parse_java_config_for_dir",
return_value=java_config,
):
result = find_all_config_files()
assert len(result) == 2
languages = {r.language for r in result}
@ -49,8 +60,13 @@ class TestFindAllConfigFiles:
write_toml(tmp_path / "pyproject.toml", {"tool": {"codeflash": {"module-root": "src"}}})
subdir = tmp_path / "subproject"
subdir.mkdir()
write_toml(subdir / "codeflash.toml", {"tool": {"codeflash": {"module-root": "src/main/java"}}})
java_config = {"language": "java", "module_root": str(subdir / "src/main/java")}
(subdir / "pom.xml").write_text("<project/>", encoding="utf-8")
monkeypatch.chdir(subdir)
with patch(
"codeflash.code_utils.config_parser._parse_java_config_for_dir",
return_value=java_config,
):
result = find_all_config_files()
assert len(result) == 2
languages = {r.language for r in result}
@ -78,27 +94,111 @@ class TestFindAllConfigFiles:
def test_finds_all_three_config_types(self, tmp_path: Path, monkeypatch) -> None:
write_toml(tmp_path / "pyproject.toml", {"tool": {"codeflash": {"module-root": "src"}}})
write_toml(tmp_path / "codeflash.toml", {"tool": {"codeflash": {"module-root": "src/main/java"}}})
pkg = {"codeflash": {"moduleRoot": "src"}}
(tmp_path / "package.json").write_text(json.dumps(pkg), encoding="utf-8")
java_config = {"language": "java", "module_root": str(tmp_path / "src/main/java")}
(tmp_path / "pom.xml").write_text("<project/>", encoding="utf-8")
monkeypatch.chdir(tmp_path)
with patch(
"codeflash.code_utils.config_parser._parse_java_config_for_dir",
return_value=java_config,
):
result = find_all_config_files()
assert len(result) == 3
languages = {r.language for r in result}
assert languages == {Language.PYTHON, Language.JAVA, Language.JAVASCRIPT}
def test_malformed_toml_skipped(self, tmp_path: Path, monkeypatch) -> None:
(tmp_path / "codeflash.toml").write_text("not valid [toml", encoding="utf-8")
def test_no_java_when_no_build_file_exists(self, tmp_path: Path, monkeypatch) -> None:
monkeypatch.chdir(tmp_path)
result = find_all_config_files()
assert len(result) == 0
def test_missing_codeflash_section_skipped(self, tmp_path: Path, monkeypatch) -> None:
write_toml(tmp_path / "codeflash.toml", {"tool": {"other": {"key": "value"}}})
write_toml(tmp_path / "pyproject.toml", {"tool": {"other": {"key": "value"}}})
monkeypatch.chdir(tmp_path)
result = find_all_config_files()
assert len(result) == 0
def test_finds_java_in_subdirectory(self, tmp_path: Path, monkeypatch) -> None:
"""Monorepo: Java project in a subdirectory is discovered from the repo root."""
write_toml(tmp_path / "pyproject.toml", {"tool": {"codeflash": {"module-root": "src"}}})
java_dir = tmp_path / "java"
java_dir.mkdir()
(java_dir / "pom.xml").write_text("<project/>", encoding="utf-8")
java_config = {"language": "java", "module_root": str(java_dir / "src/main/java")}
monkeypatch.chdir(tmp_path)
with patch(
"codeflash.code_utils.config_parser._parse_java_config_for_dir",
return_value=java_config,
):
result = find_all_config_files()
assert len(result) == 2
languages = {r.language for r in result}
assert languages == {Language.PYTHON, Language.JAVA}
java_result = next(r for r in result if r.language == Language.JAVA)
assert java_result.config_path == java_dir
def test_finds_js_in_subdirectory(self, tmp_path: Path, monkeypatch) -> None:
"""Monorepo: JS project in a subdirectory is discovered from the repo root."""
write_toml(tmp_path / "pyproject.toml", {"tool": {"codeflash": {"module-root": "src"}}})
js_dir = tmp_path / "js"
js_dir.mkdir()
pkg = {"codeflash": {"moduleRoot": "src"}}
(js_dir / "package.json").write_text(json.dumps(pkg), encoding="utf-8")
monkeypatch.chdir(tmp_path)
result = find_all_config_files()
assert len(result) == 2
languages = {r.language for r in result}
assert languages == {Language.PYTHON, Language.JAVASCRIPT}
def test_finds_all_three_in_monorepo_subdirs(self, tmp_path: Path, monkeypatch) -> None:
"""Monorepo: Python at root, Java and JS in subdirectories."""
write_toml(tmp_path / "pyproject.toml", {"tool": {"codeflash": {"module-root": "src"}}})
java_dir = tmp_path / "java"
java_dir.mkdir()
(java_dir / "pom.xml").write_text("<project/>", encoding="utf-8")
java_config = {"language": "java", "module_root": str(java_dir / "src/main/java")}
js_dir = tmp_path / "js"
js_dir.mkdir()
pkg = {"codeflash": {"moduleRoot": "src"}}
(js_dir / "package.json").write_text(json.dumps(pkg), encoding="utf-8")
monkeypatch.chdir(tmp_path)
with patch(
"codeflash.code_utils.config_parser._parse_java_config_for_dir",
return_value=java_config,
):
result = find_all_config_files()
assert len(result) == 3
languages = {r.language for r in result}
assert languages == {Language.PYTHON, Language.JAVA, Language.JAVASCRIPT}
def test_skips_hidden_and_build_subdirs(self, tmp_path: Path, monkeypatch) -> None:
"""Subdirectory scan skips .git, node_modules, target, etc."""
for name in [".git", "node_modules", "target", "build", "__pycache__"]:
d = tmp_path / name
d.mkdir()
write_toml(d / "pyproject.toml", {"tool": {"codeflash": {"module-root": "."}}})
monkeypatch.chdir(tmp_path)
result = find_all_config_files()
assert len(result) == 0
def test_root_config_wins_over_subdir(self, tmp_path: Path, monkeypatch) -> None:
"""Config at CWD (found during upward walk) takes precedence over subdirectory."""
(tmp_path / "pom.xml").write_text("<project/>", encoding="utf-8")
java_dir = tmp_path / "java"
java_dir.mkdir()
(java_dir / "pom.xml").write_text("<project/>", encoding="utf-8")
java_config = {"language": "java", "module_root": str(tmp_path / "src/main/java")}
monkeypatch.chdir(tmp_path)
with patch(
"codeflash.code_utils.config_parser._parse_java_config_for_dir",
return_value=java_config,
):
result = find_all_config_files()
java_results = [r for r in result if r.language == Language.JAVA]
assert len(java_results) == 1
assert java_results[0].config_path == tmp_path
def test_find_all_functions_uses_registry_not_singleton() -> None:
"""DISC-04: Verify find_all_functions_in_file uses per-file registry lookup."""

View file

@ -1,6 +1,5 @@
from __future__ import annotations
import json
import logging
from argparse import Namespace
from pathlib import Path
@ -49,7 +48,7 @@ class TestApplyLanguageConfig:
src = tmp_path / "src" / "main" / "java"
src.mkdir(parents=True)
config = {"module_root": str(src)}
lang_config = LanguageConfig(config=config, config_path=tmp_path / "codeflash.toml", language=Language.JAVA)
lang_config = LanguageConfig(config=config, config_path=tmp_path, language=Language.JAVA)
args = make_base_args()
from codeflash.cli_cmds.cli import apply_language_config
@ -63,7 +62,7 @@ class TestApplyLanguageConfig:
tests = tmp_path / "src" / "test" / "java"
tests.mkdir(parents=True)
config = {"module_root": str(src), "tests_root": str(tests)}
lang_config = LanguageConfig(config=config, config_path=tmp_path / "codeflash.toml", language=Language.JAVA)
lang_config = LanguageConfig(config=config, config_path=tmp_path, language=Language.JAVA)
args = make_base_args()
from codeflash.cli_cmds.cli import apply_language_config
@ -77,7 +76,7 @@ class TestApplyLanguageConfig:
tests = tmp_path / "src" / "test" / "java"
tests.mkdir(parents=True)
config = {"module_root": str(src), "tests_root": str(tests)}
lang_config = LanguageConfig(config=config, config_path=tmp_path / "codeflash.toml", language=Language.JAVA)
lang_config = LanguageConfig(config=config, config_path=tmp_path, language=Language.JAVA)
args = make_base_args()
from codeflash.cli_cmds.cli import apply_language_config
@ -93,7 +92,7 @@ class TestApplyLanguageConfig:
tests.mkdir(parents=True)
(tmp_path / "pom.xml").touch()
config = {"module_root": str(src), "tests_root": str(tests)}
lang_config = LanguageConfig(config=config, config_path=tmp_path / "codeflash.toml", language=Language.JAVA)
lang_config = LanguageConfig(config=config, config_path=tmp_path, language=Language.JAVA)
args = make_base_args()
from codeflash.cli_cmds.cli import apply_language_config
@ -109,7 +108,7 @@ class TestApplyLanguageConfig:
tests = tmp_path / "src" / "test" / "java"
tests.mkdir(parents=True)
config = {"module_root": str(src), "tests_root": str(tests)}
lang_config = LanguageConfig(config=config, config_path=tmp_path / "codeflash.toml", language=Language.JAVA)
lang_config = LanguageConfig(config=config, config_path=tmp_path, language=Language.JAVA)
args = make_base_args(module_root=str(override_module))
from codeflash.cli_cmds.cli import apply_language_config
@ -137,7 +136,7 @@ class TestApplyLanguageConfig:
tests = tmp_path / "src" / "test" / "java"
tests.mkdir(parents=True)
config = {"module_root": str(src), "tests_root": str(tests)}
lang_config = LanguageConfig(config=config, config_path=tmp_path / "codeflash.toml", language=Language.JAVA)
lang_config = LanguageConfig(config=config, config_path=tmp_path, language=Language.JAVA)
args = make_base_args()
with patch("codeflash.cli_cmds.cli.set_current_language") as mock_set:
@ -168,7 +167,7 @@ class TestApplyLanguageConfig:
default_tests.mkdir(parents=True)
monkeypatch.chdir(tmp_path)
config = {"module_root": str(src)}
lang_config = LanguageConfig(config=config, config_path=tmp_path / "codeflash.toml", language=Language.JAVA)
lang_config = LanguageConfig(config=config, config_path=tmp_path, language=Language.JAVA)
args = make_base_args()
from codeflash.cli_cmds.cli import apply_language_config
@ -204,7 +203,7 @@ def make_lang_config(tmp_path: Path, language: Language, subdir: str = "") -> La
tests = tmp_path / subdir / "src" / "test" / "java" if subdir else tmp_path / "src" / "test" / "java"
src.mkdir(parents=True, exist_ok=True)
tests.mkdir(parents=True, exist_ok=True)
config_path = tmp_path / subdir / "codeflash.toml" if subdir else tmp_path / "codeflash.toml"
config_path = tmp_path / subdir if subdir else tmp_path
return LanguageConfig(
config={"module_root": str(src), "tests_root": str(tests)},
config_path=config_path,
@ -741,7 +740,7 @@ class TestDirectFunctionCoverage:
def test_empty_config_no_module_root(self, tmp_path: Path) -> None:
config: dict = {}
result = normalize_toml_config(config, tmp_path / "codeflash.toml")
result = normalize_toml_config(config, tmp_path / "pyproject.toml")
assert result["formatter_cmds"] == []
assert result["disable_telemetry"] is False
assert "module_root" not in result
@ -752,7 +751,7 @@ class TestNormalizeTomlConfig:
config = {"module-root": "src", "tests-root": "tests"}
(tmp_path / "src").mkdir()
(tmp_path / "tests").mkdir()
result = normalize_toml_config(config, tmp_path / "codeflash.toml")
result = normalize_toml_config(config, tmp_path / "pyproject.toml")
assert "module_root" in result
assert "tests_root" in result
assert "module-root" not in result
@ -762,12 +761,12 @@ class TestNormalizeTomlConfig:
src = tmp_path / "src"
src.mkdir()
config = {"module-root": "src"}
result = normalize_toml_config(config, tmp_path / "codeflash.toml")
result = normalize_toml_config(config, tmp_path / "pyproject.toml")
assert result["module_root"] == str(src.resolve())
def test_applies_default_values(self, tmp_path: Path) -> None:
config: dict = {}
result = normalize_toml_config(config, tmp_path / "codeflash.toml")
result = normalize_toml_config(config, tmp_path / "pyproject.toml")
assert result["formatter_cmds"] == []
assert result["disable_telemetry"] is False
assert result["override_fixtures"] is False
@ -776,13 +775,13 @@ class TestNormalizeTomlConfig:
def test_preserves_explicit_values(self, tmp_path: Path) -> None:
config = {"disable-telemetry": True, "formatter-cmds": ["prettier $file"]}
result = normalize_toml_config(config, tmp_path / "codeflash.toml")
result = normalize_toml_config(config, tmp_path / "pyproject.toml")
assert result["disable_telemetry"] is True
assert result["formatter_cmds"] == ["prettier $file"]
def test_resolves_ignore_paths(self, tmp_path: Path) -> None:
config = {"ignore-paths": ["build", "dist"]}
result = normalize_toml_config(config, tmp_path / "codeflash.toml")
result = normalize_toml_config(config, tmp_path / "pyproject.toml")
assert result["ignore_paths"] == [
str((tmp_path / "build").resolve()),
str((tmp_path / "dist").resolve()),
@ -790,7 +789,7 @@ class TestNormalizeTomlConfig:
def test_empty_ignore_paths_default(self, tmp_path: Path) -> None:
config: dict = {}
result = normalize_toml_config(config, tmp_path / "codeflash.toml")
result = normalize_toml_config(config, tmp_path / "pyproject.toml")
assert result["ignore_paths"] == []
@ -809,7 +808,7 @@ class TestUnconfiguredLanguageDetection:
configs = [
LanguageConfig(config={}, config_path=Path("pyproject.toml"), language=Language.PYTHON),
LanguageConfig(config={}, config_path=Path("codeflash.toml"), language=Language.JAVA),
LanguageConfig(config={}, config_path=Path(), language=Language.JAVA),
]
changed = [Path("Foo.java"), Path("bar.py")]
result = detect_unconfigured_languages(configs, changed)
@ -826,12 +825,12 @@ class TestUnconfiguredLanguageDetection:
def test_auto_config_adds_language_config_on_success(self, mock_find_configs, tmp_path: Path) -> None:
from codeflash.main import auto_configure_language
new_lc = LanguageConfig(config={}, config_path=tmp_path / "codeflash.toml", language=Language.JAVA)
new_lc = LanguageConfig(config={}, config_path=tmp_path, language=Language.JAVA)
mock_find_configs.return_value = [new_lc]
logger = logging.getLogger("codeflash.test")
with (
patch("codeflash.main.write_config", return_value=(True, "Created codeflash.toml")) as mock_write,
patch("codeflash.main.write_config", return_value=(True, "Created config")) as mock_write,
patch("codeflash.main.detect_project_for_language") as mock_detect,
):
mock_detect.return_value = MagicMock()