From 9af01833967fca842070d44a767d2599ccff9105 Mon Sep 17 00:00:00 2001 From: Mohamed Ashraf Date: Thu, 26 Mar 2026 01:26:10 +0000 Subject: [PATCH] fix: detect fresh Java projects via pom.xml/build.gradle in stop hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hook's detect_project() only checked for codeflash.toml to identify Java projects. Fresh projects with only pom.xml or build.gradle were invisible — the hook silently did nothing when .java files were committed. On the current codeflash main branch, the CLI auto-detects and auto-configures Java from build files via handle_first_run(), so no pre-configuration step is needed in the hook. Changes: - Add pom.xml/build.gradle/build.gradle.kts detection in detect_project() - Add HAS_JAVA_CHANGES guard (matching existing Python/JS guards) - Remove broken "not configured" block that called interactive codeflash init - Add 6 Java bats tests + test helpers (add_java_commit, create_pom_xml, etc.) Co-Authored-By: Claude Opus 4.6 --- scripts/suggest-optimize.sh | 35 +++++----- tests/helpers/setup.bash | 74 +++++++++++++++++++++ tests/test_suggest_optimize.bats | 109 +++++++++++++++++++++++++++++++ 3 files changed, 199 insertions(+), 19 deletions(-) diff --git a/scripts/suggest-optimize.sh b/scripts/suggest-optimize.sh index fa12c15..2a07f8c 100755 --- a/scripts/suggest-optimize.sh +++ b/scripts/suggest-optimize.sh @@ -79,10 +79,14 @@ fi # Determine which language families actually had changes HAS_PYTHON_CHANGES="false" +HAS_JAVA_CHANGES="false" HAS_JS_CHANGES="false" if echo "$CHANGED_COMMITS" | grep -qE '\.py$'; then HAS_PYTHON_CHANGES="true" fi +if echo "$CHANGED_COMMITS" | grep -qE '\.java$'; then + HAS_JAVA_CHANGES="true" +fi if echo "$CHANGED_COMMITS" | grep -qE '\.(js|ts|jsx|tsx)$'; then HAS_JS_CHANGES="true" fi @@ -136,7 +140,7 @@ detect_project() { PROJECT_CONFIGURED="false" local search_dir="$PWD" while true; do - # Check codeflash.toml first (Java projects) + # Check codeflash.toml first (configured Java projects) if [ -f "$search_dir/codeflash.toml" ]; then PROJECT_TYPE="java" PROJECT_DIR="$search_dir" @@ -146,6 +150,14 @@ detect_project() { fi break fi + # Detect unconfigured Java projects by build file (pom.xml / build.gradle) + if [ -f "$search_dir/pom.xml" ] || [ -f "$search_dir/build.gradle" ] || [ -f "$search_dir/build.gradle.kts" ]; then + PROJECT_TYPE="java" + PROJECT_DIR="$search_dir" + PROJECT_CONFIG_PATH="" + # No codeflash.toml yet — PROJECT_CONFIGURED stays false + break + fi if [ -f "$search_dir/pyproject.toml" ]; then PROJECT_TYPE="python" PROJECT_DIR="$search_dir" @@ -282,7 +294,7 @@ Then, add \`Bash(*codeflash*)\` to the \`permissions.allow\` array in \`$SETTING fi # --- Java project path ----------------------------------------------------- -if [ "$PROJECT_TYPE" = "java" ]; then +if [ "$PROJECT_TYPE" = "java" ] && [ "$HAS_JAVA_CHANGES" = "true" ]; then INSTALL_CMD="pip install codeflash" # Find codeflash binary: check PATH first, then uv run (Java projects don't use venvs) @@ -316,23 +328,8 @@ Then, add \`Bash(*codeflash*)\` to the \`permissions.allow\` array in \`$SETTING exit 0 fi - # Not configured - if [ "$PROJECT_CONFIGURED" != "true" ]; then - SETUP_MSG="Java files were changed in a recent commit, but codeflash is not configured yet. - -Run \`${CODEFLASH_BIN} init --yes\` in \`${CHECK_DIR}\` to automatically detect and configure the Java project, then run \`${CODEFLASH_BIN} --subagent\` as a background Bash task (run_in_background: true) to optimize the code for performance." - - if [ "$CODEFLASH_AUTO_ALLOWED" != "true" ]; then - SETUP_MSG="${SETUP_MSG} - -Then, add \`Bash(*codeflash*)\` to the \`permissions.allow\` array in \`$SETTINGS_JSON\` (create the file and \`.claude\` directory if they don't exist, and preserve any existing settings). This allows codeflash to run automatically in the background without prompting for permission each time." - fi - - jq -nc --arg reason "$SETUP_MSG" '{"decision": "block", "reason": $reason, "systemMessage": $reason}' - exit 0 - fi - - # Everything ready — build codeflash command for Java + # Java CLI auto-detects config from pom.xml/build.gradle — no pre-configuration needed. + # Just run codeflash --subagent and it handles everything. if [ -n "$PROJECT_DIR" ] && [ "$PROJECT_DIR" != "$PWD" ]; then RUN_CMD="cd $PROJECT_DIR && $CODEFLASH_BIN --subagent" else diff --git a/tests/helpers/setup.bash b/tests/helpers/setup.bash index 4302c5c..c5f6dca 100644 --- a/tests/helpers/setup.bash +++ b/tests/helpers/setup.bash @@ -83,6 +83,17 @@ add_ts_commit() { git -C "$REPO" commit -m "add $file" >/dev/null 2>&1 } +add_java_commit() { + local file="${1:-App.java}" + mkdir -p "$REPO/$(dirname "$file")" + echo "public class App { public static void main(String[] args) {} }" > "$REPO/$file" + git -C "$REPO" add -A >/dev/null 2>&1 + local ts + ts=$(future_timestamp) + GIT_COMMITTER_DATE="@$ts" GIT_AUTHOR_DATE="@$ts" \ + git -C "$REPO" commit -m "add $file" >/dev/null 2>&1 +} + add_irrelevant_commit() { local file="${1:-data.txt}" echo "some data" > "$REPO/$file" @@ -173,6 +184,69 @@ EOF git -C "$REPO" commit -m "add package.json" --allow-empty >/dev/null 2>&1 } +# Create pom.xml (Maven project marker). +create_pom_xml() { + cat > "$REPO/pom.xml" << 'EOF' + + + 4.0.0 + com.example + test-project + 1.0 + +EOF + git -C "$REPO" add -A >/dev/null 2>&1 + git -C "$REPO" commit -m "add pom.xml" --allow-empty >/dev/null 2>&1 +} + +# Create build.gradle (Gradle project marker). +create_build_gradle() { + cat > "$REPO/build.gradle" << 'EOF' +plugins { id 'java' } +EOF + git -C "$REPO" add -A >/dev/null 2>&1 + git -C "$REPO" commit -m "add build.gradle" --allow-empty >/dev/null 2>&1 +} + +# Create codeflash.toml. configured=true adds [tool.codeflash]. +create_codeflash_toml() { + local configured="${1:-true}" + if [ "$configured" = "true" ]; then + cat > "$REPO/codeflash.toml" << 'EOF' +[tool.codeflash] +module-root = "src/main/java" +tests-root = "src/test/java" +ignore-paths = [] +formatter-cmds = ["disabled"] +EOF + else + cat > "$REPO/codeflash.toml" << 'EOF' +# empty codeflash config +EOF + fi + git -C "$REPO" add -A >/dev/null 2>&1 + git -C "$REPO" commit -m "add codeflash.toml" --allow-empty >/dev/null 2>&1 +} + +# Create a mock codeflash binary on PATH (for Java projects that don't use venvs). +# Usage: setup_mock_codeflash [installed=true] +setup_mock_codeflash() { + local installed="${1:-true}" + mkdir -p "$MOCK_BIN" + + if [ "$installed" = "true" ]; then + cat > "$MOCK_BIN/codeflash" << 'MOCK' +#!/bin/bash +echo "codeflash 0.1.0" +exit 0 +MOCK + else + rm -f "$MOCK_BIN/codeflash" + return + fi + chmod +x "$MOCK_BIN/codeflash" +} + # Create .claude/settings.json with Bash(*codeflash*) auto-allowed create_auto_allow() { mkdir -p "$REPO/.claude" diff --git a/tests/test_suggest_optimize.bats b/tests/test_suggest_optimize.bats index 2e74e80..86198f6 100755 --- a/tests/test_suggest_optimize.bats +++ b/tests/test_suggest_optimize.bats @@ -352,6 +352,115 @@ setup() { assert_reason_contains "npx codeflash --subagent" } +# ═══════════════════════════════════════════════════════════════════════════════ +# Java projects +# ═══════════════════════════════════════════════════════════════════════════════ + +# Setup: pom.xml exists (Maven project). Mock codeflash binary on PATH. +# One .java file committed after session start. No codeflash.toml. +# Validates: Fresh Java project detected via pom.xml (not codeflash.toml). +# The CLI auto-configures on first run, so the hook should just +# tell Claude to run codeflash --subagent directly. +# Expected: Block with reason containing "codeflash --subagent" and +# "run_in_background". +@test "java: pom.xml + codeflash installed → run codeflash" { + add_java_commit + create_pom_xml + setup_mock_codeflash true + + run run_hook false "PATH=$MOCK_BIN:$PATH" + assert_block + assert_reason_contains "codeflash --subagent" + assert_reason_contains "run_in_background" +} + +# Setup: pom.xml exists. No codeflash binary on PATH, uv mocked to fail. +# One .java commit. +# Validates: When codeflash is not installed, the hook should prompt +# the user to install it before optimization can run. +# Expected: Block with reason containing "pip install codeflash". +@test "java: pom.xml + NOT installed → install prompt" { + add_java_commit + create_pom_xml + # Use a minimal PATH that excludes codeflash and real uv. + # Mock uv to fail so it doesn't find a global codeflash. + cat > "$MOCK_BIN/uv" << 'MOCK' +#!/bin/bash +exit 1 +MOCK + chmod +x "$MOCK_BIN/uv" + + run run_hook false "PATH=$MOCK_BIN:/usr/bin:/bin" + assert_block + assert_reason_contains "pip install codeflash" +} + +# Setup: codeflash.toml with [tool.codeflash] section (already configured). +# Mock codeflash binary on PATH. One .java commit. +# Validates: Projects that already have codeflash.toml (from a previous +# codeflash run or manual init) are detected and handled correctly. +# Expected: Block with reason containing "codeflash --subagent". +@test "java: codeflash.toml configured + installed → run codeflash" { + add_java_commit + create_codeflash_toml true + setup_mock_codeflash true + + run run_hook false "PATH=$MOCK_BIN:$PATH" + assert_block + assert_reason_contains "codeflash --subagent" +} + +# Setup: build.gradle exists (Gradle project). Mock codeflash on PATH. +# One .java commit. +# Validates: Gradle projects are detected via build.gradle, same as Maven +# projects are detected via pom.xml. +# Expected: Block with reason containing "codeflash --subagent". +@test "java: build.gradle + installed → run codeflash" { + add_java_commit + create_build_gradle + setup_mock_codeflash true + + run run_hook false "PATH=$MOCK_BIN:$PATH" + assert_block + assert_reason_contains "codeflash --subagent" +} + +# Setup: pom.xml exists. Mock codeflash on PATH. Commit only touches .py +# file (no .java files). +# Validates: The HAS_JAVA_CHANGES guard prevents the Java path from firing +# when no .java files were actually committed. Even though +# detect_project() finds pom.xml and sets PROJECT_TYPE=java, +# the guard skips the Java path. The Python fallback path may still +# fire (it checks HAS_PYTHON_CHANGES, not PROJECT_TYPE). +# Expected: Block does NOT contain "Java" — the Java path was correctly skipped. +@test "java: pom.xml but only .py committed → no Java trigger" { + add_python_commit + create_pom_xml + + run run_hook false "PATH=$MOCK_BIN:$PATH" + assert_block + assert_reason_not_contains "Java" +} + +# Setup: Both pom.xml and pyproject.toml exist. Mock codeflash on PATH. +# Commit touches .java file only. +# Validates: When both Java and Python markers exist, pom.xml is checked +# before pyproject.toml in detect_project(). With only .java +# changes, the Java path fires (not Python). +# Expected: Block with "codeflash --subagent" and NOT "npx" or "venv". +@test "java: pom.xml takes precedence over pyproject.toml" { + add_java_commit + create_pom_xml + create_pyproject true + setup_mock_codeflash true + + run run_hook false "PATH=$MOCK_BIN:$PATH" + assert_block + assert_reason_contains "codeflash --subagent" + assert_reason_not_contains "npx" + assert_reason_not_contains "virtual environment" +} + # ═══════════════════════════════════════════════════════════════════════════════ # Permissions — auto-allow instructions # ═══════════════════════════════════════════════════════════════════════════════