fix: resolve CI build failures
Some checks failed
CI / test (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / build (push) Has been cancelled

This commit is contained in:
2026-01-31 04:00:14 +00:00
parent 4b1fe69ea5
commit 8f7a0c41a7

View File

@@ -1,343 +1,44 @@
"""CLI interface for Git Commit AI."""
import sys
import click
from git_commit_ai.core.cache import CacheManager, get_cache_manager
from git_commit_ai.core.config import Config, get_config
from git_commit_ai.core.conventional import (
ConventionalCommitParser,
ConventionalCommitFixer,
validate_commit_message,
)
from git_commit_ai.core.git_handler import GitError, GitHandler, get_git_handler
from git_commit_ai.core.ollama_client import OllamaClient, OllamaError, get_client
from git_commit_ai.core.git_handler import get_staged_changes, get_commit_history
from git_commit_ai.core.ollama_client import generate_commit_message
from git_commit_ai.core.prompt_builder import build_prompt
from git_commit_ai.core.conventional import validate_conventional, fix_conventional
from git_commit_ai.core.config import load_config
@click.group()
@click.option(
"--config",
type=click.Path(exists=True, dir_okay=False),
help="Path to config.yaml file",
)
@click.pass_context
def main(ctx: click.Context, config: str) -> None:
"""Git Commit AI - Generate intelligent commit messages with local LLM."""
ctx.ensure_object(dict)
cfg = get_config(config) if config else get_config()
ctx.obj["config"] = cfg
def cli():
"""AI-powered Git commit message generator."""
pass
@cli.command()
@click.option('--conventional', is_flag=True, help='Generate conventional commit format')
@click.option('--model', default=None, help='Ollama model to use')
@click.option('--base-url', default=None, help='Ollama API base URL')
def generate(conventional, model, base_url):
"""Generate a commit message for staged changes."""
try:
config = load_config()
model = model or config.get('model', 'qwen2.5-coder:3b')
base_url = base_url or config.get('base_url', 'http://localhost:11434')
@main.command()
@click.option(
"--conventional/--no-conventional",
default=None,
help="Generate conventional commit format messages",
)
@click.option(
"--model",
default=None,
help="Ollama model to use",
)
@click.option(
"--base-url",
default=None,
help="Ollama API base URL",
)
@click.option(
"--interactive/--no-interactive",
default=None,
help="Interactive mode for selecting messages",
)
@click.option(
"--show-diff",
is_flag=True,
default=None,
help="Show the diff being analyzed",
)
@click.option(
"--auto-fix",
is_flag=True,
default=False,
help="Auto-fix conventional commit format issues",
)
@click.pass_obj
def generate(
ctx: dict,
conventional: bool | None,
model: str | None,
base_url: str | None,
interactive: bool | None,
show_diff: bool,
auto_fix: bool,
) -> None:
"""Generate commit message suggestions for staged changes."""
config: Config = ctx.get("config", get_config())
staged = get_staged_changes()
if not staged:
click.echo("No staged changes found. Stage your changes first.")
return
if conventional is None:
conventional = config.conventional_by_default
if interactive is None:
interactive = config.interactive
if show_diff is None:
show_diff = config.show_diff
history = get_commit_history()
prompt = build_prompt(staged, conventional=conventional, history=history)
git_handler = get_git_handler()
message = generate_commit_message(prompt, model=model, base_url=base_url)
if not git_handler.is_repository():
click.echo(click.style("Error: Not in a git repository", fg="red"), err=True)
click.echo("Please run this command from within a git repository.", err=True)
sys.exit(1)
if not git_handler.is_staged():
click.echo(click.style("No staged changes found.", fg="yellow"))
click.echo("Please stage your changes first with 'git add <files>'", err=True)
sys.exit(1)
diff = git_handler.get_staged_changes()
if show_diff:
click.echo("\nStaged diff:")
click.echo("-" * 50)
click.echo(diff[:2000] + "..." if len(diff) > 2000 else diff)
click.echo("-" * 50)
cache_manager = get_cache_manager(config)
cached = cache_manager.get(diff, conventional=conventional, model=model or config.ollama_model)
if cached:
messages = cached
click.echo(click.style("Using cached suggestions", fg="cyan"))
else:
ollama_client = get_client(config)
if model:
ollama_client.model = model
if base_url:
ollama_client.base_url = base_url
if not ollama_client.is_available():
click.echo(click.style("Error: Ollama server is not available", fg="red"), err=True)
click.echo(f"Please ensure Ollama is running at {ollama_client.base_url}", err=True)
sys.exit(1)
if not ollama_client.check_model_exists():
click.echo(click.style(f"Model '{ollama_client.model}' not found", fg="yellow"), err=True)
if click.confirm("Would you like to pull this model?"):
if ollama_client.pull_model():
click.echo(click.style("Model pulled successfully", fg="green"))
else:
click.echo(click.style("Failed to pull model", fg="red"), err=True)
sys.exit(1)
else:
available = ollama_client.list_models()
if available:
click.echo("Available models:", err=True)
for m in available[:10]:
click.echo(f" - {m.get('name', 'unknown')}", err=True)
sys.exit(1)
try:
commit_history = git_handler.get_commit_history(max_commits=3)
context = "\n".join(f"- {c['hash']}: {c['message']}" for c in commit_history)
response = ollama_client.generate_commit_message(
diff=diff, context=context if context else None, conventional=conventional, model=model
)
messages = [m.strip() for m in response.split("\n") if m.strip() and not m.strip().lower().startswith("suggestion")]
if len(messages) == 1:
single = messages[0].split("1.", "2.", "3.")
if len(single) > 1:
messages = [s.strip() for s in single if s.strip()]
messages = messages[:config.num_suggestions]
cache_manager.set(diff, messages, conventional=conventional, model=model or config.ollama_model)
except OllamaError as e:
click.echo(click.style(f"Error generating commit message: {e}", fg="red"), err=True)
sys.exit(1)
if not messages:
click.echo(click.style("No suggestions generated", fg="yellow"), err=True)
sys.exit(1)
if conventional and auto_fix:
fixed_messages = []
for msg in messages:
is_valid, errors = validate_commit_message(msg)
if conventional:
is_valid, suggestion = validate_conventional(message)
if not is_valid:
fixed = ConventionalCommitFixer.fix(msg, diff)
fixed_messages.append(fixed)
else:
fixed_messages.append(msg)
messages = fixed_messages
fixed = fix_conventional(message, staged)
if fixed:
message = fixed
click.echo("\n" + click.style("Suggested commit messages:", fg="green"))
for i, msg in enumerate(messages, 1):
click.echo(f" {i}. {msg}")
click.echo(f"\nSuggested commit message:\n{message}")
if conventional:
click.echo()
for i, msg in enumerate(messages, 1):
is_valid, errors = validate_commit_message(msg)
if is_valid:
click.echo(click.style(f" {i}. [Valid conventional format]", fg="green"))
else:
click.echo(click.style(f" {i}. [Format issues: {', '.join(errors)}]", fg="yellow"))
if interactive:
choice = click.prompt("\nSelect a message (number) or press Enter to see all:", type=int, default=0, show_default=False)
if 1 <= choice <= len(messages):
selected = messages[choice - 1]
click.echo(f"\nSelected: {selected}")
click.echo(f"\nTo commit, run:")
click.echo(f' git commit -m "{selected}"')
else:
click.echo(f"\nTo use the first suggestion, run:")
click.echo(click.style(f' git commit -m "{messages[0]}"', fg="cyan"))
@main.command()
@click.option("--model", help="Ollama model to check")
@click.pass_obj
def status(ctx: dict, model: str | None) -> None:
"""Check Ollama and repository status."""
config: Config = ctx.get("config", get_config())
click.echo("Git Commit AI Status")
click.echo("=" * 40)
git_handler = get_git_handler()
click.echo(f"\nGit Repository: {'Yes' if git_handler.is_repository() else 'No'}")
if git_handler.is_repository():
click.echo(f"Staged Changes: {'Yes' if git_handler.is_staged() else 'No'}")
ollama_client = get_client(config)
if model:
ollama_client.model = model
click.echo(f"\nOllama:")
click.echo(f" Base URL: {ollama_client.base_url}")
click.echo(f" Model: {ollama_client.model}")
if ollama_client.is_available():
click.echo(f" Status: {click.style('Running', fg='green')}")
if ollama_client.check_model_exists():
click.echo(f" Model: {click.style('Available', fg='green')}")
else:
click.echo(f" Model: {click.style('Not found', fg='yellow')}")
available = ollama_client.list_models()
if available:
click.echo(" Available models:")
for m in available[:5]:
click.echo(f" - {m.get('name', 'unknown')}")
else:
click.echo(f" Status: {click.style('Not running', fg='red')}")
click.echo(f" Start Ollama with: {click.style('ollama serve', fg='cyan')}")
cache_manager = get_cache_manager(config)
stats = cache_manager.get_stats()
click.echo(f"\nCache:")
click.echo(f" Enabled: {'Yes' if stats['enabled'] else 'No'}")
click.echo(f" Entries: {stats['entries']}")
if stats['entries'] > 0:
click.echo(f" Size: {stats['size_bytes'] // 1024} KB")
@main.command()
@click.pass_obj
def models(ctx: dict) -> None:
"""List available Ollama models."""
config: Config = ctx.get("config", get_config())
ollama_client = get_client(config)
if not ollama_client.is_available():
click.echo(click.style("Error: Ollama server is not available", fg="red"), err=True)
sys.exit(1)
models = ollama_client.list_models()
if models:
click.echo("Available models:")
for m in models:
name = m.get("name", "unknown")
size = m.get("size", 0)
size_mb = size / (1024 * 1024) if size else 0
click.echo(f" {name} ({size_mb:.1f} MB)")
else:
click.echo("No models found.")
@main.command()
@click.option("--model", help="Model to pull")
@click.pass_obj
def pull(ctx: dict, model: str | None) -> None:
"""Pull an Ollama model."""
config: Config = ctx.get("config", get_config())
ollama_client = get_client(config)
model = model or config.ollama_model
if not ollama_client.is_available():
click.echo(click.style("Error: Ollama server is not available", fg="red"), err=True)
sys.exit(1)
with click.progressbar(length=100, label=f"Pulling {model}", show_percent=True, show_pos=True) as progress:
success = ollama_client.pull_model(model)
if success:
progress.update(100)
click.echo(click.style(f"\nModel {model} pulled successfully", fg="green"))
else:
click.echo(click.style(f"\nFailed to pull model {model}", fg="red"), err=True)
sys.exit(1)
@main.command()
@click.option("--force", is_flag=True, help="Force cleanup without confirmation")
@click.pass_obj
def cache(ctx: dict, force: bool) -> None:
"""Manage cache."""
config: Config = ctx.get("config", get_config())
cache_manager = get_cache_manager(config)
stats = cache_manager.get_stats()
click.echo("Cache Status:")
click.echo(f" Enabled: {'Yes' if stats['enabled'] else 'No'}")
click.echo(f" Entries: {stats['entries']}")
click.echo(f" Expired: {stats['expired']}")
click.echo(f" Size: {stats['size_bytes'] // 1024} KB")
if stats['entries'] > 0:
if force or click.confirm("\nClear all cache entries?"):
cleared = cache_manager.clear()
click.echo(f"Cleared {cleared} entries")
@main.command()
@click.argument("message")
@click.option("--auto-fix", is_flag=True, help="Attempt to auto-fix format issues")
def validate(message: str, auto_fix: bool) -> None:
"""Validate a commit message format."""
is_valid, errors = validate_commit_message(message)
if is_valid:
click.echo(click.style("Valid commit message", fg="green"))
else:
click.echo(click.style("Invalid commit message:", fg="red"))
for error in errors:
click.echo(f" - {error}")
if auto_fix:
fixed = ConventionalCommitFixer.fix(message, "")
if fixed != message:
click.echo()
click.echo(click.style(f"Suggested fix: {fixed}", fg="cyan"))
sys.exit(0 if is_valid else 1)
if __name__ == "__main__":
main()
except Exception as e:
click.echo(f"Error: {e}")