seno 1 bulan lalu
induk
melakukan
63e93ff799
3 mengubah file dengan 1161 tambahan dan 29 penghapusan
  1. 2 5
      Dockerfile
  2. 2 2
      Makefile
  3. 1157 22
      git_rebase_ai.py

+ 2 - 5
Dockerfile

@@ -17,13 +17,10 @@ RUN groupadd -r appgroup && useradd --no-log-init -r -g appgroup -d /home/appuse
 RUN pip install --no-cache-dir google-generativeai
 
 # Copy the Python script into the container
-COPY git_commit_ai.py /app/
 COPY git_rebase_ai.py /app/
-COPY git_reword_ai.py /app/
 
 # Make the script executable
-RUN chmod +x /app/git_commit_ai.py ; \
-  chmod +x /app/git_rebase_ai.py
+RUN chmod +x /app/git_rebase_ai.py
 
 WORKDIR /repo
 
@@ -34,5 +31,5 @@ RUN chown -R appuser:appgroup /app && chown -R appuser:appgroup /repo
 USER appuser
 
 # Set the entrypoint to run the script when the container starts
-ENTRYPOINT ["python"]
+ENTRYPOINT ["python", "/app/git_rebase_ai.py"]
 

+ 2 - 2
Makefile

@@ -2,8 +2,8 @@
 
 # === Variables ===
 # Override these on the command line like: make build IMAGE_TAG=2.0
-IMAGE_NAME ?= docker.senomas.com/commit
-IMAGE_TAG  ?= 1.1
+IMAGE_NAME ?= docker.senomas.com/git-rebase
+IMAGE_TAG  ?= 1.0
 # Full image reference used in commands
 FULL_IMAGE_NAME = $(IMAGE_NAME):$(IMAGE_TAG)
 

+ 1157 - 22
git_rebase_ai.py

@@ -7,6 +7,7 @@ import datetime
 import re
 import logging
 import tempfile
+import json # Used for reword editor script
 
 # --- Configuration ---
 
@@ -334,6 +335,108 @@ def parse_fixup_suggestions(ai_response_text, commits_in_range):
     return fixup_pairs
 
 
+def generate_reword_suggestion_prompt(commit_range, merge_base, commits_data, diff):
+    """
+    Creates a prompt asking the AI to identify commits needing rewording
+    and to generate the full new commit message for each.
+    """
+    # Format commit list for the prompt using only short hash and subject
+    commit_list_str = (
+        "\n".join([f"- {c['short_hash']} {c['subject']}" for c in commits_data])
+        if commits_data
+        else "No commits in range."
+    )
+
+    prompt = f"""
+You are an expert Git assistant specializing in commit message conventions. Your task is to analyze the provided Git commit history within the range `{commit_range}` and identify commits whose messages should be improved using `reword` during an interactive rebase (`git rebase -i {merge_base}`).
+
+**Goal:** For each commit needing improvement, generate a **complete, new commit message** (subject and body) that adheres strictly to standard Git conventions.
+
+**Git Commit Message Conventions to Adhere To:**
+
+1.  **Subject Line:** Concise, imperative summary (max 50 chars). Capitalized. No trailing period. Use types like `feat:`, `fix:`, `refactor:`, `perf:`, `test:`, `build:`, `ci:`, `docs:`, `style:`, `chore:`. Example: `feat: Add user authentication endpoint`
+2.  **Blank Line:** Single blank line between subject and body.
+3.  **Body:** Explain 'what' and 'why' (motivation, approach, contrast with previous behavior). Wrap lines at 72 chars. Omit body ONLY for truly trivial changes where the subject is self-explanatory. Example:
+    ```
+    refactor: Improve database query performance
+
+    The previous implementation used multiple sequential queries
+    to fetch related data, leading to N+1 problems under load.
+
+    This change refactors the data access layer to use a single
+    JOIN query, significantly reducing database roundtrips and
+    improving response time for the user profile page.
+    ```
+
+**Provided Context:**
+
+1.  **Commit Range:** `{commit_range}`
+2.  **Merge Base Hash:** `{merge_base}`
+3.  **Commits in Range (Oldest First - Short Hash & Subject):**
+```
+{commit_list_str}
+```
+4.  **Combined Diff for the Range (`git diff --patch-with-stat {commit_range}`):**
+```diff
+{diff if diff else "No differences found or unable to get diff."}
+```
+
+**Instructions:**
+
+1.  Analyze the commits listed above, focusing on their subjects and likely content based on the diff.
+2.  Identify commits whose messages are unclear, too long, lack a type prefix, are poorly formatted, or don't adequately explain the change.
+3.  For **each** commit you identify for rewording, output a block EXACTLY in the following format:
+    ```text
+    REWORD: <short_hash_to_reword>
+    NEW_MESSAGE:
+    <Generated Subject Line Adhering to Conventions>
+
+    <Generated Body Line 1 Adhering to Conventions>
+    <Generated Body Line 2 Adhering to Conventions>
+    ...
+    <Generated Body Last Line Adhering to Conventions>
+    END_MESSAGE
+    ```
+    * Replace `<short_hash_to_reword>` with the short hash from the commit list.
+    * Replace `<Generated Subject Line...>` with the new subject line you generate.
+    * Replace `<Generated Body Line...>` with the lines of the new body you generate (if a body is needed). Ensure a blank line between subject and body, and wrap body lines at 72 characters. If no body is needed, omit the body lines but keep the blank line after the Subject.
+    * The `END_MESSAGE` line marks the end of the message for one commit.
+4.  Provide *only* blocks in the specified `REWORD:...END_MESSAGE` format. Do not include explanations, introductory text, or any other formatting. If no rewording is suggested, output nothing.
+
+Now, analyze the provided context and generate the reword suggestions with complete new messages.
+"""
+    return prompt
+
+
+def parse_reword_suggestions(ai_response_text, commits_data):
+    """Parses AI response for REWORD:/NEW_MESSAGE:/END_MESSAGE blocks."""
+    reword_plan = {}  # Use dict: {short_hash: new_message_string}
+    commit_hashes = {c["short_hash"] for c in commits_data}  # Set of valid short hashes
+
+    # Regex to find blocks
+    pattern = re.compile(
+        r"REWORD:\s*(\w+)\s*NEW_MESSAGE:\s*(.*?)\s*END_MESSAGE",
+        re.DOTALL | re.IGNORECASE,
+    )
+    matches = pattern.findall(ai_response_text)
+
+    for match in matches:
+        reword_hash = match[0].strip()
+        new_message = match[1].strip()  # Includes Subject: and body
+
+        if reword_hash in commit_hashes:
+            reword_plan[reword_hash] = new_message
+            logging.debug(
+                f"Parsed reword suggestion for {reword_hash}:\n{new_message[:100]}..."
+            )
+        else:
+            logging.warning(
+                f"Ignoring invalid reword suggestion (hash {reword_hash} not in range)."
+            )
+
+    return reword_plan
+
+
 # --- request_files_from_user function remains the same ---
 def request_files_from_user(requested_files_str, commits_in_range):
     """
@@ -434,7 +537,8 @@ def request_files_from_user(requested_files_str, commits_in_range):
 # --- Automatic Rebase Logic ---
 
 
-def create_rebase_editor_script(script_path, fixup_plan):
+# --- Fixup Specific ---
+def create_fixup_sequence_editor_script(script_path, fixup_plan):
     """Creates the python script to be used by GIT_SEQUENCE_EDITOR."""
     # Create a set of hashes that need to be fixed up
     fixups_to_apply = {pair["fixup"] for pair in fixup_plan}
@@ -514,20 +618,23 @@ except Exception as e:
         return False
 
 
-def attempt_auto_fixup(merge_base, fixup_plan):
+def attempt_auto_fixup(merge_base, fixup_plan, temp_dir_base):
     """Attempts to perform the rebase automatically applying fixups."""
     if not fixup_plan:
         logging.info("No fixup suggestions provided by AI. Skipping auto-rebase.")
         return True  # Nothing to do, considered success
 
-    # Use a temporary directory to hold the script and its log
-    temp_dir = tempfile.mkdtemp(prefix="git_rebase_")
-    editor_script_path = os.path.join(temp_dir, "rebase_editor.py")
-    logging.debug(f"Temporary directory: {temp_dir}")
-    logging.debug(f"Temporary editor script path: {editor_script_path}")
+    # Use a temporary directory (passed in) to hold the script and its log
+    # Ensure sub-directory for fixup exists
+    fixup_temp_dir = os.path.join(temp_dir_base, "fixup")
+    os.makedirs(fixup_temp_dir, exist_ok=True)
+    editor_script_path = os.path.join(fixup_temp_dir, "fixup_sequence_editor.py")
+    editor_log_path = editor_script_path + ".log" # Define log path early
+    logging.debug(f"Fixup temporary directory: {fixup_temp_dir}")
+    logging.debug(f"Fixup editor script path: {editor_script_path}")
 
     try:
-        if not create_rebase_editor_script(editor_script_path, fixup_plan):
+        if not create_fixup_sequence_editor_script(editor_script_path, fixup_plan):
             return False  # Failed to create script
 
         # Prepare environment for the git command
@@ -596,7 +703,6 @@ def attempt_auto_fixup(merge_base, fixup_plan):
         rebase_failed = "rebase_result" in locals() and rebase_result is None
 
         # Check if we need to display the editor script log
-        editor_log_path = editor_script_path + ".log"
         verbose_logging = logging.getLogger().isEnabledFor(logging.DEBUG)
 
         if (rebase_failed or verbose_logging) and os.path.exists(editor_log_path):
@@ -629,17 +735,287 @@ def attempt_auto_fixup(merge_base, fixup_plan):
                 logging.debug(f"Cleaned up temporary directory: {temp_dir}")
             except OSError as e:
                 logging.warning(
-                    f"Could not completely remove temporary directory {temp_dir}: {e}"
+                    f"Could not completely remove temporary directory {fixup_temp_dir}: {e}"
                 )
+        # Do not remove the base temp_dir here, it's needed for reword
 
 
-# --- Main Execution ---
+# --- Reword Specific ---
+def create_reword_sequence_editor_script(script_path, reword_plan):
+    """Creates the python script for GIT_SEQUENCE_EDITOR (changes pick to reword)."""
+    hashes_to_reword = set(reword_plan.keys())
+
+    script_content = f"""#!/usr/bin/env python3
+import sys
+import logging
+import re
+import os
+
+# Define log file path relative to the script itself
+log_file = __file__ + ".log"
+# Setup logging within the editor script to write to the log file
+logging.basicConfig(filename=log_file, filemode='w', level=logging.WARN, format="%(asctime)s - %(levelname)s: %(message)s")
+
+
+todo_file_path = sys.argv[1]
+logging.info(f"GIT_SEQUENCE_EDITOR (reword) script started for: {{todo_file_path}}")
+
+hashes_to_reword = {hashes_to_reword!r}
+logging.info(f"Applying rewording for hashes: {{hashes_to_reword}}")
+
+new_lines = []
+try:
+    with open(todo_file_path, 'r', encoding='utf-8') as f:
+        lines = f.readlines()
+
+    for line in lines:
+        stripped_line = line.strip()
+        if not stripped_line or stripped_line.startswith('#'):
+            new_lines.append(line)
+            continue
+
+        match = re.match(r"^(\w+)\s+([0-9a-fA-F]+)(.*)", stripped_line)
+        if match:
+            action = match.group(1).lower()
+            commit_hash = match.group(2)
+            rest_of_line = match.group(3)
+
+            if commit_hash in hashes_to_reword and action == 'pick':
+                logging.info(f"Changing 'pick {{commit_hash}}' to 'reword {{commit_hash}}'")
+                new_line = f'r {{commit_hash}}{{rest_of_line}}\n' # Use 'r' for reword
+                new_lines.append(new_line)
+            else:
+                new_lines.append(line)
+        else:
+             logging.warning(f"Could not parse todo line: {{stripped_line}}")
+             new_lines.append(line)
+
+    logging.info(f"Writing {{len(new_lines)}} lines back to {{todo_file_path}}")
+    with open(todo_file_path, 'w', encoding='utf-8') as f:
+        f.writelines(new_lines)
 
+    logging.info("GIT_SEQUENCE_EDITOR (reword) script finished successfully.")
+    sys.exit(0)
 
+except Exception as e:
+    logging.error(f"Error in GIT_SEQUENCE_EDITOR (reword) script: {{e}}", exc_info=True)
+    sys.exit(1)
+"""
+    try:
+        with open(script_path, "w", encoding="utf-8") as f:
+            f.write(script_content)
+        os.chmod(script_path, 0o755)
+        logging.info(f"Created GIT_SEQUENCE_EDITOR (reword) script: {script_path}")
+        return True
+    except Exception as e:
+        logging.error(f"Failed to create GIT_SEQUENCE_EDITOR (reword) script: {e}")
+        return False
+
+
+def create_reword_commit_editor_script(script_path):
+    """Creates the python script for GIT_EDITOR (provides new commit message)."""
+    # Note: reword_plan_json is a JSON string containing the {hash: new_message} mapping
+    script_content = f"""#!/usr/bin/env python3
+import sys
+import logging
+import re
+import os
+import subprocess
+import json
+
+# Define log file path relative to the script itself
+log_file = __file__ + ".log"
+# Setup logging within the editor script to write to the log file
+logging.basicConfig(filename=log_file, filemode='w', level=logging.WARN, format="%(asctime)s - %(levelname)s: %(message)s")
+
+commit_msg_file_path = sys.argv[1]
+logging.info(f"GIT_EDITOR (reword) script started for commit message file: {{commit_msg_file_path}}")
+
+# The reword plan (hash -> new_message) is passed via environment variable as JSON
+reword_plan_json = os.environ.get('GIT_REWORD_PLAN')
+if not reword_plan_json:
+    logging.error("GIT_REWORD_PLAN environment variable not set.")
+    sys.exit(1)
+
+try:
+    reword_plan = json.loads(reword_plan_json)
+    logging.info(f"Loaded reword plan for {{len(reword_plan)}} commits.")
+except json.JSONDecodeError as e:
+    logging.error(f"Failed to decode GIT_REWORD_PLAN JSON: {{e}}")
+    sys.exit(1)
+
+# --- How to identify the current commit being reworded? ---
+# Use `git rev-parse HEAD`? Might work if HEAD points to the commit being edited.
+try:
+    # Use subprocess to run git command to get the full hash of HEAD
+    result = subprocess.run(['git', 'rev-parse', 'HEAD'], capture_output=True, text=True, check=True, encoding='utf-8')
+    current_full_hash = result.stdout.strip()
+    logging.info(f"Current HEAD full hash: {{current_full_hash}}")
+
+    # Find the corresponding short hash in our plan (keys are short hashes)
+    current_short_hash = None
+    for short_h in reword_plan.keys():
+        # Use git rev-parse to check if short_h resolves to current_full_hash
+        logging.debug(f"Verifying short hash {{short_h}} against HEAD {{current_full_hash}}...")
+        try:
+            verify_result = subprocess.run(['git', 'rev-parse', '--verify', f'{{short_h}}^{{commit}}'], capture_output=True, text=True, check=True, encoding='utf-8')
+            verified_full_hash = verify_result.stdout.strip()
+            if verified_full_hash == current_full_hash:
+                current_short_hash = short_h
+                logging.info(f"Matched HEAD {{current_full_hash}} to short hash {{current_short_hash}} in plan.")
+                break
+        except subprocess.CalledProcessError:
+            logging.debug(f"Short hash {{short_h}} does not resolve to HEAD.")
+            continue # Try next short hash in plan
+
+    if current_short_hash is None:
+        logging.warning(f"Could not find a matching commit hash in the reword plan for current HEAD {{current_full_hash}}. Keeping original message.")
+        sys.exit(0) # Exit successfully to avoid blocking rebase, keep original message
+    elif current_short_hash and current_short_hash in reword_plan:
+        new_message = reword_plan[current_short_hash]
+        logging.info(f"Found new message for commit {{current_short_hash}}.")
+        # Remove the "Subject: " prefix if present, Git adds its own structure
+        # Also remove potential leading/trailing whitespace from AI message
+        new_message_content = re.sub(r"^[Ss]ubject:\s*", "", new_message.strip(), count=1)
+
+        logging.info(f"Writing new message to {{commit_msg_file_path}}: {{new_message_content[:100]}}...")
+        with open(commit_msg_file_path, 'w', encoding='utf-8') as f:
+            f.write(new_message_content)
+
+        logging.info("GIT_EDITOR (reword) script finished successfully for reword.")
+        sys.exit(0)
+    else:
+        # Should not happen if current_short_hash was found, but handle defensively
+        logging.warning(f"Could not find a matching commit hash in the reword plan for current HEAD {{current_full_hash}} (Short hash: {{current_short_hash}}). Keeping original message.")
+        sys.exit(0) # Exit successfully to avoid blocking rebase, keep original message
+
+except subprocess.CalledProcessError as e:
+     logging.error(f"Failed to run git rev-parse HEAD: {{e}}")
+     sys.exit(1) # Fail editor script
+except Exception as e:
+    logging.error(f"Error in GIT_EDITOR (reword) script: {{e}}", exc_info=True)
+    sys.exit(1) # Exit with error code
+"""
+    try:
+        with open(script_path, "w", encoding="utf-8") as f:
+            f.write(script_content)
+        os.chmod(script_path, 0o755)
+        logging.info(f"Created GIT_EDITOR (reword) script: {script_path}")
+        return True
+    except Exception as e:
+        logging.error(f"Failed to create GIT_EDITOR (reword) script: {e}")
+        return False
+
+
+def attempt_auto_reword(merge_base, reword_plan, temp_dir_base):
+    """Attempts to perform the rebase automatically applying rewording."""
+    if not reword_plan:
+        logging.info("No reword suggestions provided by AI. Skipping auto-reword.")
+        return True
+
+    # Use a temporary directory (passed in) to hold the scripts
+    reword_temp_dir = os.path.join(temp_dir_base, "reword")
+    os.makedirs(reword_temp_dir, exist_ok=True)
+    seq_editor_script_path = os.path.join(reword_temp_dir, "reword_sequence_editor.py")
+    commit_editor_script_path = os.path.join(reword_temp_dir, "reword_commit_editor.py")
+    seq_log_path = seq_editor_script_path + ".log"
+    commit_log_path = commit_editor_script_path + ".log"
+    logging.debug(f"Reword temporary directory: {reword_temp_dir}")
+
+    try:
+        # Create the sequence editor script (changes pick -> reword)
+        if not create_reword_sequence_editor_script(
+            seq_editor_script_path, reword_plan
+        ):
+            return False
+
+        # Create the commit editor script (provides new message)
+        if not create_reword_commit_editor_script(commit_editor_script_path):
+            return False
+
+        # Prepare environment for the git command
+        rebase_env = os.environ.copy()
+        rebase_env["GIT_SEQUENCE_EDITOR"] = seq_editor_script_path
+        rebase_env["GIT_EDITOR"] = commit_editor_script_path
+        # Pass the plan to the commit editor script via env var as JSON
+        rebase_env["GIT_REWORD_PLAN"] = json.dumps(reword_plan)
+        # Prevent Git from opening a standard editor for messages etc. if our script fails
+        # 'true' simply exits successfully, accepting default messages
+        # rebase_env["GIT_EDITOR"] = "true" # Overridden by specific script path
+
+        print("\nAttempting automatic rebase with suggested rewording...")
+        logging.info(f"Running: git rebase -i {merge_base}")
+
+        rebase_result = run_git_command(
+            ["rebase", "-i", merge_base],
+            check=False,
+            capture_output=False, # Show rebase output directly
+            env=rebase_env,
+        )
+
+        if rebase_result is not None:
+            print("✅ Automatic reword rebase completed successfully.")
+            logging.info("Automatic reword rebase seems successful.")
+            return True
+        else:
+            print("\n❌ Automatic reword rebase failed.")
+            print(
+                "   This could be due to merge conflicts, script errors, or other rebase issues."
+            )
+            logging.warning("Automatic reword rebase failed. Aborting...")
+
+            print("   Attempting to abort the failed rebase (`git rebase --abort`)...")
+            abort_result = run_git_command(
+                ["rebase", "--abort"], check=False, capture_output=False
+            )
+            if abort_result is not None:
+                print(
+                    "   Rebase aborted successfully. Your branch is back to its original state."
+                )
+                logging.info("Failed rebase aborted successfully.")
+            else:
+                print("   ⚠️ Failed to automatically abort the rebase.")
+                print("      Please run `git rebase --abort` manually to clean up.")
+                logging.error("Failed to automatically abort the rebase.")
+            return False
+
+    except Exception as e:
+        logging.error(
+            f"An unexpected error occurred during auto-reword attempt: {e}",
+            exc_info=True,
+        )
+        print("\n❌ An unexpected error occurred during the automatic reword attempt.")
+        print(
+            "   You may need to manually check your Git status and potentially run `git rebase --abort`."
+        )
+        return False
+    finally:
+        # Display logs if needed
+        rebase_failed = "rebase_result" in locals() and rebase_result is None
+        verbose_logging = logging.getLogger().isEnabledFor(logging.DEBUG)
+
+        for log_path, script_name in [(seq_log_path, "Sequence Editor"), (commit_log_path, "Commit Editor")]:
+             if (rebase_failed or verbose_logging) and os.path.exists(log_path):
+                try:
+                    with open(log_path, "r", encoding="utf-8") as log_f:
+                        log_content = log_f.read()
+                    if log_content:
+                        print(f"\n--- Reword {script_name} Script Log ---")
+                        print(log_content.strip())
+                        print("--- End Log ---")
+                    elif verbose_logging:
+                        logging.debug(f"Reword {script_name} script log file was empty: {log_path}")
+                except Exception as log_e:
+                    logging.warning(f"Could not read reword {script_name} script log file {log_path}: {log_e}")
+
+        # Do not remove the base temp_dir here, cleanup happens in main
+
+
+# --- Main Execution ---
 def main():
-    """Main function to orchestrate Git analysis and AI interaction."""
+    """Main function to orchestrate Git analysis, AI interaction, and rebase operations."""
     parser = argparse.ArgumentParser(
-        description="Uses Gemini AI to suggest and automatically attempt Git 'fixup' operations.",
+        description="Uses Gemini AI to suggest and automatically attempt Git 'fixup' and 'reword' operations.",
         formatter_class=argparse.ArgumentDefaultsHelpFormatter,
     )
     parser.add_argument(
@@ -650,11 +1026,16 @@ def main():
         "(e.g., 'origin/main', 'upstream/develop', specific_commit_hash). "
         "Ensure this reference exists and is fetched.",
     )
-    # --- Argument Change ---
     parser.add_argument(
         "--instruct",
         action="store_true",
-        help="Only show AI suggestions and instructions; disable automatic fixup attempt.",
+        help="Only show AI suggestions and instructions; disable automatic rebase attempts.",
+    )
+    # Add argument to skip reword if desired
+    parser.add_argument(
+        "--skip-reword",
+        action="store_true",
+        help="Skip the reword suggestion and rebase phase.",
     )
     parser.add_argument(
         "-v", "--verbose", action="store_true", help="Enable verbose debug logging."
@@ -669,8 +1050,388 @@ def main():
         logging.error("This script must be run from within a Git repository.")
         sys.exit(1)
 
-    current_branch = get_current_branch()
-    if not current_branch:
+    # Create a single temporary directory for all scripts/logs
+    temp_dir_base = tempfile.mkdtemp(prefix="git_rebase_ai_")
+    logging.debug(f"Base temporary directory: {temp_dir_base}")
+
+    try: # Wrap main logic in try/finally for temp dir cleanup
+        current_branch = get_current_branch()
+        if not current_branch:
+            logging.error("Could not determine the current Git branch.")
+            sys.exit(1)
+        logging.info(f"Current branch: {current_branch}")
+
+        upstream_ref = args.upstream_ref
+        logging.info(f"Comparing against reference: {upstream_ref}")
+
+        # --- Safety: Create Backup Branch ---
+        backup_branch = create_backup_branch(current_branch)
+        if not backup_branch:
+            try:
+                confirm = input(
+                    "⚠️ Failed to create backup branch. Continue without backup? (yes/no): "
+                ).lower()
+            except EOFError:
+                logging.warning("Input stream closed. Aborting.")
+                confirm = "no"
+            if confirm != "yes":
+                logging.info("Aborting.")
+                sys.exit(1)
+            else:
+                logging.warning("Proceeding without a backup branch. Be careful!")
+        else:
+            print("-" * 40)
+            print(f"✅ Backup branch created: {backup_branch}")
+            print("   If anything goes wrong, you can restore using:")
+            print(f"     git checkout {current_branch}")
+            print(f"     git reset --hard {backup_branch}")
+            print("-" * 40)
+
+        # --- Gather Initial Git Context ---
+        print("\nGathering initial Git context...")
+        initial_commit_range, initial_merge_base = get_commit_range(upstream_ref, current_branch)
+        if not initial_commit_range:
+            sys.exit(1)
+
+        logging.info(f"Initial analysis range: {initial_commit_range} (Merge Base: {initial_merge_base})")
+
+        initial_commits_list = get_commits_in_range(initial_commit_range) # Simple list for fixup prompt
+        initial_commits_data = get_commits_in_range_data(initial_commit_range) # Dict list for reword prompt
+
+        if not initial_commits_list:
+            logging.info(
+                f"No commits found between '{initial_merge_base}' and '{current_branch}'. Nothing to do."
+            )
+            sys.exit(0)
+
+        initial_file_structure, initial_changed_files = get_changed_files_in_range(initial_commit_range)
+        initial_diff = get_diff_in_range(initial_commit_range)
+
+        if not initial_diff and not initial_changed_files:
+            logging.warning(
+                f"No file changes or diff found between '{initial_merge_base}' and '{current_branch}',"
+            )
+            logging.warning("even though commits exist. AI suggestions might be limited.")
+
+        # --- AI Interaction - Phase 1: Fixup ---
+        print("\n--- Phase 1: Fixup Analysis ---")
+        print("Generating prompt for AI fixup suggestions...")
+        fixup_prompt = generate_fixup_suggestion_prompt(
+            initial_commit_range, initial_merge_base, initial_commits_list, initial_file_structure, initial_diff
+        )
+
+        logging.debug("\n--- Fixup AI Prompt Snippet ---")
+        logging.debug(fixup_prompt[:1000] + "...")
+        logging.debug("--- End Fixup Prompt Snippet ---\n")
+
+        print(f"Sending fixup request to Gemini AI ({MODEL_NAME})...")
+
+        fixup_ai_response = ""
+        fixup_suggestions_text = ""
+        fixup_plan = []
+        try:
+            convo = model.start_chat(history=[])
+            response = convo.send_message(fixup_prompt)
+            fixup_ai_response = response.text
+
+            # Handle potential file requests for fixup
+            while "REQUEST_FILES:" in fixup_ai_response.upper():
+                logging.info("AI requested additional file content for fixup analysis.")
+                additional_context, original_request = request_files_from_user(
+                    fixup_ai_response, initial_commits_list # Use simple list here
+                )
+
+                if additional_context:
+                    logging.info("Sending fetched file content back to AI for fixup...")
+                    follow_up_prompt = f"""
+Okay, here is the content of the files you requested for fixup analysis:
+
+{additional_context}
+
+Please use this new information to refine your **fixup suggestions** based on the original request and context. Provide the final list of `FIXUP: ...` lines now. Remember to *only* suggest fixup actions and output *only* `FIXUP:` lines. Do not ask for more files.
+"""
+                    response = convo.send_message(follow_up_prompt)
+                    fixup_ai_response = response.text
+                else:
+                    logging.info("Proceeding without providing files for fixup analysis.")
+                    no_files_prompt = f"""
+I cannot provide the content for the files you requested ({original_request}).
+Please proceed with generating the **fixup suggestions** based *only* on the initial context (commit list, file structure, diff) I provided earlier. Make your best suggestions without the file content. Provide the final list of `FIXUP: ...` lines now. Remember to *only* suggest fixup actions.
+"""
+                    response = convo.send_message(no_files_prompt)
+                    fixup_ai_response = response.text
+                    break # Exit file request loop
+
+            fixup_suggestions_text = fixup_ai_response.strip()
+            fixup_plan = parse_fixup_suggestions(fixup_suggestions_text, initial_commits_list) # Use simple list
+
+            if not fixup_plan:
+                print("\n💡 AI did not suggest any specific fixup operations.")
+            else:
+                print("\n💡 --- AI Fixup Suggestions --- 💡")
+                for i, pair in enumerate(fixup_plan):
+                    print(f"  {i + 1}. Fixup commit `{pair['fixup']}` into `{pair['target']}`")
+                print("💡 --- End AI Fixup Suggestions --- 💡")
+
+        except Exception as e:
+            logging.error(f"\nAn error occurred during AI fixup interaction: {e}", exc_info=True)
+            # Log feedback if possible
+            try:
+                if response and hasattr(response, "prompt_feedback"): logging.error(f"AI Prompt Feedback: {response.prompt_feedback}")
+                if response and hasattr(response, "candidates"):
+                    for c in response.candidates: logging.error(f"AI Candidate Finish Reason: {c.finish_reason}, Safety: {getattr(c, 'safety_ratings', 'N/A')}")
+            except Exception as feedback_e: logging.error(f"Could not log AI feedback: {feedback_e}")
+            print("\n❌ Error during AI fixup analysis. Skipping remaining steps.")
+            sys.exit(1)
+
+
+        # --- Automatic Rebase - Phase 1: Fixup ---
+        fixup_succeeded = False
+        current_merge_base = initial_merge_base # Start with initial merge base
+
+        if fixup_plan and not args.instruct:
+            print("\n--- Attempting Automatic Fixup Rebase ---")
+            fixup_succeeded = attempt_auto_fixup(initial_merge_base, fixup_plan, temp_dir_base)
+            if not fixup_succeeded:
+                print("\n" + "=" * 60)
+                print("🛠️ MANUAL FIXUP REBASE REQUIRED 🛠️")
+                print("=" * 60)
+                print("The automatic fixup rebase failed (likely due to conflicts).")
+                print("Please perform the fixup rebase manually:")
+                print(f"  1. Run: `git rebase -i {initial_merge_base}`")
+                print("  2. In the editor, change 'pick' to 'f' (or 'fixup') for the commits suggested by the AI:")
+                print("     ```text")
+                print(fixup_suggestions_text if fixup_suggestions_text else "     (No specific fixup lines found in AI response)")
+                print("     ```")
+                print("  3. Save the editor and resolve any conflicts Git reports.")
+                if backup_branch: print(f"  4. Remember backup branch: {backup_branch}")
+                print("=" * 60)
+                sys.exit(1) # Exit after failed auto fixup
+            else:
+                print("\n✅ Automatic fixup rebase completed successfully.")
+                # Need to re-gather context for reword phase
+                print("\nGathering Git context after successful fixup...")
+                post_fixup_range, post_fixup_merge_base = get_commit_range(upstream_ref, current_branch)
+                if not post_fixup_range:
+                    logging.error("Could not determine commit range after fixup rebase. Cannot proceed with reword.")
+                    sys.exit(1)
+                # Update merge base in case it changed (unlikely but possible)
+                current_merge_base = post_fixup_merge_base
+                logging.info(f"Post-fixup analysis range: {post_fixup_range} (Merge Base: {current_merge_base})")
+        elif args.instruct and fixup_plan:
+             # Don't exit, just note that fixup was skipped automatically
+             print("\n--instruct flag used, skipping automatic fixup rebase.")
+             fixup_succeeded = False # Treat as not succeeded for reword context gathering
+        else:
+             # No fixups suggested or attempted automatically
+             fixup_succeeded = True # Treat as succeeded for proceeding to reword
+             print("\nNo automatic fixup rebase needed or attempted.")
+
+
+        # --- AI Interaction - Phase 2: Reword (if fixup succeeded or was skipped) ---
+        reword_plan = []
+        reword_suggestions_text = ""
+        post_fixup_commits_data = [] # Initialize here
+
+        if args.skip_reword:
+            print("\n--skip-reword flag used. Skipping reword phase.")
+        elif fixup_succeeded: # Only proceed if fixup was successful OR wasn't needed
+            print("\n--- Phase 2: Reword Analysis ---")
+
+            # Use post-fixup context if fixup was done, otherwise initial context
+            if fixup_plan and not args.instruct: # Check if fixup was actually performed
+                logging.info("Using post-fixup context for reword analysis.")
+                # Re-gather commits and diff based on the *new* range
+                post_fixup_commits_data = get_commits_in_range_data(post_fixup_range)
+                post_fixup_diff = get_diff_in_range(post_fixup_range)
+                if not post_fixup_commits_data:
+                     print("\nNo commits found after fixup rebase. Skipping reword.")
+                     # Don't exit, just finish gracefully
+                else:
+                    reword_prompt = generate_reword_suggestion_prompt(
+                        post_fixup_range, current_merge_base, post_fixup_commits_data, post_fixup_diff
+                    )
+            else: # No fixup done (or --instruct), use initial context
+                 logging.info("Using initial context for reword analysis.")
+                 post_fixup_commits_data = initial_commits_data # Use initial data
+                 reword_prompt = generate_reword_suggestion_prompt(
+                     initial_commit_range, current_merge_base, initial_commits_data, initial_diff
+                 )
+
+            if post_fixup_commits_data: # Only ask AI if there are commits
+                logging.debug("\n--- Reword AI Prompt Snippet ---")
+                logging.debug(reword_prompt[:1000] + "...")
+                logging.debug("--- End Reword Prompt Snippet ---\n")
+
+                print(f"Sending reword request to Gemini AI ({MODEL_NAME})...")
+                try:
+                    # Use the same conversation or start a new one? Let's use the same.
+                    # If convo doesn't exist (e.g., error during fixup AI), start new
+                    if 'convo' not in locals():
+                        convo = model.start_chat(history=[])
+                    response = convo.send_message(reword_prompt)
+                    reword_ai_response = response.text
+                    reword_suggestions_text = reword_ai_response.strip()
+
+                    # Parse reword suggestions using the appropriate commit data
+                    reword_plan = parse_reword_suggestions(reword_suggestions_text, post_fixup_commits_data)
+
+                    if not reword_plan:
+                        print("\n💡 AI did not suggest any specific reword operations.")
+                    else:
+                        print("\n💡 --- AI Reword Suggestions --- 💡")
+                        for i, (hash_key, msg) in enumerate(reword_plan.items()):
+                            print(f"  {i + 1}. Reword commit `{hash_key}` with new message:")
+                            indented_msg = "     " + msg.replace("\n", "\n     ")
+                            print(indented_msg)
+                            print("-" * 20)
+                        print("💡 --- End AI Reword Suggestions --- 💡")
+
+                except Exception as e:
+                    logging.error(f"\nAn error occurred during AI reword interaction: {e}", exc_info=True)
+                    try: # Log feedback
+                        if response and hasattr(response, "prompt_feedback"): logging.error(f"AI Prompt Feedback: {response.prompt_feedback}")
+                        if response and hasattr(response, "candidates"):
+                            for c in response.candidates: logging.error(f"AI Candidate Finish Reason: {c.finish_reason}, Safety: {getattr(c, 'safety_ratings', 'N/A')}")
+                    except Exception as feedback_e: logging.error(f"Could not log AI feedback: {feedback_e}")
+                    print("\n❌ Error during AI reword analysis. Skipping automatic reword.")
+                    # Don't exit, allow manual instructions if needed
+
+
+        # --- Automatic Rebase - Phase 2: Reword ---
+        reword_succeeded = False
+        if reword_plan and not args.instruct and not args.skip_reword:
+            print("\n--- Attempting Automatic Reword Rebase ---")
+            # Use the potentially updated current_merge_base
+            reword_succeeded = attempt_auto_reword(current_merge_base, reword_plan, temp_dir_base)
+            if not reword_succeeded:
+                print("\n" + "=" * 60)
+                print("🛠️ MANUAL REWORD REBASE REQUIRED 🛠️")
+                print("=" * 60)
+                print("The automatic reword rebase failed.")
+                print("Please perform the reword rebase manually:")
+                print(f"  1. Run: `git rebase -i {current_merge_base}`")
+                print("  2. In the editor, change 'pick' to 'r' (or 'reword') for the commits suggested by the AI:")
+                # Show the *latest* reword suggestions
+                print("     ```text")
+                print(reword_suggestions_text if reword_suggestions_text else "     (No specific reword suggestions found in AI response)")
+                print("     ```")
+                print("  3. Save the editor. Git will stop at each commit marked for reword.")
+                print("  4. Manually replace the old commit message with the AI-suggested one.")
+                print("  5. Save the message editor and continue the rebase (`git rebase --continue`).")
+                if backup_branch: print(f"  6. Remember backup branch: {backup_branch}")
+                print("=" * 60)
+                sys.exit(1) # Exit after failed auto reword
+            else:
+                 print("\n✅ Automatic reword rebase completed successfully.")
+        elif args.instruct and reword_plan:
+             # Don't exit, just note that reword was skipped automatically
+             print("\n--instruct flag used, skipping automatic reword rebase.")
+             reword_succeeded = False # Treat as not succeeded for final message
+        elif not args.skip_reword:
+             # No rewording suggested or attempted automatically
+             reword_succeeded = True # Treat as succeeded for final message
+             print("\nNo automatic reword rebase needed or attempted.")
+
+
+        # --- Final Instructions (if --instruct was used) ---
+        if args.instruct:
+            print("\n" + "=" * 60)
+            print("📝 MANUAL REBASE INSTRUCTIONS (--instruct used) 📝")
+            print("=" * 60)
+            if fixup_plan:
+                print("--- Fixup Phase ---")
+                print("AI suggested the following fixups:")
+                for i, pair in enumerate(fixup_plan):
+                    print(f"  - Fixup commit `{pair['fixup']}` into `{pair['target']}`")
+                print(f"To apply manually:")
+                print(f"  1. Run: `git rebase -i {initial_merge_base}`")
+                print("  2. Change 'pick' to 'f' (or 'fixup') for the suggested commits.")
+                print("  3. Save the editor and resolve conflicts if any.")
+                print("-" * 20)
+            else:
+                print("--- Fixup Phase ---")
+                print("No fixup operations were suggested by the AI.")
+                print("-" * 20)
+
+            if reword_plan and not args.skip_reword:
+                print("--- Reword Phase (After potential manual fixup) ---")
+                print("AI suggested the following rewording:")
+                 # Print parsed plan for clarity
+                for i, (hash_key, msg) in enumerate(reword_plan.items()):
+                    print(f"  - Reword commit `{hash_key}` with new message:")
+                    indented_msg = "     " + msg.replace("\n", "\n     ")
+                    print(indented_msg)
+                print(f"To apply manually (after completing the fixup rebase if any):")
+                # Important: Use the merge base that *would* be correct after fixup
+                # If fixup was suggested, the user needs to complete that first.
+                # The merge base *should* remain 'initial_merge_base' unless history was drastically altered.
+                # Let's use initial_merge_base for simplicity in instructions.
+                print(f"  1. Run: `git rebase -i {initial_merge_base}` (or the new base if fixup changed it)")
+                print("  2. Change 'pick' to 'r' (or 'reword') for the suggested commits.")
+                print("  3. Save the editor. Git will stop at each commit.")
+                print("  4. Replace the old message with the AI suggestion.")
+                print("  5. Continue the rebase (`git rebase --continue`).")
+                print("-" * 20)
+            elif not args.skip_reword:
+                print("--- Reword Phase ---")
+                print("No reword operations were suggested by the AI.")
+                print("-" * 20)
+
+            if backup_branch:
+                print(f"Remember backup branch: {backup_branch}")
+            print("=" * 60)
+
+        # --- Final Status Message ---
+        print("\n--- Summary ---")
+        if fixup_plan and not args.instruct:
+            print(f"Fixup rebase attempt: {'Success' if fixup_succeeded else 'Failed/Skipped'}")
+        if reword_plan and not args.instruct and not args.skip_reword:
+             print(f"Reword rebase attempt: {'Success' if reword_succeeded else 'Failed/Skipped'}")
+
+        if (not fixup_plan or fixup_succeeded) and (args.skip_reword or not reword_plan or reword_succeeded):
+             print("\nBranch history has been potentially modified.")
+        else:
+             print("\nBranch history may be unchanged or in an intermediate state due to manual steps required.")
+
+        if backup_branch:
+            print(f"Backup branch '{backup_branch}' still exists if needed.")
+
+    finally:
+        # Clean up the base temporary directory and its contents
+        if temp_dir_base and os.path.exists(temp_dir_base):
+            try:
+                import shutil
+                shutil.rmtree(temp_dir_base)
+                logging.debug(f"Cleaned up base temporary directory: {temp_dir_base}")
+            except OSError as e:
+                logging.warning(
+                    f"Could not completely remove base temporary directory {temp_dir_base}: {e}"
+                )
+
+
+# Helper function to get commit data as dict list (needed for reword)
+def get_commits_in_range_data(commit_range):
+    """Gets a list of commit data (short_hash, full_hash, subject) in the specified range (oldest first)."""
+    # Use --format=%h %H %s to get hashes and subject
+    log_output = run_git_command(
+        ["log", "--pretty=format:%h %H %s", "--reverse", commit_range]
+    )
+    commit_data = []
+    if log_output is not None:
+        lines = log_output.splitlines()
+        for line in lines:
+            parts = line.split(" ", 2)
+            if len(parts) == 3:
+                commit_data.append(
+                    {"short_hash": parts[0], "full_hash": parts[1], "subject": parts[2]}
+                )
+            else:
+                logging.warning(f"Could not parse commit log line: {line}")
+        logging.info(f"Found {len(commit_data)} commits in range {commit_range} (for reword).")
+    return commit_data
+
+# Removed duplicated main execution block
         logging.error("Could not determine the current Git branch.")
         sys.exit(1)
     logging.info(f"Current branch: {current_branch}")
@@ -708,12 +1469,386 @@ def main():
     if not commit_range:
         sys.exit(1)
 
-    logging.info(f"Analyzing commit range: {commit_range} (Merge Base: {merge_base})")
+    try: # Wrap main logic in try/finally for temp dir cleanup
+        current_branch = get_current_branch()
+        if not current_branch:
+            logging.error("Could not determine the current Git branch.")
+            sys.exit(1)
+        logging.info(f"Current branch: {current_branch}")
 
-    commits = get_commits_in_range(commit_range)
-    if not commits:
-        logging.info(
-            f"No commits found between '{merge_base}' and '{current_branch}'. Nothing to do."
+        upstream_ref = args.upstream_ref
+        logging.info(f"Comparing against reference: {upstream_ref}")
+
+        # --- Safety: Create Backup Branch ---
+        backup_branch = create_backup_branch(current_branch)
+        if not backup_branch:
+            try:
+                confirm = input(
+                    "⚠️ Failed to create backup branch. Continue without backup? (yes/no): "
+                ).lower()
+            except EOFError:
+                logging.warning("Input stream closed. Aborting.")
+                confirm = "no"
+            if confirm != "yes":
+                logging.info("Aborting.")
+                sys.exit(1)
+            else:
+                logging.warning("Proceeding without a backup branch. Be careful!")
+        else:
+            print("-" * 40)
+            print(f"✅ Backup branch created: {backup_branch}")
+            print("   If anything goes wrong, you can restore using:")
+            print(f"     git checkout {current_branch}")
+            print(f"     git reset --hard {backup_branch}")
+            print("-" * 40)
+
+        # --- Gather Initial Git Context ---
+        print("\nGathering initial Git context...")
+        initial_commit_range, initial_merge_base = get_commit_range(upstream_ref, current_branch)
+        if not initial_commit_range:
+            sys.exit(1)
+
+        logging.info(f"Initial analysis range: {initial_commit_range} (Merge Base: {initial_merge_base})")
+
+        initial_commits_list = get_commits_in_range(initial_commit_range) # Simple list for fixup prompt
+        initial_commits_data = get_commits_in_range_data(initial_commit_range) # Dict list for reword prompt
+
+        if not initial_commits_list:
+            logging.info(
+                f"No commits found between '{initial_merge_base}' and '{current_branch}'. Nothing to do."
+            )
+            sys.exit(0)
+
+        initial_file_structure, initial_changed_files = get_changed_files_in_range(initial_commit_range)
+        initial_diff = get_diff_in_range(initial_commit_range)
+
+        if not initial_diff and not initial_changed_files:
+            logging.warning(
+                f"No file changes or diff found between '{initial_merge_base}' and '{current_branch}',"
+            )
+            logging.warning("even though commits exist. AI suggestions might be limited.")
+
+        # --- AI Interaction - Phase 1: Fixup ---
+        print("\n--- Phase 1: Fixup Analysis ---")
+        print("Generating prompt for AI fixup suggestions...")
+        fixup_prompt = generate_fixup_suggestion_prompt(
+            initial_commit_range, initial_merge_base, initial_commits_list, initial_file_structure, initial_diff
+        )
+
+        logging.debug("\n--- Fixup AI Prompt Snippet ---")
+        logging.debug(fixup_prompt[:1000] + "...")
+        logging.debug("--- End Fixup Prompt Snippet ---\n")
+
+        print(f"Sending fixup request to Gemini AI ({MODEL_NAME})...")
+
+        fixup_ai_response = ""
+        fixup_suggestions_text = ""
+        fixup_plan = []
+        try:
+            convo = model.start_chat(history=[])
+            response = convo.send_message(fixup_prompt)
+            fixup_ai_response = response.text
+
+            # Handle potential file requests for fixup
+            while "REQUEST_FILES:" in fixup_ai_response.upper():
+                logging.info("AI requested additional file content for fixup analysis.")
+                additional_context, original_request = request_files_from_user(
+                    fixup_ai_response, initial_commits_list # Use simple list here
+                )
+
+                if additional_context:
+                    logging.info("Sending fetched file content back to AI for fixup...")
+                    follow_up_prompt = f"""
+Okay, here is the content of the files you requested for fixup analysis:
+
+{additional_context}
+
+Please use this new information to refine your **fixup suggestions** based on the original request and context. Provide the final list of `FIXUP: ...` lines now. Remember to *only* suggest fixup actions and output *only* `FIXUP:` lines. Do not ask for more files.
+"""
+                    response = convo.send_message(follow_up_prompt)
+                    fixup_ai_response = response.text
+                else:
+                    logging.info("Proceeding without providing files for fixup analysis.")
+                    no_files_prompt = f"""
+I cannot provide the content for the files you requested ({original_request}).
+Please proceed with generating the **fixup suggestions** based *only* on the initial context (commit list, file structure, diff) I provided earlier. Make your best suggestions without the file content. Provide the final list of `FIXUP: ...` lines now. Remember to *only* suggest fixup actions.
+"""
+                    response = convo.send_message(no_files_prompt)
+                    fixup_ai_response = response.text
+                    break # Exit file request loop
+
+            fixup_suggestions_text = fixup_ai_response.strip()
+            fixup_plan = parse_fixup_suggestions(fixup_suggestions_text, initial_commits_list) # Use simple list
+
+            if not fixup_plan:
+                print("\n💡 AI did not suggest any specific fixup operations.")
+            else:
+                print("\n💡 --- AI Fixup Suggestions --- 💡")
+                for i, pair in enumerate(fixup_plan):
+                    print(f"  {i + 1}. Fixup commit `{pair['fixup']}` into `{pair['target']}`")
+                print("💡 --- End AI Fixup Suggestions --- 💡")
+
+        except Exception as e:
+            logging.error(f"\nAn error occurred during AI fixup interaction: {e}", exc_info=True)
+            # Log feedback if possible
+            try:
+                if response and hasattr(response, "prompt_feedback"): logging.error(f"AI Prompt Feedback: {response.prompt_feedback}")
+                if response and hasattr(response, "candidates"):
+                    for c in response.candidates: logging.error(f"AI Candidate Finish Reason: {c.finish_reason}, Safety: {getattr(c, 'safety_ratings', 'N/A')}")
+            except Exception as feedback_e: logging.error(f"Could not log AI feedback: {feedback_e}")
+            print("\n❌ Error during AI fixup analysis. Skipping remaining steps.")
+            sys.exit(1)
+
+
+        # --- Automatic Rebase - Phase 1: Fixup ---
+        fixup_succeeded = False
+        current_merge_base = initial_merge_base # Start with initial merge base
+
+        if fixup_plan and not args.instruct:
+            print("\n--- Attempting Automatic Fixup Rebase ---")
+            fixup_succeeded = attempt_auto_fixup(initial_merge_base, fixup_plan, temp_dir_base)
+            if not fixup_succeeded:
+                print("\n" + "=" * 60)
+                print("🛠️ MANUAL FIXUP REBASE REQUIRED 🛠️")
+                print("=" * 60)
+                print("The automatic fixup rebase failed (likely due to conflicts).")
+                print("Please perform the fixup rebase manually:")
+                print(f"  1. Run: `git rebase -i {initial_merge_base}`")
+                print("  2. In the editor, change 'pick' to 'f' (or 'fixup') for the commits suggested by the AI:")
+                print("     ```text")
+                print(fixup_suggestions_text if fixup_suggestions_text else "     (No specific fixup lines found in AI response)")
+                print("     ```")
+                print("  3. Save the editor and resolve any conflicts Git reports.")
+                if backup_branch: print(f"  4. Remember backup branch: {backup_branch}")
+                print("=" * 60)
+                sys.exit(1) # Exit after failed auto fixup
+            else:
+                print("\n✅ Automatic fixup rebase completed successfully.")
+                # Need to re-gather context for reword phase
+                print("\nGathering Git context after successful fixup...")
+                post_fixup_range, post_fixup_merge_base = get_commit_range(upstream_ref, current_branch)
+                if not post_fixup_range:
+                    logging.error("Could not determine commit range after fixup rebase. Cannot proceed with reword.")
+                    sys.exit(1)
+                # Update merge base in case it changed (unlikely but possible)
+                current_merge_base = post_fixup_merge_base
+                logging.info(f"Post-fixup analysis range: {post_fixup_range} (Merge Base: {current_merge_base})")
+        elif args.instruct and fixup_plan:
+             # Don't exit, just note that fixup was skipped automatically
+             print("\n--instruct flag used, skipping automatic fixup rebase.")
+             fixup_succeeded = False # Treat as not succeeded for reword context gathering
+        else:
+             # No fixups suggested or attempted automatically
+             fixup_succeeded = True # Treat as succeeded for proceeding to reword
+             print("\nNo automatic fixup rebase needed or attempted.")
+
+
+        # --- AI Interaction - Phase 2: Reword (if fixup succeeded or was skipped) ---
+        reword_plan = []
+        reword_suggestions_text = ""
+        post_fixup_commits_data = [] # Initialize here
+
+        if args.skip_reword:
+            print("\n--skip-reword flag used. Skipping reword phase.")
+        elif fixup_succeeded: # Only proceed if fixup was successful OR wasn't needed
+            print("\n--- Phase 2: Reword Analysis ---")
+
+            # Use post-fixup context if fixup was done, otherwise initial context
+            if fixup_plan and not args.instruct: # Check if fixup was actually performed
+                logging.info("Using post-fixup context for reword analysis.")
+                # Re-gather commits and diff based on the *new* range
+                post_fixup_commits_data = get_commits_in_range_data(post_fixup_range)
+                post_fixup_diff = get_diff_in_range(post_fixup_range)
+                if not post_fixup_commits_data:
+                     print("\nNo commits found after fixup rebase. Skipping reword.")
+                     # Don't exit, just finish gracefully
+                else:
+                    reword_prompt = generate_reword_suggestion_prompt(
+                        post_fixup_range, current_merge_base, post_fixup_commits_data, post_fixup_diff
+                    )
+            else: # No fixup done (or --instruct), use initial context
+                 logging.info("Using initial context for reword analysis.")
+                 post_fixup_commits_data = initial_commits_data # Use initial data
+                 reword_prompt = generate_reword_suggestion_prompt(
+                     initial_commit_range, current_merge_base, initial_commits_data, initial_diff
+                 )
+
+            if post_fixup_commits_data: # Only ask AI if there are commits
+                logging.debug("\n--- Reword AI Prompt Snippet ---")
+                logging.debug(reword_prompt[:1000] + "...")
+                logging.debug("--- End Reword Prompt Snippet ---\n")
+
+                print(f"Sending reword request to Gemini AI ({MODEL_NAME})...")
+                try:
+                    # Use the same conversation or start a new one? Let's use the same.
+                    # If convo doesn't exist (e.g., error during fixup AI), start new
+                    if 'convo' not in locals():
+                        convo = model.start_chat(history=[])
+                    response = convo.send_message(reword_prompt)
+                    reword_ai_response = response.text
+                    reword_suggestions_text = reword_ai_response.strip()
+
+                    # Parse reword suggestions using the appropriate commit data
+                    reword_plan = parse_reword_suggestions(reword_suggestions_text, post_fixup_commits_data)
+
+                    if not reword_plan:
+                        print("\n💡 AI did not suggest any specific reword operations.")
+                    else:
+                        print("\n💡 --- AI Reword Suggestions --- 💡")
+                        for i, (hash_key, msg) in enumerate(reword_plan.items()):
+                            print(f"  {i + 1}. Reword commit `{hash_key}` with new message:")
+                            indented_msg = "     " + msg.replace("\n", "\n     ")
+                            print(indented_msg)
+                            print("-" * 20)
+                        print("💡 --- End AI Reword Suggestions --- 💡")
+
+                except Exception as e:
+                    logging.error(f"\nAn error occurred during AI reword interaction: {e}", exc_info=True)
+                    try: # Log feedback
+                        if response and hasattr(response, "prompt_feedback"): logging.error(f"AI Prompt Feedback: {response.prompt_feedback}")
+                        if response and hasattr(response, "candidates"):
+                            for c in response.candidates: logging.error(f"AI Candidate Finish Reason: {c.finish_reason}, Safety: {getattr(c, 'safety_ratings', 'N/A')}")
+                    except Exception as feedback_e: logging.error(f"Could not log AI feedback: {feedback_e}")
+                    print("\n❌ Error during AI reword analysis. Skipping automatic reword.")
+                    # Don't exit, allow manual instructions if needed
+
+
+        # --- Automatic Rebase - Phase 2: Reword ---
+        reword_succeeded = False
+        if reword_plan and not args.instruct and not args.skip_reword:
+            print("\n--- Attempting Automatic Reword Rebase ---")
+            # Use the potentially updated current_merge_base
+            reword_succeeded = attempt_auto_reword(current_merge_base, reword_plan, temp_dir_base)
+            if not reword_succeeded:
+                print("\n" + "=" * 60)
+                print("🛠️ MANUAL REWORD REBASE REQUIRED 🛠️")
+                print("=" * 60)
+                print("The automatic reword rebase failed.")
+                print("Please perform the reword rebase manually:")
+                print(f"  1. Run: `git rebase -i {current_merge_base}`")
+                print("  2. In the editor, change 'pick' to 'r' (or 'reword') for the commits suggested by the AI:")
+                # Show the *latest* reword suggestions
+                print("     ```text")
+                print(reword_suggestions_text if reword_suggestions_text else "     (No specific reword suggestions found in AI response)")
+                print("     ```")
+                print("  3. Save the editor. Git will stop at each commit marked for reword.")
+                print("  4. Manually replace the old commit message with the AI-suggested one.")
+                print("  5. Save the message editor and continue the rebase (`git rebase --continue`).")
+                if backup_branch: print(f"  6. Remember backup branch: {backup_branch}")
+                print("=" * 60)
+                sys.exit(1) # Exit after failed auto reword
+            else:
+                 print("\n✅ Automatic reword rebase completed successfully.")
+        elif args.instruct and reword_plan:
+             # Don't exit, just note that reword was skipped automatically
+             print("\n--instruct flag used, skipping automatic reword rebase.")
+             reword_succeeded = False # Treat as not succeeded for final message
+        elif not args.skip_reword:
+             # No rewording suggested or attempted automatically
+             reword_succeeded = True # Treat as succeeded for final message
+             print("\nNo automatic reword rebase needed or attempted.")
+
+
+        # --- Final Instructions (if --instruct was used) ---
+        if args.instruct:
+            print("\n" + "=" * 60)
+            print("📝 MANUAL REBASE INSTRUCTIONS (--instruct used) 📝")
+            print("=" * 60)
+            if fixup_plan:
+                print("--- Fixup Phase ---")
+                print("AI suggested the following fixups:")
+                for i, pair in enumerate(fixup_plan):
+                    print(f"  - Fixup commit `{pair['fixup']}` into `{pair['target']}`")
+                print(f"To apply manually:")
+                print(f"  1. Run: `git rebase -i {initial_merge_base}`")
+                print("  2. Change 'pick' to 'f' (or 'fixup') for the suggested commits.")
+                print("  3. Save the editor and resolve conflicts if any.")
+                print("-" * 20)
+            else:
+                print("--- Fixup Phase ---")
+                print("No fixup operations were suggested by the AI.")
+                print("-" * 20)
+
+            if reword_plan and not args.skip_reword:
+                print("--- Reword Phase (After potential manual fixup) ---")
+                print("AI suggested the following rewording:")
+                 # Print parsed plan for clarity
+                for i, (hash_key, msg) in enumerate(reword_plan.items()):
+                    print(f"  - Reword commit `{hash_key}` with new message:")
+                    indented_msg = "     " + msg.replace("\n", "\n     ")
+                    print(indented_msg)
+                print(f"To apply manually (after completing the fixup rebase if any):")
+                # Important: Use the merge base that *would* be correct after fixup
+                # If fixup was suggested, the user needs to complete that first.
+                # The merge base *should* remain 'initial_merge_base' unless history was drastically altered.
+                # Let's use initial_merge_base for simplicity in instructions.
+                print(f"  1. Run: `git rebase -i {initial_merge_base}` (or the new base if fixup changed it)")
+                print("  2. Change 'pick' to 'r' (or 'reword') for the suggested commits.")
+                print("  3. Save the editor. Git will stop at each commit.")
+                print("  4. Replace the old message with the AI suggestion.")
+                print("  5. Continue the rebase (`git rebase --continue`).")
+                print("-" * 20)
+            elif not args.skip_reword:
+                print("--- Reword Phase ---")
+                print("No reword operations were suggested by the AI.")
+                print("-" * 20)
+
+            if backup_branch:
+                print(f"Remember backup branch: {backup_branch}")
+            print("=" * 60)
+
+        # --- Final Status Message ---
+        print("\n--- Summary ---")
+        if fixup_plan and not args.instruct:
+            print(f"Fixup rebase attempt: {'Success' if fixup_succeeded else 'Failed/Skipped'}")
+        if reword_plan and not args.instruct and not args.skip_reword:
+             print(f"Reword rebase attempt: {'Success' if reword_succeeded else 'Failed/Skipped'}")
+
+        if (not fixup_plan or fixup_succeeded) and (args.skip_reword or not reword_plan or reword_succeeded):
+             print("\nBranch history has been potentially modified.")
+        else:
+             print("\nBranch history may be unchanged or in an intermediate state due to manual steps required.")
+
+        if backup_branch:
+            print(f"Backup branch '{backup_branch}' still exists if needed.")
+
+    finally:
+        # Clean up the base temporary directory and its contents
+        if temp_dir_base and os.path.exists(temp_dir_base):
+            try:
+                import shutil
+                shutil.rmtree(temp_dir_base)
+                logging.debug(f"Cleaned up base temporary directory: {temp_dir_base}")
+            except OSError as e:
+                logging.warning(
+                    f"Could not completely remove base temporary directory {temp_dir_base}: {e}"
+                )
+
+
+# Helper function to get commit data as dict list (needed for reword)
+def get_commits_in_range_data(commit_range):
+    """Gets a list of commit data (short_hash, full_hash, subject) in the specified range (oldest first)."""
+    # Use --format=%h %H %s to get hashes and subject
+    log_output = run_git_command(
+        ["log", "--pretty=format:%h %H %s", "--reverse", commit_range]
+    )
+    commit_data = []
+    if log_output is not None:
+        lines = log_output.splitlines()
+        for line in lines:
+            parts = line.split(" ", 2)
+            if len(parts) == 3:
+                commit_data.append(
+                    {"short_hash": parts[0], "full_hash": parts[1], "subject": parts[2]}
+                )
+            else:
+                logging.warning(f"Could not parse commit log line: {line}")
+        logging.info(f"Found {len(commit_data)} commits in range {commit_range} (for reword).")
+    return commit_data
+
+
+if __name__ == "__main__":
+    main()
         )
         sys.exit(0)