Skip to content

Conversation

@sogoiii
Copy link
Contributor

@sogoiii sogoiii commented Dec 5, 2025

Purpose

Add comprehensive CLI commands for managing custom system prompts, enabling users to override the default prompt with their own custom instructions for commit message generation.

Summary

  • ✅ New gac prompt subcommand suite with show/set/clear operations
  • ✅ Interactive prompt editor with --edit flag for terminal-based editing
  • ✅ File-based prompt import with --file option
  • ✅ Environment variable support via GAC_SYSTEM_PROMPT_PATH
  • ✅ 100% test coverage for all commands and edge cases

Changes

CLI Commands

gac prompt show - Display the currently active system prompt (custom or default)

gac prompt set --edit - Open interactive terminal editor with:

  • Multi-line text editing via prompt_toolkit
  • Terminal size adaptation
  • Save/cancel shortcuts with visual hints
  • Pre-populated with existing prompt content

gac prompt set --file <path> - Import custom prompt from file

gac prompt clear - Remove custom prompt and revert to default

Prompt Precedence

  1. GAC_SYSTEM_PROMPT_PATH environment variable (highest priority)
  2. ~/.config/gac/custom_system_prompt.txt (stored configuration)
  3. Built-in default prompt (fallback)

Code Structure

  • src/gac/prompt_cli.py (265 lines) - Main prompt management logic
  • Updated src/gac/cli.py - Integrated new subcommand
  • tests/test_prompt_cli.py (265 lines) - Comprehensive test suite

Impact Analysis

  • Scope: CLI command addition, no breaking changes
  • Test Coverage: 265 lines of tests for all commands and edge cases
  • Bundle Size: Minimal - uses existing dependencies (prompt_toolkit)
  • Dependencies: None new required

Validation

  • ✅ Unit Tests: Comprehensive coverage for all commands
  • ✅ Edge Cases: Handles missing files, invalid paths, empty prompts
  • ✅ Error Handling: Clear messages for all failure scenarios
  • ✅ Integration: Seamlessly integrates with existing CLI structure

Summary by CodeRabbit

  • New Features

    • Added a new prompt command group for managing system prompts
    • prompt show: displays the active prompt and its source
    • prompt set: create or update prompts via interactive editor or external file
    • prompt clear: remove custom prompt settings
    • Environment-variable support for specifying prompt files
  • Tests

    • Added comprehensive tests covering show/set/clear behavior, precedence, editor flows, and error cases

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 5, 2025

Walkthrough

A new prompt management CLI module was added and integrated into the main CLI, providing commands to show, set (interactive or from file), and clear custom system prompts, with environment-variable precedence and an embedded terminal editor.

Changes

Cohort / File(s) Change Summary
CLI Integration
src/gac/cli.py
Added import from gac.prompt_cli import prompt as prompt_cli and registered the command with cli.add_command(prompt_cli) to expose the new prompt subcommands.
Prompt Management Module
src/gac/prompt_cli.py
New module implementing get_active_custom_prompt() and a Click prompt group with show, set (mutually-exclusive --edit / --file), and clear subcommands; includes interactive editor via prompt_toolkit, env var (GAC_SYSTEM_PROMPT_PATH) precedence, file I/O, and error handling.
Tests
tests/test_prompt_cli.py
New comprehensive tests for show/set/clear and get_active_custom_prompt(), covering env-var precedence, file operations, editor interactions, error cases, and CLI behavior (CliRunner, fixtures, monkeypatching).

Sequence Diagram

sequenceDiagram
    participant User as User/Terminal
    participant CLI as gac prompt CLI
    participant FS as File System
    participant Editor as prompt_toolkit Editor

    User->>CLI: prompt set --edit
    CLI->>FS: Resolve target path (GAC_SYSTEM_PROMPT_PATH or stored file)
    FS-->>CLI: Path (and existing content if any)
    CLI->>Editor: Launch editor with current content (VI mode)
    Editor->>User: Display editor
    User->>Editor: Edit and Save (Ctrl+S / Esc+Enter) or Cancel (Ctrl+C)
    Editor-->>CLI: Return edited text or cancellation
    alt saved
        CLI->>FS: Ensure directories exist
        CLI->>FS: Write prompt file
        FS-->>CLI: Write success
        CLI->>User: Confirmation message
    else cancelled or empty
        CLI->>User: Abort or no-op message
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Review prompt_toolkit integration and signal/keyboard handling in the interactive editor.
  • Verify file path resolution and env-var precedence in get_active_custom_prompt().
  • Check file I/O error handling (read/write permissions, missing dirs) across set/clear.
  • Inspect tests for robustness around environment fixtures and editor monkeypatching.

Poem

🐰 I hopped into code with glee,
Prompt commands now sprout for me,
Show, set, and clear — a tidy feat,
Edit in terminal, cozy and neat.
Ctrl+S sings: the prompt is complete.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: addition of a custom system prompt management CLI with the 'gac prompt' subcommand suite.
Docstring Coverage ✅ Passed Docstring coverage is 87.10% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Dec 5, 2025

Codecov Report

❌ Patch coverage is 55.40541% with 66 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/gac/prompt_cli.py 54.79% 62 Missing and 4 partials ⚠️

📢 Thoughts on this report? Let us know!

@cellwebb cellwebb force-pushed the main branch 2 times, most recently from f35749f to 1a02629 Compare December 6, 2025 19:39
@sogoiii sogoiii marked this pull request as ready for review December 11, 2025 03:33
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/gac/prompt_cli.py (1)

18-46: Consider logging OSError exceptions for debugging.

The function correctly implements the precedence logic (env var → stored file → default). However, lines 34-35 and 42-43 silently catch OSError exceptions. While this graceful degradation is appropriate for config file reading, adding logging would help debug file permission or corruption issues.

Apply this diff to add debug logging:

             try:
                 content = env_file.read_text(encoding="utf-8")
                 return content, f"GAC_SYSTEM_PROMPT_PATH={env_path}"
-            except OSError:
+            except OSError as e:
+                logger.debug(f"Failed to read env var prompt file {env_path}: {e}")
                 pass
 
     # Check stored custom prompt file
     if CUSTOM_PROMPT_FILE.exists():
         try:
             content = CUSTOM_PROMPT_FILE.read_text(encoding="utf-8")
             return content, str(CUSTOM_PROMPT_FILE)
-        except OSError:
+        except OSError as e:
+            logger.debug(f"Failed to read stored prompt file {CUSTOM_PROMPT_FILE}: {e}")
             pass
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 39cb796 and 505a87c.

📒 Files selected for processing (3)
  • src/gac/cli.py (2 hunks)
  • src/gac/prompt_cli.py (1 hunks)
  • tests/test_prompt_cli.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.py: Use type annotations throughout the codebase
Use snake_case for modules and functions, CapWords for classes

Files:

  • src/gac/cli.py
  • src/gac/prompt_cli.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Use Ruff formatter with 120-character line limit

Files:

  • src/gac/cli.py
  • src/gac/prompt_cli.py
  • tests/test_prompt_cli.py
🧬 Code graph analysis (2)
src/gac/cli.py (1)
src/gac/prompt_cli.py (1)
  • prompt (50-52)
tests/test_prompt_cli.py (1)
src/gac/prompt_cli.py (2)
  • get_active_custom_prompt (18-46)
  • prompt (50-52)
🔇 Additional comments (13)
src/gac/cli.py (2)

26-26: LGTM!

The import follows the established naming pattern for CLI command imports and maintains alphabetical ordering.


206-206: LGTM!

Command registration follows the established pattern and maintains alphabetical ordering with other commands.

tests/test_prompt_cli.py (6)

12-26: Well-designed test fixtures.

The mock_paths fixture properly isolates file system operations by overriding module-level constants, and the runner fixture provides a clean interface for CLI testing.


29-83: Excellent test coverage for the show command.

The tests comprehensively cover all prompt sources (none, env var, stored file) and correctly verify precedence rules. The output normalization on line 50 is a good practice for handling Rich's potential line wrapping.


85-119: Thorough coverage of file-based prompt setting.

Tests validate successful copying, error handling for missing files, and automatic directory creation. The flexible error message assertion on line 104 is appropriate for different error formats.


121-190: Comprehensive interactive editing test coverage.

The tests properly mock _edit_text_interactive to avoid interactive prompts and verify all key scenarios including new/update/abort flows. Lines 135, 150, and 186 correctly validate that the editor receives appropriate initial content, and line 189 ensures env-var-based editing updates the correct file.


192-213: Good coverage of clear command behavior.

Tests validate both file deletion and idempotent behavior when no file exists. The assertion on line 212 correctly ensures graceful handling of the no-op case.


215-265: Complete coverage of the prompt resolution helper.

Tests thoroughly validate get_active_custom_prompt across all scenarios including precedence rules and graceful handling of missing files. Lines 262-265 correctly verify that nonexistent env var paths return (None, None) rather than raising errors.

src/gac/prompt_cli.py (5)

1-16: Clean module setup.

Module structure follows Python conventions with appropriate imports, docstring, and module-level constants. Logger and console instances are properly initialized.


55-73: Well-implemented show command.

The local import on line 58 appropriately avoids potential circular dependencies. The logic correctly displays either the custom prompt or the default template, with appropriate title formatting based on the source.


76-179: Robust interactive editor implementation.

The function implements a full-featured terminal editor with:

  • Terminal size adaptation (lines 108-113) for better UX
  • Clear key bindings (Ctrl+S, Esc+Enter for save; Ctrl+C for cancel)
  • Comprehensive error handling (lines 174-179)
  • VI editing mode for power users

The local imports on lines 81-89 appropriately defer dependency loading until needed.


182-205: Correctly determines edit target based on environment.

The function properly respects GAC_SYSTEM_PROMPT_PATH when set, falling back to the default stored file. Error handling is consistent with get_active_custom_prompt.


258-265: Clarify clear command behavior with environment variables.

The clear command only deletes CUSTOM_PROMPT_FILE and does not touch files referenced by GAC_SYSTEM_PROMPT_PATH. While this is probably the intended behavior (not modifying user-managed files), users might be confused if they have an env var set and expect clear to remove all custom prompts.

Consider adding a note to the help text or output when GAC_SYSTEM_PROMPT_PATH is set:

 @prompt.command()
 def clear() -> None:
-    """Clear custom system prompt (revert to default)."""
+    """Clear custom system prompt (revert to default).
+    
+    Note: This only clears the stored prompt file. If GAC_SYSTEM_PROMPT_PATH
+    is set, you'll need to unset it or delete that file separately.
+    """
+    env_path = os.getenv("GAC_SYSTEM_PROMPT_PATH")
+    if env_path:
+        console.print(f"[yellow]Note: GAC_SYSTEM_PROMPT_PATH is set to {env_path}[/yellow]")
+        console.print("[yellow]You may need to unset this environment variable or delete that file.[/yellow]\n")
+    
     if CUSTOM_PROMPT_FILE.exists():
         CUSTOM_PROMPT_FILE.unlink()
         console.print(f"Custom prompt deleted: {CUSTOM_PROMPT_FILE}")
     else:
         console.print("No custom prompt file to delete.")

Comment on lines +208 to +256
@prompt.command()
@click.option("--edit", "-e", is_flag=True, help="Edit prompt interactively in terminal")
@click.option("--file", "file_path", type=click.Path(exists=True), help="Copy prompt from file")
def set(edit: bool, file_path: str | None) -> None:
"""Set custom system prompt via interactive editor or file."""
# Require exactly one of --edit or --file
if edit and file_path:
console.print("[red]Error: --edit and --file are mutually exclusive[/red]")
raise click.Abort()

if not edit and not file_path:
console.print("[red]Error: either --edit or --file must be specified[/red]")
raise click.Abort()

if edit:
# Get the target file and its current content
target_file, initial_content = _get_prompt_file_to_edit()

# Create parent directory if needed
target_file.parent.mkdir(parents=True, exist_ok=True)

# Open interactive editor
result = _edit_text_interactive(initial_content)

if result is None:
console.print("\n[yellow]Edit cancelled, no changes made.[/yellow]")
return

if not result:
console.print("\n[yellow]Empty prompt not saved.[/yellow]")
return

# Save result
target_file.write_text(result, encoding="utf-8")
console.print(f"\n[green]Custom prompt saved to {target_file}[/green]")

elif file_path:
# Copy file content
source_file = Path(file_path)
try:
content = source_file.read_text(encoding="utf-8")
# Create parent directory if needed
CUSTOM_PROMPT_FILE.parent.mkdir(parents=True, exist_ok=True)
CUSTOM_PROMPT_FILE.write_text(content, encoding="utf-8")
console.print(f"Custom prompt copied from {file_path} to {CUSTOM_PROMPT_FILE}")
except OSError as e:
console.print(f"[red]Error reading file {file_path}: {e}[/red]")
raise click.Abort() from e
Copy link

@coderabbitai coderabbitai bot Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Inconsistent behavior between --edit and --file options.

Lines 222-242 show that --edit respects GAC_SYSTEM_PROMPT_PATH (via _get_prompt_file_to_edit), but lines 244-255 show that --file always writes to CUSTOM_PROMPT_FILE, ignoring the environment variable.

This creates confusing behavior:

  1. User sets GAC_SYSTEM_PROMPT_PATH=/custom/path
  2. User runs gac prompt set --file my.txt
  3. File is copied to ~/.config/gac/custom_system_prompt.txt
  4. User runs gac prompt show
  5. Still sees content from /custom/path because env var takes precedence
  6. User's --file operation appears to have no effect

Apply this diff to make --file consistent with --edit:

     elif file_path:
         # Copy file content
         source_file = Path(file_path)
         try:
             content = source_file.read_text(encoding="utf-8")
+            # Determine target file (respects env var like --edit does)
+            env_path = os.getenv("GAC_SYSTEM_PROMPT_PATH")
+            target_file = Path(env_path) if env_path else CUSTOM_PROMPT_FILE
             # Create parent directory if needed
-            CUSTOM_PROMPT_FILE.parent.mkdir(parents=True, exist_ok=True)
-            CUSTOM_PROMPT_FILE.write_text(content, encoding="utf-8")
-            console.print(f"Custom prompt copied from {file_path} to {CUSTOM_PROMPT_FILE}")
+            target_file.parent.mkdir(parents=True, exist_ok=True)
+            target_file.write_text(content, encoding="utf-8")
+            console.print(f"Custom prompt copied from {file_path} to {target_file}")
         except OSError as e:
             console.print(f"[red]Error reading file {file_path}: {e}[/red]")
             raise click.Abort() from e
🤖 Prompt for AI Agents
In src/gac/prompt_cli.py around lines 208 to 255, the --file branch currently
always writes the copied prompt to CUSTOM_PROMPT_FILE which ignores
GAC_SYSTEM_PROMPT_PATH and causes inconsistent behavior versus --edit; change
the --file flow to obtain the target path via _get_prompt_file_to_edit() (same
as the --edit branch), create the parent dir for that target, then write the
source file content to that target (preserving the existing OSError handling),
and keep the console messages and error handling consistent with the --edit
path.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a user sets GAC_SYSTEM_PROMPT_PATH then that takes precedent. We accept that the user knows what they are doing. Should i maybe add a log for this specific case? Telling the user the env is set and therefore --file command will do nothing?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sogoiii, I understand your point about GAC_SYSTEM_PROMPT_PATH taking precedence, but there's an inconsistency issue here:

Current behavior:

  • gac prompt set --editrespects env var, writes to the path specified in GAC_SYSTEM_PROMPT_PATH if set (via _get_prompt_file_to_edit())
  • gac prompt set --fileignores env var, always writes to ~/.config/gac/custom_system_prompt.txt

This means:

export GAC_SYSTEM_PROMPT_PATH=/custom/prompt.txt

# This edits /custom/prompt.txt ✓
gac prompt set --edit

# This writes to ~/.config/gac/custom_system_prompt.txt, which won't be used ✗
gac prompt set --file source.txt

For consistency, I'd recommend:
Make both --edit and --file behave identically - both should respect GAC_SYSTEM_PROMPT_PATH. This way users get predictable behavior regardless of which option they use.

If you prefer keeping the current design:
Then yes, you should definitely add a warning or error (not just a log), something like:

env_path = os.getenv("GAC_SYSTEM_PROMPT_PATH")
if env_path:
    console.print(f"[yellow]Warning: GAC_SYSTEM_PROMPT_PATH is set to {env_path}[/yellow]")
    console.print("[yellow]The file will be copied to ~/.config/gac/custom_system_prompt.txt but won't be used.[/yellow]")
    console.print("[yellow]Consider using --edit instead, or unset GAC_SYSTEM_PROMPT_PATH.[/yellow]")

However, I'd still recommend option 1 (consistency) as the better UX. What do you think?

@cellwebb
Copy link
Owner

@sogoiii the linting failed because of mypy issues which I'm not worried about, but there are some failing tests. I did some reorganization and the default system prompt is now at src/gac/templates/system_prompt.txt which I think is perhaps why the tests are failing.

@sogoiii
Copy link
Contributor Author

sogoiii commented Dec 11, 2025

@cellwebb ok weird. I ran uv run pytest in my local and it passed. Not complaining, I'm just wondering whats different from my local and the build server. Im looking at the error as well.

Add comprehensive CLI commands for managing custom system prompts:
- `gac prompt show`: Display active custom prompt or default
- `gac prompt set --edit`: Edit prompt interactively in terminal
- `gac prompt set --file <path>`: Copy prompt from file
- `gac prompt clear`: Remove custom prompt and revert to default
- `--show-system-prompt`: Global flag to display active prompt

Custom prompts support precedence:
1. GAC_SYSTEM_PROMPT_PATH environment variable (highest)
2. ~/.config/gac/custom_system_prompt.txt (stored config)
3. Built-in default prompt (fallback)

Interactive editor includes:
- Multi-line text editing with prompt_toolkit
- Scrollable interface adapting to terminal size
- Visual hints for save/cancel shortcuts
- Pre-populated with existing prompt content

Includes comprehensive test coverage for all commands,
precedence rules, edge cases, and error handling.
- Remove --show-system-prompt flag and its implementation logic
- Clean up unused imports (Panel from rich.panel, get_active_custom_prompt)
- Eliminate 21 lines of dead code including prompt display functionality
- Simplify CLI interface by removing a maintenance burden feature
@sogoiii
Copy link
Contributor Author

sogoiii commented Dec 11, 2025

@cellwebb ok, my branch has drifted since i made the PR a week ago. I have to rebase against main.

- Update prompt_cli.py to use _load_default_system_template() instead of DEFAULT_SYSTEM_TEMPLATE constant
- Load default system template dynamically when displaying in 'show' command
- Improve code modularity by centralizing template loading logic
@sogoiii sogoiii force-pushed the feat/add-prompt-command branch from 505a87c to 6bc3006 Compare December 11, 2025 05:02
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/gac/prompt_cli.py (1)

245-256: Inconsistent behavior between --edit and --file options remains unaddressed.

This issue was flagged in a previous review but has not been resolved. The --file option always writes to CUSTOM_PROMPT_FILE (line 252), ignoring GAC_SYSTEM_PROMPT_PATH, while --edit respects the environment variable via _get_prompt_file_to_edit() (line 225).

This creates confusing UX:

  1. User sets GAC_SYSTEM_PROMPT_PATH=/custom/path
  2. User runs gac prompt set --file my.txt
  3. File is copied to ~/.config/gac/custom_system_prompt.txt
  4. User runs gac prompt show
  5. Still sees content from /custom/path because env var takes precedence
  6. User's --file operation appears to have no effect

Apply this diff to make --file consistent with --edit:

     elif file_path:
         # Copy file content
         source_file = Path(file_path)
         try:
             content = source_file.read_text(encoding="utf-8")
+            # Determine target file (respects env var like --edit does)
+            target_file, _ = _get_prompt_file_to_edit()
             # Create parent directory if needed
-            CUSTOM_PROMPT_FILE.parent.mkdir(parents=True, exist_ok=True)
-            CUSTOM_PROMPT_FILE.write_text(content, encoding="utf-8")
-            console.print(f"Custom prompt copied from {file_path} to {CUSTOM_PROMPT_FILE}")
+            target_file.parent.mkdir(parents=True, exist_ok=True)
+            target_file.write_text(content, encoding="utf-8")
+            console.print(f"Custom prompt copied from {file_path} to {target_file}")
         except OSError as e:
             console.print(f"[red]Error reading file {file_path}: {e}[/red]")
             raise click.Abort() from e
🧹 Nitpick comments (7)
src/gac/prompt_cli.py (3)

18-46: Consider logging OSError exceptions for better debuggability.

The function silently catches and ignores OSError exceptions when reading prompt files (lines 34-35, 42-43). While the fallback behavior is reasonable, logging these errors would help users diagnose issues like permission problems or I/O failures.

Apply this diff to add logging:

             try:
                 content = env_file.read_text(encoding="utf-8")
                 return content, f"GAC_SYSTEM_PROMPT_PATH={env_path}"
-            except OSError:
-                pass
+            except OSError as e:
+                logger.warning(f"Failed to read prompt from {env_path}: {e}")
+                pass

     # Check stored custom prompt file
     if CUSTOM_PROMPT_FILE.exists():
         try:
             content = CUSTOM_PROMPT_FILE.read_text(encoding="utf-8")
             return content, str(CUSTOM_PROMPT_FILE)
-        except OSError:
-            pass
+        except OSError as e:
+            logger.warning(f"Failed to read prompt from {CUSTOM_PROMPT_FILE}: {e}")
+            pass

77-180: Consider extracting the hint text to a constant.

The hint text appears twice (line 96 and line 126), with slightly different formatting. Extracting to a constant would reduce duplication and make future updates easier.

+    EDITOR_HINT = "Esc+Enter or Ctrl+S to save | Ctrl+C to cancel"
+
     try:
         import shutil

         console.print("\n[bold]Edit your custom system prompt:[/bold]")
-        console.print("[dim]Esc+Enter or Ctrl+S to save | Ctrl+C to cancel[/dim]\n")
+        console.print(f"[dim]{EDITOR_HINT}[/dim]\n")

         # ... rest of code ...

         # Create hint window
         hint_window = Window(
-            content=FormattedTextControl(text=[("class:hint", " Esc+Enter or Ctrl+S to save | Ctrl+C to cancel ")]),
+            content=FormattedTextControl(text=[("class:hint", f" {EDITOR_HINT} ")]),
             height=1,
             dont_extend_height=True,
         )

183-206: Consider logging OSError exceptions for better debuggability.

Similar to get_active_custom_prompt(), this function silently ignores OSError exceptions (lines 195-196, 204-205). Adding logging would help diagnose read failures.

Apply this diff:

         if target_file.exists():
             try:
                 content = target_file.read_text(encoding="utf-8")
-            except OSError:
-                pass
+            except OSError as e:
+                logger.warning(f"Failed to read existing prompt from {target_file}: {e}")
+                pass
         return target_file, content

     # Default to stored config file
     content = ""
     if CUSTOM_PROMPT_FILE.exists():
         try:
             content = CUSTOM_PROMPT_FILE.read_text(encoding="utf-8")
-        except OSError:
-            pass
+        except OSError as e:
+                logger.warning(f"Failed to read existing prompt from {CUSTOM_PROMPT_FILE}: {e}")
+                pass
     return CUSTOM_PROMPT_FILE, content
tests/test_prompt_cli.py (4)

121-190: Consider adding a test for mutual exclusivity of --edit and --file.

The TestPromptSetEdit class tests the case where neither option is provided (lines 167-172), but doesn't test the case where both --edit and --file are provided together. This validation logic exists in src/gac/prompt_cli.py lines 215-217.

Add this test to the TestPromptSetEdit class:

def test_prompt_set_mutually_exclusive(self, runner, mock_paths, tmp_path, monkeypatch):
    """Error if both --edit and --file provided."""
    monkeypatch.delenv("GAC_SYSTEM_PROMPT_PATH", raising=False)
    source_file = tmp_path / "source.txt"
    source_file.write_text("Test content", encoding="utf-8")
    
    result = runner.invoke(prompt, ["set", "--edit", "--file", str(source_file)])
    assert result.exit_code != 0
    assert "mutually exclusive" in result.output.lower()

121-190: Consider adding a test for empty prompt handling in --edit mode.

The set command handles the case where the interactive editor returns an empty string (lines 237-239 in src/gac/prompt_cli.py), but this scenario isn't tested. Adding this test would improve coverage of edge cases.

Add this test to the TestPromptSetEdit class:

def test_prompt_set_edit_empty_result(self, runner, mock_paths, monkeypatch):
    """Mock _edit_text_interactive() returning empty string, verify not saved."""
    monkeypatch.delenv("GAC_SYSTEM_PROMPT_PATH", raising=False)
    
    with patch("gac.prompt_cli._edit_text_interactive") as mock_edit:
        mock_edit.return_value = ""  # Empty string
        result = runner.invoke(prompt, ["set", "--edit"])
    
    assert result.exit_code == 0
    assert not mock_paths["custom_prompt"].exists()
    assert "empty" in result.output.lower()

85-119: Add a test for --file with GAC_SYSTEM_PROMPT_PATH set after fixing the consistency issue.

Once the inconsistency between --edit and --file (flagged in the main code review) is fixed, add a test similar to test_prompt_set_edit_env_var_file (lines 174-189) to verify that --file also respects the environment variable.

After applying the fix to src/gac/prompt_cli.py, add this test:

def test_prompt_set_file_env_var_file(self, runner, mock_paths, tmp_path, monkeypatch):
    """When GAC_SYSTEM_PROMPT_PATH is set, --file copies to that file."""
    env_prompt_file = tmp_path / "env_prompt.txt"
    monkeypatch.setenv("GAC_SYSTEM_PROMPT_PATH", str(env_prompt_file))
    
    source_file = tmp_path / "source.txt"
    source_file.write_text("Source content", encoding="utf-8")
    
    result = runner.invoke(prompt, ["set", "--file", str(source_file)])
    assert result.exit_code == 0
    # Env file should be updated, not the default stored file
    assert env_prompt_file.read_text(encoding="utf-8") == "Source content"
    assert not mock_paths["custom_prompt"].exists()

192-213: Add a test documenting clear behavior when GAC_SYSTEM_PROMPT_PATH is set.

To document the expected behavior flagged in the main code review, add a test that verifies what happens when clear is called while GAC_SYSTEM_PROMPT_PATH is set.

Add this test to TestPromptClear:

def test_prompt_clear_with_env_var(self, runner, mock_paths, tmp_path, monkeypatch):
    """Clear only deletes stored file, not env var file."""
    # Create both env var file and stored file
    env_prompt_file = tmp_path / "env_prompt.txt"
    env_prompt_file.write_text("Env content", encoding="utf-8")
    monkeypatch.setenv("GAC_SYSTEM_PROMPT_PATH", str(env_prompt_file))
    
    mock_paths["config_dir"].mkdir(parents=True, exist_ok=True)
    mock_paths["custom_prompt"].write_text("Stored content", encoding="utf-8")
    
    result = runner.invoke(prompt, ["clear"])
    assert result.exit_code == 0
    # Stored file should be deleted
    assert not mock_paths["custom_prompt"].exists()
    # Env var file should remain
    assert env_prompt_file.exists()
    assert env_prompt_file.read_text(encoding="utf-8") == "Env content"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 505a87c and 6bc3006.

📒 Files selected for processing (3)
  • src/gac/cli.py (2 hunks)
  • src/gac/prompt_cli.py (1 hunks)
  • tests/test_prompt_cli.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Use Ruff formatter with 120-character line limit

Files:

  • tests/test_prompt_cli.py
  • src/gac/cli.py
  • src/gac/prompt_cli.py
src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.py: Use type annotations throughout the codebase
Use snake_case for modules and functions, CapWords for classes

Files:

  • src/gac/cli.py
  • src/gac/prompt_cli.py
🧬 Code graph analysis (2)
src/gac/cli.py (1)
src/gac/prompt_cli.py (1)
  • prompt (50-52)
src/gac/prompt_cli.py (1)
src/gac/prompt.py (1)
  • _load_default_system_template (22-24)
🔇 Additional comments (2)
src/gac/cli.py (1)

27-27: LGTM! Clean integration of the prompt CLI.

The import and command registration follow the existing patterns for other CLI subcommands. The alphabetical ordering and consistent structure maintain code organization.

Also applies to: 210-210

tests/test_prompt_cli.py (1)

1-265: Well-structured and comprehensive test suite.

The tests are well-organized into logical test classes, use appropriate fixtures and mocking strategies, and cover the main functionality paths. The suggestions above are for enhancing coverage of edge cases rather than addressing critical gaps.

Comment on lines +259 to +266
@prompt.command()
def clear() -> None:
"""Clear custom system prompt (revert to default)."""
if CUSTOM_PROMPT_FILE.exists():
CUSTOM_PROMPT_FILE.unlink()
console.print(f"Custom prompt deleted: {CUSTOM_PROMPT_FILE}")
else:
console.print("No custom prompt file to delete.")
Copy link

@coderabbitai coderabbitai bot Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, explore the repository structure
git ls-files src/ | head -20

Repository: cellwebb/gac

Length of output: 527


🏁 Script executed:

# Search for tests related to the clear command
fd -e py -path "*/test*" | xargs rg -l "clear" 2>/dev/null | head -10

Repository: cellwebb/gac

Length of output: 461


🏁 Script executed:

# Check for documentation about GAC_SYSTEM_PROMPT_PATH
rg -n "GAC_SYSTEM_PROMPT_PATH" --type=md,txt --type=py -B2 -A2

Repository: cellwebb/gac

Length of output: 86


🏁 Script executed:

# Look for the implementation of get_custom_prompt and related functions
rg -n "def get_custom_prompt|def show|GAC_SYSTEM_PROMPT_PATH" src/ -B1 -A3

Repository: cellwebb/gac

Length of output: 2705


🏁 Script executed:

# Check the clear command implementation and surrounding code
sed -n '245,280p' src/gac/prompt_cli.py

Repository: cellwebb/gac

Length of output: 952


🏁 Script executed:

# View the full set command implementation
sed -n '175,210p' src/gac/prompt_cli.py

Repository: cellwebb/gac

Length of output: 1173


🏁 Script executed:

# Check test file for clear and set command tests
cat tests/test_prompt_cli.py

Repository: cellwebb/gac

Length of output: 12591


🏁 Script executed:

# Search for any documentation or comments about the clear command and env var interaction
rg -n "clear.*env|GAC_SYSTEM_PROMPT_PATH.*clear" src/ -B2 -A2

Repository: cellwebb/gac

Length of output: 421


🏁 Script executed:

# Check if there's any documentation file that mentions this behavior
fd -type f \( -name "*.md" -o -name "*.rst" -o -name "*.txt" \) | xargs rg -l "GAC_SYSTEM_PROMPT_PATH\|clear.*prompt" 2>/dev/null

Repository: cellwebb/gac

Length of output: 228


Inconsistent behavior between set and clear commands with GAC_SYSTEM_PROMPT_PATH.

The set command (in --edit mode) respects GAC_SYSTEM_PROMPT_PATH and modifies the env-var-specified file if set, but the clear command ignores the environment variable and only deletes CUSTOM_PROMPT_FILE. This means running gac prompt clear followed by gac prompt show will still display the custom prompt from GAC_SYSTEM_PROMPT_PATH if it's set.

This inconsistency is not covered by tests. Consider either:

  1. Making clear also handle the env-var-specified file (delete or unset it)
  2. Documenting this limitation in the command help text
  3. Adding a warning if GAC_SYSTEM_PROMPT_PATH is set when running clear
🤖 Prompt for AI Agents
In src/gac/prompt_cli.py around lines 259-266, the clear command currently only
unlinks CUSTOM_PROMPT_FILE and ignores GAC_SYSTEM_PROMPT_PATH; update it to
first check for an environment-specified path (GAC_SYSTEM_PROMPT_PATH) and use
that as the target file if present, falling back to CUSTOM_PROMPT_FILE
otherwise; attempt to unlink the chosen path, print a success message if deleted
or a "no custom prompt file" message if missing, and catch/log any filesystem
errors so behavior matches the set/edit command and avoids leaving an
env-controlled prompt behind.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again the GAC_SYSTEM_PROMPT_PATH takes precedent. The env var is the original way of handling custom prompts. The new command is mean to handle new users who wont use the env var.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sogoiii, thanks for the clarification! I understand now – the env var is for power users who manage their own files, while the CLI commands are for casual users who don't use environment variables. That design rationale makes sense.

One small UX consideration: since set --edit does respect GAC_SYSTEM_PROMPT_PATH and will edit that file when set (via _get_prompt_file_to_edit()), it might be helpful to show users which file they're editing. For example:

[bold]Editing: /path/from/env/var[/bold]
Edit your custom system prompt:
[dim]Esc+Enter or Ctrl+S to save | Ctrl+C to cancel[/dim]

This would make it clear that they're editing the env-var file (which won't be affected by clear) vs the CLI-managed file. Just a thought for future improvement – not critical for this PR.


✏️ Learnings added
Learnt from: sogoiii
Repo: cellwebb/gac PR: 63
File: src/gac/prompt_cli.py:259-266
Timestamp: 2025-12-11T05:16:13.792Z
Learning: In gac CLI, GAC_SYSTEM_PROMPT_PATH environment variable is the original method for setting custom prompts and is intended for power users who manage their own prompt files. The new `gac prompt` CLI commands (set, clear, show) are designed for casual users who don't use environment variables. The clear command intentionally only deletes the CLI-managed file (~/.config/gac/custom_system_prompt.txt) and does not remove env-var-specified files, as those users are expected to manage their own files.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@cellwebb cellwebb merged commit 5a1860a into cellwebb:main Dec 11, 2025
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants