How it Works#

This page explains the “invisible” logic behind gmuse—from how it extracts data from your repository to how it communicates with Large Language Models.

It is for users who want to understand what gmuse sends to an LLM, how prompts are assembled, and how generated messages are validated.

The Execution Pipeline#

When you run gmuse msg, the following sequence occurs:

  1. Extraction: gmuse queries your local Git repository to extract the currently staged diff and the last few commit messages for style reference. See What is sent to the provider below for details about what is included and provider handling.

  2. Context Assembly: It merges your diff with project-specific instructions from a .gmuse file (if present) and your optional --hint.

  3. Prompting: A two-part prompt (System + User) is constructed and sent to your chosen LLM provider via LiteLLM, which provides a unified interface to many providers. Prompts are constructed locally and sent directly to the configured provider (gmuse does not proxy prompts through a gmuse-hosted server); see What is sent to the provider and Privacy & Security for provider retention details.

  4. Validation: The generated message is checked against the requested format and length constraints. If validation fails, gmuse will display a clear error and prevent the commit from proceeding; common error examples include:

    • “Generated message is empty”

    • “Message too long: 1200 characters (max 1000)”

    • “Message does not match Conventional Commits format” Validation rules include format-specific checks (Conventional, Gitmoji) and a maximum message length (1000 characters). Choose the expected format using --format or in your configuration.

Output & clipboard behavior#

By default gmuse prints the generated message to stdout; copying to the system clipboard is opt‑in. Pass --copy or set copy_to_clipboard = true in your configuration to enable copying. Clipboard operations use pyperclip from the optional gmuse[clipboard] extra; the CLI will warn if the dependency or a system clipboard utility (e.g., pbcopy, xclip, wl-copy) is missing. --dry-run never copies output; shell completions insert the suggestion into git commit -m rather than copying it to the clipboard.

What is sent to the provider#

When gmuse calls the LLM provider it sends the assembled prompts (System + User). The user prompt typically includes:

  • Staged diff (raw diff; may be truncated to fit token limits)

  • A short staged changes summary (files changed, lines added/removed)

  • Recent commit messages for style reference (default 5, configurable via --history-depth)

  • Repository instructions from a .gmuse file (if present)

  • The CLI --hint value (if provided)

  • Format/task instructions and any examples or templates

Note: gmuse does not send unstaged changes, your API keys, files outside the repository, or environment variables — see What is NEVER sent: for full details on what is never sent and provider retention policies.

Truncation Policy#

Large diffs are automatically truncated to fit within token limits (default: 20,000 bytes ≈ 5,000 tokens). This limit is configurable via max_diff_bytes:

# ~/.config/gmuse/config.toml
max_diff_bytes = 50000  # Allow larger diffs

Or via CLI flag:

$ gmuse msg --max-diff-bytes 30000

Note: Token estimation uses a simple heuristic of ~4 characters per token (also configurable via chars_per_token), which is approximate for GPT-style models. Actual token counts vary by model and tokenizer.

How truncation works:

  1. File headers (diff --git, ---, +++) are always preserved for context.

  2. Content lines are kept until the byte limit is reached.

  3. When truncated, a marker ... (diff truncated for brevity) is appended.

  4. The TRUNCATED: true flag appears in --dry-run output when truncation occurred.

If you see truncation warnings, consider:

  • Splitting large changes into smaller, focused commits

  • Increasing max_diff_bytes if your provider supports larger contexts

  • Reviewing the full diff separately before committing

Intelligence & Context#

The “magic” of gmuse isn’t just sending a diff to an AI; it’s about providing the right context so the AI sounds like a contributor to your specific project.

Style Matching (Few-Shot Learning)#

By default, gmuse pulls the last 5 commit messages from your current branch and explicitly includes them in the prompt as style examples. The LLM is given these concrete examples so it can:

  • Identify preferred terminology.

  • Match the verbosity of existing messages.

  • Reproduce prefix or tagging conventions (like PROJ-123:) already in use.

Note: The model doesn’t magically “infer” your style—it’s given your recent commits as explicit examples via build_context() in the prompt assembly.

Repository Instructions#

If a .gmuse file is found, its contents are injected directly into the prompt. This is perfect for team-wide rules like “Always reference a Jira ticket” or “Keep descriptions under 50 characters.” See the Configuration Guide for details.

Prompting Strategy#

We use a layered prompting approach to ensure consistent, high-quality output.

1. The System Persona#

The LLM is initialized with a “System Prompt” that defines its identity as an expert developer. It is given strict guidelines:

  • Focus on WHAT changed and WHY (not just a list of files).

  • Use the imperative mood (e.g., “Add feature” instead of “Added feature”).

  • Prioritize clarity over cleverness.

2. The Task Template#

Depending on your chosen format, a specific task template is appended to the prompt:

  • Conventional: Defines the type(scope): description structure and provides allowed types (feat, fix, etc.).

  • Gitmoji: Provides a mapping of emojis to intents and ensures the emoji prefix is used.

  • Freeform: Instructs the model to use natural, concise language.

Debugging & dry-run#

Debugging & Troubleshooting: For practical debugging steps (dry-run usage, provider diagnostics, sample outputs), see the Troubleshooting guide.

Generation Parameters#

gmuse uses several configurable parameters to control how the LLM generates commit messages:

  • temperature (default: 0.7): Controls randomness/creativity (0.0 = deterministic, 2.0 = very creative)

  • max_tokens (default: 500): Maximum number of tokens in the generated response

  • max_diff_bytes (default: 20000): Maximum diff size sent to the LLM

  • max_message_length (default: 1000): Maximum commit message length (validation)

  • chars_per_token (default: 4): Token estimation heuristic

All of these are configurable via CLI flags, environment variables, or config file. See the Configuration Reference for complete details and usage examples.

Quick examples:

# More deterministic messages (CI/CD)
$ gmuse msg --temperature 0.0

# Shorter messages
$ gmuse msg --max-tokens 200

# Larger diff context
$ gmuse msg --max-diff-bytes 50000

For persistent settings, add them to your config file:

temperature = 0.3
max_tokens = 200
max_diff_bytes = 30000

Validation Rules#

For exact validation rules, canonical error messages, examples, and minimal failing/passing examples, see the Validation Rules.

Error Handling#

For common provider errors, sample messages, and recovery steps, see Error handling.

Example: Tracing a Generation#

To see how these layers come together, imagine you are fixing a security vulnerability.

1. The Ingredients#

  • Staged Diff:

    - cursor.execute("SELECT * FROM users WHERE id = " + user_id)
    + cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
    
  • Style Reference: gmuse scans your history and sees messages like

    PROJ-123: Add login endpoint
    PROJ-456: Fix logout redirect
    
  • User Hint: You run gmuse msg --hint "PROJ-789".

  • Repository instructions: A .gmuse file (e.g., Always reference a Jira ticket).

  • Task template: The format’s task prompt (Conventional/Gitmoji/Freeform) — selected via --format or your config — is automatically appended to the user prompt by gmuse and enforces structure and constraints (for example, Conventional requires type(scope): description and a concise one-line message). You do not need to provide a separate template.

2. The Context Assembly#

gmuse constructs a structured prompt using three distinct layers:

  1. Instructions (System Prompt): The “Expert Developer” persona and guidelines (use imperative mood, focus on WHAT and WHY, etc.). See SYSTEM_PROMPT in src/gmuse/prompts.py.

  2. Evidence (Context in User Prompt): The LLM is explicitly given your recent commits as style examples, your --hint, and any repository instructions. It doesn’t infer patterns from nothing—it has concrete examples like PROJ-123: Add login endpoint to reproduce.

  3. Task Template: The format-specific instructions (Conventional/Gitmoji/Freeform) are appended to the user prompt. For Conventional, this includes the allowed types (feat, fix, etc.) and format rules.

This assembly happens in build_prompt() (see src/gmuse/prompts.py), which returns the (system_prompt, user_prompt) tuple sent to the LLM.

3. The Assembled Prompts#

For this example, the assembled prompts would look like:

System Prompt:

You are an expert commit message generator. Your role is to analyze code changes
and produce clear, informative commit messages...

User Prompt:

Recent commits for style reference:
- PROJ-456: Fix logout redirect
- PROJ-123: Add login endpoint

Repository instructions:
Always reference a Jira ticket in the format PROJ-NNN.

User hint: PROJ-789

Staged changes summary:
- Files changed: 1
- Lines added: 1
- Lines removed: 1

Diff:
diff --git a/database.py b/database.py
...
- cursor.execute("SELECT * FROM users WHERE id = " + user_id)
+ cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))

Generate a commit message following Conventional Commits specification.
Format: type(scope): description
...

4. The LLM Response#

The LLM combines the technical change (parameterized query), the style examples (PROJ-NNN prefix pattern), the repository instruction (“Always reference a Jira ticket”), and your hint (PROJ-789) to produce:

fix(database): PROJ-789: use parameterized query to prevent SQL injection

5. Validation#

Before returning the message, gmuse runs validate_message() (see src/gmuse/prompts.py):

  • Not empty: Message has content

  • Length check: 68 characters < 1000 max

  • Conventional format: Matches ^(feat|fix|docs|...)\(.*\)?: .+

Validation passes, and the message is displayed (or copied to clipboard if --copy was used).

See Also#