From 5360897ce78d2676294be6069927a71396175a84 Mon Sep 17 00:00:00 2001 From: aseembits93 Date: Fri, 3 Apr 2026 11:25:31 -0700 Subject: [PATCH 1/3] Add Java language support and simplify hook to delegate setup to skills - Add Java file detection (.java) in suggest-optimize.sh with dedicated message - Remove oauth-login.sh (auth now handled by codeflash CLI directly) - Simplify hook: delegate all install/config logic to codeflash:optimize skill - Add --all, --effort, --no-pr flags to optimize skill - Add Java project config support (pom.xml, build.gradle.kts) - Make debug logging conditional on CODEFLASH_DEBUG=1 - Fix "to to" typos in hook messages - Update tests: simplify assertions to match skill delegation, add Java tests - Update README directory structure Co-Authored-By: Claude Opus 4.6 --- README.md | 10 +- scripts/oauth-login.sh | 405 ------------------------------- scripts/suggest-optimize.sh | 23 +- skills/optimize/SKILL.md | 16 +- tests/helpers/setup.bash | 11 + tests/test_suggest_optimize.bats | 141 ++++++----- 6 files changed, 130 insertions(+), 476 deletions(-) delete mode 100755 scripts/oauth-login.sh diff --git a/README.md b/README.md index 7a0f85f..2c43aac 100644 --- a/README.md +++ b/README.md @@ -81,18 +81,16 @@ codeflash-cc-plugin/ ├── .claude-plugin/ │ ├── marketplace.json # Marketplace manifest │ └── plugin.json # Plugin manifest -├── agents/ -│ └── optimizer.md # Background optimization agent -├── commands/ -│ └── setup.md # /setup command for auto-allow permissions ├── hooks/ │ └── hooks.json # Stop hook for commit detection ├── scripts/ │ ├── find-venv.sh # Shared helper: find and activate a Python venv │ └── suggest-optimize.sh # Detects Python/Java/JS/TS changes, suggests /optimize ├── skills/ -│ └── optimize/ -│ └── SKILL.md # /optimize slash command +│ ├── optimize/ +│ │ └── SKILL.md # /optimize slash command +│ └── setup/ +│ └── SKILL.md # /setup command for installation and configuration └── README.md ``` diff --git a/scripts/oauth-login.sh b/scripts/oauth-login.sh deleted file mode 100755 index 68e3aed..0000000 --- a/scripts/oauth-login.sh +++ /dev/null @@ -1,405 +0,0 @@ -#!/usr/bin/env bash -# OAuth PKCE login flow for Codeflash. -# Opens browser for authentication, exchanges code for API key, -# and saves it to the user's shell RC file. -# -# Usage: -# ./oauth-login.sh # Full browser flow -# ./oauth-login.sh --exchange-code STATE_FILE CODE # Complete headless flow -# -# Exit codes: -# 0 = success (API key saved) -# 1 = error -# 2 = headless mode (remote URL printed to stdout, state saved to temp file) - -set -euo pipefail - -CFWEBAPP_BASE_URL="https://app.codeflash.ai" -TOKEN_URL="${CFWEBAPP_BASE_URL}/codeflash/auth/oauth/token" -CLIENT_ID="cf-cli-app" -TIMEOUT=180 - -# --- Detect if a browser can be launched (matches codeflash's should_attempt_browser_launch) --- -can_open_browser() { - # CI/CD or non-interactive environments - if [ -n "${CI:-}" ] || [ "${DEBIAN_FRONTEND:-}" = "noninteractive" ]; then - return 1 - fi - - # Text-only browsers - local browser_env="${BROWSER:-}" - case "$browser_env" in - www-browser|lynx|links|w3m|elinks|links2) return 1 ;; - esac - - local is_ssh="false" - if [ -n "${SSH_CONNECTION:-}" ]; then - is_ssh="true" - fi - - # Linux: require a display server - if [ "$(uname -s)" = "Linux" ]; then - if [ -z "${DISPLAY:-}" ] && [ -z "${WAYLAND_DISPLAY:-}" ] && [ -z "${MIR_SOCKET:-}" ]; then - return 1 - fi - fi - - # SSH on non-Linux (e.g. macOS remote) — no browser - if [ "$is_ssh" = "true" ] && [ "$(uname -s)" != "Linux" ]; then - return 1 - fi - - return 0 -} - -# --- Save API key to shell RC (matches codeflash's shell_utils.py logic) --- -save_api_key() { - local api_key="$1" - - if [ "${OS:-}" = "Windows_NT" ] || [[ "$(uname -s 2>/dev/null)" == MINGW* ]]; then - # Windows: use dedicated codeflash env files (same as codeflash CLI) - if [ -n "${PSMODULEPATH:-}" ]; then - RC_FILE="$HOME/codeflash_env.ps1" - EXPORT_LINE="\$env:CODEFLASH_API_KEY = \"${api_key}\"" - REMOVE_PATTERN='^\$env:CODEFLASH_API_KEY' - else - RC_FILE="$HOME/codeflash_env.bat" - EXPORT_LINE="set CODEFLASH_API_KEY=\"${api_key}\"" - REMOVE_PATTERN='^set CODEFLASH_API_KEY=' - fi - else - # Unix: use shell RC file (same mapping as codeflash CLI) - SHELL_NAME=$(basename "${SHELL:-/bin/bash}") - case "$SHELL_NAME" in - zsh) RC_FILE="$HOME/.zshrc" ;; - ksh) RC_FILE="$HOME/.kshrc" ;; - csh|tcsh) RC_FILE="$HOME/.cshrc" ;; - dash) RC_FILE="$HOME/.profile" ;; - *) RC_FILE="$HOME/.bashrc" ;; - esac - EXPORT_LINE="export CODEFLASH_API_KEY=\"${api_key}\"" - REMOVE_PATTERN='^export CODEFLASH_API_KEY=' - fi - - # Remove any existing CODEFLASH_API_KEY lines and append the new one - if [ -f "$RC_FILE" ]; then - CLEANED=$(grep -v "$REMOVE_PATTERN" "$RC_FILE" || true) - printf '%s\n' "$CLEANED" > "$RC_FILE" - fi - printf '%s\n' "$EXPORT_LINE" >> "$RC_FILE" - - # Also export for the current session - export CODEFLASH_API_KEY="$api_key" -} - -# --- Exchange code for token and save --- -exchange_and_save() { - local auth_code="$1" - local code_verifier="$2" - local redirect_uri="$3" - - TOKEN_RESPONSE=$(curl -s -X POST "$TOKEN_URL" \ - -H "Content-Type: application/json" \ - -d "{ - \"grant_type\": \"authorization_code\", - \"code\": \"${auth_code}\", - \"code_verifier\": \"${code_verifier}\", - \"redirect_uri\": \"${redirect_uri}\", - \"client_id\": \"${CLIENT_ID}\" - }") - - API_KEY=$(printf '%s' "$TOKEN_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('access_token',''))" 2>/dev/null || true) - - if [ -z "$API_KEY" ] || [[ ! "$API_KEY" == cf-* ]]; then - exit 1 - fi - - save_api_key "$API_KEY" -} - -# =================================================================== -# Mode: --exchange-code STATE_FILE CODE -# Complete a headless flow using a previously saved PKCE state file. -# =================================================================== -if [ "${1:-}" = "--exchange-code" ]; then - STATE_FILE="${2:-}" - MANUAL_CODE="${3:-}" - - if [ -z "$STATE_FILE" ] || [ -z "$MANUAL_CODE" ] || [ ! -f "$STATE_FILE" ]; then - exit 1 - fi - - # Read saved state - CODE_VERIFIER=$(python3 -c "import json; print(json.load(open('${STATE_FILE}')).get('code_verifier',''))" 2>/dev/null || true) - REMOTE_REDIRECT=$(python3 -c "import json; print(json.load(open('${STATE_FILE}')).get('remote_redirect_uri',''))" 2>/dev/null || true) - - rm -f "$STATE_FILE" - - if [ -z "$CODE_VERIFIER" ] || [ -z "$REMOTE_REDIRECT" ]; then - exit 1 - fi - - exchange_and_save "$MANUAL_CODE" "$CODE_VERIFIER" "$REMOTE_REDIRECT" - exit 0 -fi - -# =================================================================== -# Mode: Full OAuth flow (default) -# =================================================================== - -# --- PKCE pair --- -CODE_VERIFIER=$(openssl rand -base64 48 | tr -d '=/+\n' | head -c 64) -CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=') - -# --- State --- -STATE=$(openssl rand -hex 16) - -# --- Find a free port --- -PORT=$(python3 -c "import socket; s=socket.socket(); s.bind(('',0)); print(s.getsockname()[1]); s.close()") - -LOCAL_REDIRECT_URI="http://localhost:${PORT}/callback" -REMOTE_REDIRECT_URI="${CFWEBAPP_BASE_URL}/codeflash/auth/callback" -ENCODED_LOCAL_REDIRECT=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${LOCAL_REDIRECT_URI}'))") -ENCODED_REMOTE_REDIRECT=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${REMOTE_REDIRECT_URI}'))") - -AUTH_PARAMS="response_type=code&client_id=${CLIENT_ID}&code_challenge=${CODE_CHALLENGE}&code_challenge_method=sha256&state=${STATE}" -LOCAL_AUTH_URL="${CFWEBAPP_BASE_URL}/codeflash/auth?${AUTH_PARAMS}&redirect_uri=${ENCODED_LOCAL_REDIRECT}" -REMOTE_AUTH_URL="${CFWEBAPP_BASE_URL}/codeflash/auth?${AUTH_PARAMS}&redirect_uri=${ENCODED_REMOTE_REDIRECT}" - -# --- Headless detection --- -if ! can_open_browser; then - # Save PKCE state so --exchange-code can complete the flow later - HEADLESS_STATE_FILE=$(mktemp /tmp/codeflash-oauth-state-XXXXXX.json) - python3 -c " -import json -json.dump({ - 'code_verifier': '${CODE_VERIFIER}', - 'remote_redirect_uri': '${REMOTE_REDIRECT_URI}', - 'state': '${STATE}' -}, open('${HEADLESS_STATE_FILE}', 'w')) -" - # Output JSON for Claude to parse — this is the ONLY stdout in headless mode - printf '{"headless":true,"url":"%s","state_file":"%s"}\n' "$REMOTE_AUTH_URL" "$HEADLESS_STATE_FILE" - exit 2 -fi - -# --- Temp file for callback result --- -RESULT_FILE=$(mktemp /tmp/codeflash-oauth-XXXXXX.json) -trap 'rm -f "$RESULT_FILE"' EXIT - -# --- Start local callback server with Codeflash-styled pages --- -export PORT STATE RESULT_FILE TIMEOUT -python3 - "$PORT" "$STATE" "$RESULT_FILE" << 'PYEOF' & -import http.server, urllib.parse, json, sys, threading - -port = int(sys.argv[1]) -state = sys.argv[2] -result_file = sys.argv[3] - -STYLE = ( - ":root{" - "--bg:hsl(0,0%,99%);--fg:hsl(222.2,84%,4.9%);--card:hsl(0,0%,100%);" - "--card-fg:hsl(222.2,84%,4.9%);--primary:hsl(38,100%,63%);" - "--muted-fg:hsl(41,8%,46%);--border:hsl(41,30%,90%);" - "--destructive:hsl(0,84.2%,60.2%);--destructive-fg:#fff;" - "--success:hsl(142,76%,36%)}" - "html.dark{" - "--bg:hsl(0,6%,5%);--fg:#fff;--card:hsl(0,3%,11%);" - "--card-fg:#fff;--primary:hsl(38,100%,63%);" - "--muted-fg:hsl(48,20%,65%);--border:hsl(48,20%,25%);" - "--destructive:hsl(0,62.8%,30.6%)}" - "*{margin:0;padding:0;box-sizing:border-box}" - "body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;" - "background:var(--bg);color:var(--fg);min-height:100vh;" - "display:flex;align-items:center;justify-content:center;padding:20px;position:relative}" - "body::before{content:'';position:fixed;inset:0;" - "background:linear-gradient(to bottom,hsl(38,100%,63%,.1),hsl(38,100%,63%,.05),transparent);" - "pointer-events:none;z-index:0}" - "body::after{content:'';position:fixed;inset:0;" - "background-image:linear-gradient(to right,rgba(128,128,128,.03) 1px,transparent 1px)," - "linear-gradient(to bottom,rgba(128,128,128,.03) 1px,transparent 1px);" - "background-size:24px 24px;pointer-events:none;z-index:0}" - ".ctr{max-width:420px;width:100%;position:relative;z-index:1}" - ".logo-ctr{display:flex;justify-content:center;margin-bottom:48px}" - ".logo{height:40px;width:auto}" - ".ll{display:block}.ld{display:none}" - "html.dark .ll{display:none}html.dark .ld{display:block}" - ".card{background:var(--card);color:var(--card-fg);border:1px solid var(--border);" - "border-radius:16px;box-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -2px rgba(0,0,0,.05);" - "padding:48px;animation:fadeIn .3s ease-out forwards}" - "@keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}" - ".ic{width:48px;height:48px;background:hsl(38,100%,63%,.1);border-radius:12px;" - "display:flex;align-items:center;justify-content:center;margin:0 auto 24px}" - ".spinner{width:24px;height:24px;border:2px solid var(--border);" - "border-top-color:var(--primary);border-radius:50%;animation:spin .8s linear infinite}" - "@keyframes spin{to{transform:rotate(360deg)}}" - ".si{width:64px;height:64px;background:hsl(142,76%,36%,.1);border-radius:12px;" - "display:flex;align-items:center;justify-content:center;margin:0 auto 24px}" - ".sc{width:32px;height:32px;stroke:hsl(142,76%,36%)}" - "h1{font-size:24px;font-weight:600;margin:0 0 12px;color:var(--card-fg);text-align:center}" - "p{color:var(--muted-fg);margin:0;font-size:14px;line-height:1.5;text-align:center}" - ".eb{background:var(--destructive);color:var(--destructive-fg);" - "padding:14px 18px;border-radius:8px;margin-top:24px;font-size:14px;line-height:1.5;text-align:center}" - "@media(max-width:480px){.card{padding:32px 24px}h1{font-size:20px}.logo{height:32px}}" -) - -LOGO = ( - '
' - '' - '' - '
' -) - -# Pre-built static HTML fragments (no user data) -SUCCESS_FRAG = ( - '
' - '' - '

Success!

' - '

Authentication completed. You can close this window and return to your terminal.

' -) - -ERR_ICON_FRAG = ( - '
' - '' - '' - '' - '' - '

Authentication Failed

' -) - - -def loading_page(): - # Inline the static fragments as JS string constants so the polling - # script only inserts pre-defined trusted HTML, never user data. - success_js = json.dumps(SUCCESS_FRAG) - err_icon_js = json.dumps(ERR_ICON_FRAG) - return ( - '' - '' - 'CodeFlash Authentication' - f'' - '' - '' - f'
{LOGO}' - '
' - '
' - '

Authenticating

' - '

Please wait while we verify your credentials...

' - '
' - '' - ) - - -class H(http.server.BaseHTTPRequestHandler): - server_version = "CFHTTP" - - def do_GET(self): - p = urllib.parse.urlparse(self.path) - - if p.path == "/status": - self.send_response(200) - self.send_header("Content-type", "application/json") - self.send_header("Access-Control-Allow-Origin", "*") - self.end_headers() - body = json.dumps({ - "success": self.server.token_error is None and self.server.auth_code is not None, - "error": self.server.token_error, - }) - self.wfile.write(body.encode()) - return - - if p.path != "/callback": - self.send_response(404) - self.end_headers() - return - - params = urllib.parse.parse_qs(p.query) - code = params.get("code", [None])[0] - recv_state = params.get("state", [None])[0] - error = params.get("error", [None])[0] - - if error or not code or recv_state != state: - self.server.token_error = error or "state_mismatch" - else: - self.server.auth_code = code - with open(result_file, "w") as f: - json.dump({"code": code}, f) - - self.send_response(200) - self.send_header("Content-type", "text/html") - self.end_headers() - self.wfile.write(loading_page().encode()) - - def log_message(self, *a): - pass - - -httpd = http.server.HTTPServer(("localhost", port), H) -httpd.auth_code = None -httpd.token_error = None -httpd.serve_forever() -PYEOF -SERVER_PID=$! - -# --- Open browser (macOS, Linux, WSL) --- -if [[ "$(uname)" == "Darwin" ]]; then - open "$LOCAL_AUTH_URL" 2>/dev/null || true -elif command -v wslview >/dev/null 2>&1; then - wslview "$LOCAL_AUTH_URL" 2>/dev/null || true -elif command -v xdg-open >/dev/null 2>&1; then - xdg-open "$LOCAL_AUTH_URL" 2>/dev/null || true -elif command -v cmd.exe >/dev/null 2>&1; then - cmd.exe /c start "" "$LOCAL_AUTH_URL" 2>/dev/null || true -fi - -# --- Wait for callback --- -WAITED=0 -while [ ! -s "$RESULT_FILE" ] && [ "$WAITED" -lt "$TIMEOUT" ]; do - sleep 1 - WAITED=$((WAITED + 1)) - if ! kill -0 "$SERVER_PID" 2>/dev/null; then - break - fi -done - -if [ ! -s "$RESULT_FILE" ]; then - kill "$SERVER_PID" 2>/dev/null || true - wait "$SERVER_PID" 2>/dev/null || true - exit 1 -fi - -# --- Parse callback result --- -AUTH_CODE=$(python3 -c "import json; print(json.load(open('${RESULT_FILE}')).get('code',''))" 2>/dev/null || true) - -if [ -z "$AUTH_CODE" ]; then - kill "$SERVER_PID" 2>/dev/null || true - wait "$SERVER_PID" 2>/dev/null || true - exit 1 -fi - -# --- Exchange code for token --- -exchange_and_save "$AUTH_CODE" "$CODE_VERIFIER" "$LOCAL_REDIRECT_URI" - -# Give the browser a moment to poll /status and see success, then shut down -sleep 2 -kill "$SERVER_PID" 2>/dev/null || true -wait "$SERVER_PID" 2>/dev/null || true \ No newline at end of file diff --git a/scripts/suggest-optimize.sh b/scripts/suggest-optimize.sh index ec7f8b0..331bbdd 100755 --- a/scripts/suggest-optimize.sh +++ b/scripts/suggest-optimize.sh @@ -5,9 +5,11 @@ set -euo pipefail -LOGFILE="/tmp/codeflash-hook-debug.log" -exec 2>>"$LOGFILE" -set -x +if [ "${CODEFLASH_DEBUG:-}" = "1" ]; then + LOGFILE="/tmp/codeflash-hook-debug.log" + exec 2>>"$LOGFILE" + set -x +fi # Read stdin (Stop hook pipes context as JSON via stdin) INPUT=$(cat) @@ -94,10 +96,14 @@ fi # Determine which language families actually had changes HAS_PYTHON_CHANGES="false" +HAS_JAVA_CHANGES="false" HAS_JS_CHANGES="false" if echo "$CHANGED_FILES" | grep -qE '\.py$'; then HAS_PYTHON_CHANGES="true" fi +if echo "$CHANGED_FILES" | grep -qE '\.java$'; then + HAS_JAVA_CHANGES="true" +fi if echo "$CHANGED_FILES" | grep -qE '\.(js|ts|jsx|tsx)$'; then HAS_JS_CHANGES="true" fi @@ -125,7 +131,14 @@ echo "$CHANGED_FILES" >> "$OPTIMIZED_FILES_MARKER" # --- JS/TS project path --------------------------------------------------- if [ "$HAS_JS_CHANGES" = "true" ]; then - MESSAGE="JS/TS files were changed in a recent commit. Use the codeflash:optimize skill WITHOUT ANY ARGUMENTS to to optimize the JavaScript/TypeScript code for performance. Use npx to execute codeflash" + MESSAGE="JS/TS files were changed in a recent commit. Use the codeflash:optimize skill WITHOUT ANY ARGUMENTS to optimize the JavaScript/TypeScript code for performance. Use npx to execute codeflash" + jq -nc --arg reason "$MESSAGE" '{"decision": "block", "reason": $reason, "systemMessage": $reason}' + exit 0 +fi + +# --- Java project path ----------------------------------------------------- +if [ "$HAS_JAVA_CHANGES" = "true" ]; then + MESSAGE="Java files were changed in a recent commit. Use the codeflash:optimize skill WITHOUT ANY ARGUMENTS to optimize the Java code for performance." jq -nc --arg reason "$MESSAGE" '{"decision": "block", "reason": $reason, "systemMessage": $reason}' exit 0 fi @@ -135,6 +148,6 @@ if [ "$HAS_PYTHON_CHANGES" != "true" ]; then exit 0 fi -MESSAGE="Python files were changed in a recent commit. Use the codeflash:optimize skill WITHOUT ANY ARGUMENTS to to optimize the Python code for performance." +MESSAGE="Python files were changed in a recent commit. Use the codeflash:optimize skill WITHOUT ANY ARGUMENTS to optimize the Python code for performance." jq -nc --arg reason "$MESSAGE" '{"decision": "block", "reason": $reason, "systemMessage": $reason}' diff --git a/skills/optimize/SKILL.md b/skills/optimize/SKILL.md index 3b2a184..9ac1f78 100644 --- a/skills/optimize/SKILL.md +++ b/skills/optimize/SKILL.md @@ -2,8 +2,8 @@ name: optimize description: Optimize Python, Java, JavaScript, or TypeScript code for performance using Codeflash user-invocable: true -argument-hint: "[--file path] [--function name]" -allowed-tools: ["Bash"] +argument-hint: "[--file path] [--function name] [--all] [--effort low|medium|high] [--no-pr]" +tools: ["Bash"] --- Run the `codeflash` CLI to optimize code for performance. @@ -14,18 +14,24 @@ Disambiguate the file and function from `$ARGUMENTS` if --file and/or --function ## Correct cwd -Based on the language of the file/s of concern, Find the `pyproject.toml` (Python) /`package.json` (JS/TS) file closest to the file/files of concern (the file passed to codeflash --file or the files which changed in the diff). +Based on the language of the file/s of concern, find the closest project config file: +- **Python**: `pyproject.toml` +- **Java**: `pom.xml` or `build.gradle.kts` +- **JS/TS**: `package.json` -`cd` into the directory where you found the `pyproject.toml`/`package.json`. +`cd` into the directory where you found that config file. ## Build the command Start with: `codeflash --subagent` for Python and Java Code or `uv run codeflash --subagent` if a `uv.lock` file is present. -Start with: `npx codeflash --subagent`for JS/TS Code +Start with: `npx codeflash --subagent` for JS/TS Code Then add flags based on `$ARGUMENTS`: - If a `--file` path was provided: add `--file ` - If a `--function` name was also provided: add `--function ` +- If `--all` was provided: add `--all` (optimizes the entire project) +- If `--effort ` was provided: add `--effort ` +- If `--no-pr` was provided: add `--no-pr` (skip PR creation) - If no arguments were provided, run `codeflash --subagent` as-is (it detects changed files automatically) ## Execute diff --git a/tests/helpers/setup.bash b/tests/helpers/setup.bash index bc007b1..45f7a94 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:-Main.java}" + mkdir -p "$REPO/$(dirname "$file")" + echo "public class Main {}" > "$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" diff --git a/tests/test_suggest_optimize.bats b/tests/test_suggest_optimize.bats index ccde695..3e9b2b7 100755 --- a/tests/test_suggest_optimize.bats +++ b/tests/test_suggest_optimize.bats @@ -192,81 +192,76 @@ setup() { # Setup: pyproject.toml with [tool.codeflash]. Fake venv exists but does NOT # contain a codeflash binary. VIRTUAL_ENV set. -# Validates: When codeflash is configured but not installed in the venv, the -# hook should prompt the user to install it before optimization can run. -# Expected: Block with reason containing "pip install codeflash". -@test "python: configured + codeflash NOT installed → install prompt" { +# Validates: The simplified hook delegates all setup/install logic to the +# codeflash:optimize skill. Regardless of installation state, the +# hook should block and point to the skill. +# Expected: Block with reason containing "codeflash:optimize". +@test "python: configured + codeflash NOT installed → delegates to skill" { add_python_commit create_pyproject true create_fake_venv "$REPO/.venv" false run run_hook false "VIRTUAL_ENV=$REPO/.venv" assert_block - assert_reason_contains "pip install codeflash" + assert_reason_contains "codeflash:optimize" } # Setup: pyproject.toml exists but has NO [tool.codeflash] section. Fake venv # with codeflash binary installed. VIRTUAL_ENV set. -# Validates: When codeflash is installed but not configured, the hook should -# instruct Claude to discover the project structure (module root, -# tests folder) and write the [tool.codeflash] config section. -# Expected: Block with reason containing "[tool.codeflash]" and "module-root" -# (the config fields to be written). -@test "python: NOT configured + codeflash installed → setup prompt" { +# Validates: The hook no longer checks configuration state — it delegates to +# the skill which handles setup fallback internally. +# Expected: Block with reason containing "codeflash:optimize". +@test "python: NOT configured + codeflash installed → delegates to skill" { add_python_commit create_pyproject false create_fake_venv "$REPO/.venv" run run_hook false "VIRTUAL_ENV=$REPO/.venv" assert_block - assert_reason_contains "[tool.codeflash]" - assert_reason_contains "module-root" + assert_reason_contains "codeflash:optimize" } # Setup: pyproject.toml without [tool.codeflash]. Fake venv WITHOUT codeflash # binary. VIRTUAL_ENV set. -# Validates: When both installation and configuration are missing, the hook -# should instruct Claude to both install codeflash and set up the -# config. The install step is embedded within the setup instructions. -# Expected: Block with reason containing both "[tool.codeflash]" (setup) and -# "install codeflash" (installation). -@test "python: NOT configured + NOT installed → setup + install prompt" { +# Validates: Even when both installation and configuration are missing, the hook +# delegates to the skill (which handles setup fallback). +# Expected: Block with reason containing "codeflash:optimize". +@test "python: NOT configured + NOT installed → delegates to skill" { add_python_commit create_pyproject false create_fake_venv "$REPO/.venv" false run run_hook false "VIRTUAL_ENV=$REPO/.venv" assert_block - assert_reason_contains "[tool.codeflash]" - assert_reason_contains "install codeflash" + assert_reason_contains "codeflash:optimize" } # Setup: pyproject.toml with [tool.codeflash]. No .venv or venv directory # anywhere. VIRTUAL_ENV not set. -# Validates: Without a virtual environment, codeflash cannot run. The hook -# reaches the no-venv code path. Currently the script exits non-zero -# due to an unset SETUP_PERMISSIONS_STEP variable (known issue). -# Expected: Exit non-zero (script bug: unset variable with set -u). -@test "python: no venv + configured → exits non-zero (known script bug)" { +# Validates: The simplified hook no longer inspects venv state — it just detects +# Python file changes and delegates to the skill. +# Expected: Block with reason containing "codeflash:optimize". +@test "python: no venv + configured → delegates to skill" { add_python_commit create_pyproject true # No venv created, no VIRTUAL_ENV set run run_hook false - [ "$status" -ne 0 ] + assert_block + assert_reason_contains "codeflash:optimize" } # Setup: pyproject.toml WITHOUT [tool.codeflash]. No venv anywhere. # VIRTUAL_ENV not set. -# Validates: Same no-venv code path. Currently the script exits non-zero -# due to an unset SETUP_PERMISSIONS_STEP variable (known issue). -# Expected: Exit non-zero (script bug: unset variable with set -u). -@test "python: no venv + NOT configured → exits non-zero (known script bug)" { +# Validates: Same as above — hook delegates regardless of project state. +# Expected: Block with reason containing "codeflash:optimize". +@test "python: no venv + NOT configured → delegates to skill" { add_python_commit create_pyproject false run run_hook false - [ "$status" -ne 0 ] + assert_block + assert_reason_contains "codeflash:optimize" } # Setup: pyproject.toml with [tool.codeflash]. Fake venv at $REPO/.venv with @@ -311,55 +306,47 @@ setup() { # Setup: package.json with "codeflash" key. Mock npx returns failure for # `codeflash --version` (package not installed). One .js commit. -# Validates: When codeflash is configured in package.json but the npm package -# is not installed, the hook should prompt to install it as a dev -# dependency before running. -# Expected: Block with reason containing "npm install --save-dev codeflash". -@test "js: configured + NOT installed → install prompt" { +# Validates: The simplified hook delegates all setup/install logic to the +# codeflash:optimize skill regardless of installation state. +# Expected: Block with reason containing "codeflash:optimize". +@test "js: configured + NOT installed → delegates to skill" { add_js_commit create_package_json true setup_mock_npx false run run_hook false "PATH=$MOCK_BIN:$PATH" assert_block - assert_reason_contains "npm install --save-dev codeflash" + assert_reason_contains "codeflash:optimize" } # Setup: package.json exists but has NO "codeflash" key. Mock npx returns # success (codeflash is installed). One .js commit. -# Validates: When codeflash is installed but not configured, the hook should -# instruct Claude to discover project structure and add the "codeflash" -# config key to package.json with moduleRoot, testsRoot, etc. -# Expected: Block with reason containing "moduleRoot" and "testsRoot" -# (the config fields to be added to package.json). -@test "js: NOT configured + installed → setup prompt" { +# Validates: The hook no longer checks configuration state — it delegates to +# the skill which handles setup fallback internally. +# Expected: Block with reason containing "codeflash:optimize". +@test "js: NOT configured + installed → delegates to skill" { add_js_commit create_package_json false setup_mock_npx true run run_hook false "PATH=$MOCK_BIN:$PATH" assert_block - assert_reason_contains "moduleRoot" - assert_reason_contains "testsRoot" + assert_reason_contains "codeflash:optimize" } # Setup: package.json without "codeflash" key. Mock npx fails (not installed). # One .js commit. -# Validates: When both installation and configuration are missing for a JS -# project. The setup message should include an install step -# ("npm install --save-dev codeflash") embedded within the broader -# config setup instructions. -# Expected: Block with reason containing both "moduleRoot" (setup) and -# "npm install --save-dev codeflash" (installation). -@test "js: NOT configured + NOT installed → setup + install prompt" { +# Validates: Even when both installation and configuration are missing, the hook +# delegates to the skill (which handles setup fallback). +# Expected: Block with reason containing "codeflash:optimize". +@test "js: NOT configured + NOT installed → delegates to skill" { add_js_commit create_package_json false setup_mock_npx false run run_hook false "PATH=$MOCK_BIN:$PATH" assert_block - assert_reason_contains "moduleRoot" - assert_reason_contains "npm install --save-dev codeflash" + assert_reason_contains "codeflash:optimize" } # Setup: Configured package.json + mock npx. Commit touches a .ts file @@ -393,6 +380,50 @@ setup() { assert_reason_contains "codeflash:optimize" } +# ═══════════════════════════════════════════════════════════════════════════════ +# Java projects +# ═══════════════════════════════════════════════════════════════════════════════ + +# Setup: One .java file committed after session start. +# Validates: The hook detects Java file changes and produces a block decision +# pointing to the codeflash:optimize skill. +# Expected: Block with reason containing "codeflash:optimize" and "Java". +@test "java: .java commit triggers Java path" { + add_java_commit + + run run_hook false + assert_block + assert_reason_contains "codeflash:optimize" + assert_reason_contains "Java" +} + +# Setup: One .java file committed. Run the hook twice. +# Validates: Dedup logic works for Java commits (same as Python/JS). +# Expected: First run blocks; second run exits silently. +@test "java: dedup prevents double trigger" { + add_java_commit + + run run_hook false + assert_block + + run run_hook false + assert_no_block +} + +# Setup: One .java file committed. Verify the message does NOT mention JS/TS +# or Python — it should be Java-specific. +# Validates: Java commits route through the Java branch, not JS or Python. +# Expected: Block reason contains "Java", not "JS/TS" or "Python". +@test "java: message is Java-specific, not JS or Python" { + add_java_commit + + run run_hook false + assert_block + assert_reason_contains "Java" + assert_reason_not_contains "JS/TS" + assert_reason_not_contains "Python" +} + # ═══════════════════════════════════════════════════════════════════════════════ # Permissions — auto-allow instructions # ═══════════════════════════════════════════════════════════════════════════════ From 00d15e65a396c1a0ea310d3faf49e1a0e93d66d4 Mon Sep 17 00:00:00 2001 From: aseembits93 Date: Fri, 3 Apr 2026 11:33:38 -0700 Subject: [PATCH 2/3] cleaning up --- scripts/suggest-optimize.sh | 23 +++++------------------ skills/optimize/SKILL.md | 9 +++------ 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/scripts/suggest-optimize.sh b/scripts/suggest-optimize.sh index f703fb7..44e2022 100755 --- a/scripts/suggest-optimize.sh +++ b/scripts/suggest-optimize.sh @@ -5,11 +5,9 @@ set -euo pipefail -if [ "${CODEFLASH_DEBUG:-}" = "1" ]; then - LOGFILE="/tmp/codeflash-hook-debug.log" - exec 2>>"$LOGFILE" - set -x -fi +LOGFILE="/tmp/codeflash-hook-debug.log" +exec 2>>"$LOGFILE" +set -x # Read stdin (Stop hook pipes context as JSON via stdin) INPUT=$(cat) @@ -96,15 +94,11 @@ fi # Determine which language families actually had changes HAS_PYTHON_CHANGES="false" -HAS_JAVA_CHANGES="false" HAS_JS_CHANGES="false" HAS_JAVA_CHANGES="false" if echo "$CHANGED_FILES" | grep -qE '\.py$'; then HAS_PYTHON_CHANGES="true" fi -if echo "$CHANGED_FILES" | grep -qE '\.java$'; then - HAS_JAVA_CHANGES="true" -fi if echo "$CHANGED_FILES" | grep -qE '\.(js|ts|jsx|tsx)$'; then HAS_JS_CHANGES="true" fi @@ -135,14 +129,7 @@ echo "$CHANGED_FILES" >> "$OPTIMIZED_FILES_MARKER" # --- JS/TS project path --------------------------------------------------- if [ "$HAS_JS_CHANGES" = "true" ]; then - MESSAGE="JS/TS files were changed in a recent commit. Use the codeflash:optimize skill WITHOUT ANY ARGUMENTS to optimize the JavaScript/TypeScript code for performance. Use npx to execute codeflash" - jq -nc --arg reason "$MESSAGE" '{"decision": "block", "reason": $reason, "systemMessage": $reason}' - exit 0 -fi - -# --- Java project path ----------------------------------------------------- -if [ "$HAS_JAVA_CHANGES" = "true" ]; then - MESSAGE="Java files were changed in a recent commit. Use the codeflash:optimize skill WITHOUT ANY ARGUMENTS to optimize the Java code for performance." + MESSAGE="JS/TS files were changed in a recent commit. Use the codeflash:optimize skill WITHOUT ANY ARGUMENTS to to optimize the JavaScript/TypeScript code for performance. Use npx to execute codeflash" jq -nc --arg reason "$MESSAGE" '{"decision": "block", "reason": $reason, "systemMessage": $reason}' exit 0 fi @@ -159,6 +146,6 @@ if [ "$HAS_PYTHON_CHANGES" != "true" ]; then exit 0 fi -MESSAGE="Python files were changed in a recent commit. Use the codeflash:optimize skill WITHOUT ANY ARGUMENTS to optimize the Python code for performance." +MESSAGE="Python files were changed in a recent commit. Use the codeflash:optimize skill WITHOUT ANY ARGUMENTS to to optimize the Python code for performance." jq -nc --arg reason "$MESSAGE" '{"decision": "block", "reason": $reason, "systemMessage": $reason}' diff --git a/skills/optimize/SKILL.md b/skills/optimize/SKILL.md index 879b912..10b808b 100644 --- a/skills/optimize/SKILL.md +++ b/skills/optimize/SKILL.md @@ -2,8 +2,8 @@ name: optimize description: Optimize Python, Java, JavaScript, or TypeScript code for performance using Codeflash user-invocable: true -argument-hint: "[--file path] [--function name] [--all] [--effort low|medium|high] [--no-pr]" -tools: ["Bash"] +argument-hint: "[--file path] [--function name]" +allowed-tools: ["Bash"] --- Run the `codeflash` CLI to optimize code for performance. @@ -24,14 +24,11 @@ Based on the language of the file/s of concern, find the config file closest to ## Build the command Start with: `codeflash --subagent` for Python and Java Code or `uv run codeflash --subagent` if a `uv.lock` file is present. -Start with: `npx codeflash --subagent` for JS/TS Code +Start with: `npx codeflash --subagent`for JS/TS Code Then add flags based on `$ARGUMENTS`: - If a `--file` path was provided: add `--file ` - If a `--function` name was also provided: add `--function ` -- If `--all` was provided: add `--all` (optimizes the entire project) -- If `--effort ` was provided: add `--effort ` -- If `--no-pr` was provided: add `--no-pr` (skip PR creation) - If no arguments were provided, run `codeflash --subagent` as-is (it detects changed files automatically) ## Execute From 7dfdd19497137f43db7a2a481dec91a4573e1cf3 Mon Sep 17 00:00:00 2001 From: aseembits93 Date: Fri, 3 Apr 2026 11:34:59 -0700 Subject: [PATCH 3/3] cleaning up --- skills/optimize/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/optimize/SKILL.md b/skills/optimize/SKILL.md index 10b808b..8b4c646 100644 --- a/skills/optimize/SKILL.md +++ b/skills/optimize/SKILL.md @@ -1,6 +1,6 @@ --- name: optimize -description: Optimize Python, Java, JavaScript, or TypeScript code for performance using Codeflash +description: Optimize code for performance using Codeflash user-invocable: true argument-hint: "[--file path] [--function name]" allowed-tools: ["Bash"]