feat: add colon-separated argument parsing for repomix options

Amolith created

- Implement parse_fragment_string() to extract URL and arguments from colon-separated format
- Add build_repomix_command() to construct repomix command with parsed arguments
- Support all major repomix flags: compress, include, ignore, style, remove-comments, etc.
- Handle complex URL formats (https://, ssh://, git@) with proper colon parsing
- Add comprehensive test coverage for argument parsing and command building
- Update documentation with usage examples and supported arguments

Examples:
- repomix:https://git.sr.ht/~amolith/willow:compress
- repomix:https://git.sr.ht/~amolith/willow:include=*.py,*.md:ignore=tests/
- repomix:git@github.com:user/repo.git:compress:remove-comments

Change summary

CLAUDE.md                       |  77 ++++
llm_fragments_repomix.py        | 184 +++++++++++
tests/test_fragments_repomix.py | 540 +++++++++++++++++++++++++++++++++++
3 files changed, 792 insertions(+), 9 deletions(-)

Detailed changes

CLAUDE.md 🔗

@@ -0,0 +1,77 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Architecture
+
+This is a Python plugin for the LLM CLI tool that adds a fragment loader for repository contents using Repomix. The plugin registers a single fragment loader that:
+
+1. Clones a git repository to a temporary directory
+2. Runs repomix on the cloned repository to generate consolidated output
+3. Returns the repomix output as a single LLM fragment
+4. Cleans up the temporary directory
+
+**Core Components:**
+- `llm_fragments_repomix.py`: Main plugin file with the fragment loader implementation
+- `pyproject.toml`: Python packaging configuration with entry points for LLM
+
+## Development Commands
+
+**Build and Install:**
+```bash
+pip install -e .
+```
+
+**Testing:**
+```bash
+pytest tests/  # Run all tests
+pytest tests/test_fragments_repomix.py  # Run specific test file
+```
+
+**Package Building:**
+```bash
+python -m build
+```
+
+## Dependencies
+
+- **Runtime:** LLM framework, git command, repomix command
+- **Development:** pytest (optional)
+- **External:** Requires `repomix` to be installed globally via npm
+
+## Usage Pattern
+
+The plugin is used with LLM's fragment system:
+```bash
+llm -f repomix:https://git.sr.ht/~amolith/willow "Tell me about this project"
+```
+
+Supports https://, ssh://, and git@ repository URLs.
+
+### Arguments
+
+You can pass arguments to repomix using colon-separated syntax:
+
+```bash
+# Basic compression
+llm -f repomix:https://git.sr.ht/~amolith/willow:compress "Tell me about this project"
+
+# Include specific file patterns
+llm -f repomix:https://git.sr.ht/~amolith/willow:include=*.py,*.md "Analyze the Python and documentation files"
+
+# Multiple arguments
+llm -f repomix:https://git.sr.ht/~amolith/willow:compress:include=*.py:ignore=tests/ "Analyze Python files but skip tests"
+```
+
+Supported arguments:
+- `compress` - Compress output to reduce token count
+- `include=pattern` - Include files matching pattern (comma-separated)
+- `ignore=pattern` - Ignore files matching pattern (comma-separated)
+- `style=type` - Output style (xml, markdown, plain)
+- `remove-comments` - Remove comments from code
+- `remove-empty-lines` - Remove empty lines
+- `output-show-line-numbers` - Add line numbers to output
+- `no-file-summary` - Disable file summary section
+- `no-directory-structure` - Disable directory structure section
+
+For a complete list of supported arguments, refer to the [Repomix documentation](https://github.com/yamadashy/repomix).

llm_fragments_repomix.py 🔗

@@ -1,4 +1,4 @@
-from typing import List
+from typing import List, Dict, Tuple
 import llm
 import os
 import pathlib
@@ -7,6 +7,162 @@ import tempfile
 import shutil
 
 
+def parse_fragment_string(fragment_string: str) -> Tuple[str, Dict[str, str]]:
+    """
+    Parse a fragment string into URL and arguments
+    
+    Format: url:arg1:arg2=value:arg3
+    Returns: (url, {arg1: True, arg2: "value", arg3: True})
+    """
+    # Define known repomix flags to detect where arguments start
+    known_flags = {
+        "compress", "remove-comments", "remove-empty-lines", 
+        "output-show-line-numbers", "no-file-summary", 
+        "no-directory-structure", "no-files", 
+        "include-empty-directories", "no-git-sort-by-changes",
+        "include-diffs", "no-gitignore", "no-default-patterns",
+        "no-security-check", "verbose", "quiet", "style"
+    }
+    
+    # Split on colons to get all parts
+    parts = fragment_string.split(":")
+    if len(parts) < 1:
+        raise ValueError("Invalid fragment string format")
+    
+    # Handle different URL formats
+    if fragment_string.startswith("https://"):
+        # Find where arguments start
+        # parts = ['https', '//github.com/user/repo', 'compress']
+        arg_start_idx = None
+        for i, part in enumerate(parts):
+            if i <= 1:  # Skip protocol parts (https, //domain/path)
+                continue
+            if "=" in part or part in known_flags:
+                arg_start_idx = i
+                break
+        
+        if arg_start_idx is None:
+            url = fragment_string
+            arg_parts = []
+        else:
+            # Reconstruct URL by joining the parts before arguments
+            url_parts = parts[:arg_start_idx]
+            url = ":".join(url_parts)
+            arg_parts = parts[arg_start_idx:]
+            
+    elif fragment_string.startswith("ssh://"):
+        # Similar logic for SSH URLs
+        # parts = ['ssh', '//git@github.com', 'user/repo.git', 'compress']
+        arg_start_idx = None
+        for i, part in enumerate(parts):
+            if i <= 1:  # Skip protocol parts (ssh, //domain)
+                continue
+            if "=" in part or part in known_flags:
+                arg_start_idx = i
+                break
+        
+        if arg_start_idx is None:
+            url = fragment_string
+            arg_parts = []
+        else:
+            url_parts = parts[:arg_start_idx]
+            url = ":".join(url_parts)
+            arg_parts = parts[arg_start_idx:]
+            
+    elif fragment_string.startswith("git@"):
+        # git@host:path format - need to be careful about colons
+        # Look for arguments after the repo path
+        arg_start_idx = None
+        
+        # Skip the first colon (after hostname) when looking for arguments
+        for i, part in enumerate(parts):
+            if i <= 1:  # Skip git@host and first path part
+                continue
+            if "=" in part or part in known_flags:
+                arg_start_idx = i
+                break
+        
+        if arg_start_idx is None:
+            url = fragment_string
+            arg_parts = []
+        else:
+            url_parts = parts[:arg_start_idx]
+            url = ":".join(url_parts)
+            arg_parts = parts[arg_start_idx:]
+    else:
+        # No protocol prefix, assume simple format
+        url = parts[0]
+        arg_parts = parts[1:]
+    
+    # Parse arguments
+    args = {}
+    for arg in arg_parts:
+        if arg and "=" in arg:
+            key, value = arg.split("=", 1)
+            args[key] = value
+        elif arg:
+            args[arg] = True
+    
+    return url, args
+
+
+def build_repomix_command(repo_path: str, args: Dict[str, str]) -> List[str]:
+    """
+    Build repomix command with arguments
+    
+    Args:
+        repo_path: Path to the repository
+        args: Dictionary of arguments
+        
+    Returns:
+        List of command parts
+    """
+    cmd = ["repomix", "--stdout"]
+    
+    # Map of supported arguments to their command-line flags
+    supported_args = {
+        "compress": "--compress",
+        "style": "--style",
+        "include": "--include",
+        "ignore": "--ignore",
+        "remove-comments": "--remove-comments",
+        "remove-empty-lines": "--remove-empty-lines",
+        "output-show-line-numbers": "--output-show-line-numbers",
+        "no-file-summary": "--no-file-summary",
+        "no-directory-structure": "--no-directory-structure",
+        "no-files": "--no-files",
+        "header-text": "--header-text",
+        "instruction-file-path": "--instruction-file-path",
+        "include-empty-directories": "--include-empty-directories",
+        "no-git-sort-by-changes": "--no-git-sort-by-changes",
+        "include-diffs": "--include-diffs",
+        "no-gitignore": "--no-gitignore",
+        "no-default-patterns": "--no-default-patterns",
+        "no-security-check": "--no-security-check",
+        "token-count-encoding": "--token-count-encoding",
+        "top-files-len": "--top-files-len",
+        "verbose": "--verbose",
+        "quiet": "--quiet",
+    }
+    
+    # Add arguments to command
+    for arg, value in args.items():
+        if arg in supported_args:
+            flag = supported_args[arg]
+            if value is True:
+                # Boolean flag
+                cmd.append(flag)
+            elif value and value != "":
+                # Value argument
+                cmd.extend([flag, value])
+            # Skip empty string values
+    
+    # Add repository path
+    cmd.append(repo_path)
+    
+    return cmd
+
+
 @llm.hookimpl
 def register_fragment_loaders(register):
     register("repomix", repomix_loader)
@@ -16,14 +172,21 @@ def repomix_loader(argument: str) -> List[llm.Fragment]:
     """
     Load repository contents as fragments using Repomix
     
-    Argument is a git repository URL (https:// or ssh://)
+    Argument can be:
+    - A git repository URL: https://git.sr.ht/~amolith/willow
+    - URL with arguments: https://git.sr.ht/~amolith/willow:compress:include=*.py
+    
     Examples:
         repomix:https://git.sr.ht/~amolith/willow
-        repomix:ssh://git.sr.ht:~amolith/willow
+        repomix:ssh://git.sr.ht:~amolith/willow:compress
+        repomix:git@github.com:user/repo.git:include=*.ts,*.js:ignore=*.md
     """
-    if not argument.startswith(("https://", "ssh://", "git@")):
+    # Parse the fragment string to extract URL and arguments
+    url, args = parse_fragment_string(argument)
+    
+    if not url.startswith(("https://", "ssh://", "git@")):
         raise ValueError(
-            f"Repository URL must start with https://, ssh://, or git@ - got: {argument}"
+            f"Repository URL must start with https://, ssh://, or git@ - got: {url}"
         )
     
     # Check if repomix is available
@@ -40,15 +203,18 @@ def repomix_loader(argument: str) -> List[llm.Fragment]:
         try:
             # Clone the repository
             subprocess.run(
-                ["git", "clone", "--depth=1", argument, str(repo_path)],
+                ["git", "clone", "--depth=1", url, str(repo_path)],
                 check=True,
                 capture_output=True,
                 text=True,
             )
             
+            # Build repomix command with arguments
+            repomix_cmd = build_repomix_command(str(repo_path), args)
+            
             # Run repomix on the cloned repository
             result = subprocess.run(
-                ["repomix", "--stdout", str(repo_path)],
+                repomix_cmd,
                 check=True,
                 capture_output=True,
                 text=True,
@@ -68,11 +234,11 @@ def repomix_loader(argument: str) -> List[llm.Fragment]:
             # Handle Git or repomix errors
             if "git" in str(e.cmd):
                 raise ValueError(
-                    f"Failed to clone repository {argument}: {e.stderr}"
+                    f"Failed to clone repository {url}: {e.stderr}"
                 )
             elif "repomix" in str(e.cmd):
                 raise ValueError(
-                    f"Failed to run repomix on {argument}: {e.stderr}"
+                    f"Failed to run repomix on {url}: {e.stderr}"
                 )
             else:
                 raise ValueError(

tests/test_fragments_repomix.py 🔗

@@ -0,0 +1,540 @@
+from llm_fragments_repomix import repomix_loader
+import pytest
+from unittest.mock import patch, Mock, MagicMock
+import subprocess
+import tempfile
+import pathlib
+
+
+class TestRepomixLoader:
+    """Test the repomix_loader function"""
+
+    def test_invalid_url_formats(self):
+        """Test that invalid URL formats raise ValueError"""
+        invalid_urls = [
+            "not-a-url",
+            "ftp://example.com/repo",
+            "file://local/path",
+            "http://example.com",  # No path
+            "",  # Empty string
+        ]
+        
+        for invalid_url in invalid_urls:
+            with pytest.raises(ValueError) as ex:
+                repomix_loader(invalid_url)
+            assert "Repository URL must start with https://, ssh://, or git@" in str(ex.value)
+
+    @patch('llm_fragments_repomix.shutil.which')
+    def test_repomix_not_installed(self, mock_which):
+        """Test error when repomix is not installed"""
+        mock_which.return_value = None
+        
+        with pytest.raises(ValueError) as ex:
+            repomix_loader("https://github.com/user/repo")
+        assert "repomix command not found" in str(ex.value)
+        assert "https://github.com/yamadashy/repomix" in str(ex.value)
+
+    @patch('llm_fragments_repomix.shutil.which')
+    @patch('llm_fragments_repomix.subprocess.run')
+    def test_git_clone_failure(self, mock_run, mock_which):
+        """Test handling of git clone failures"""
+        mock_which.return_value = "/usr/bin/repomix"
+        
+        # Mock git clone failure
+        mock_run.side_effect = subprocess.CalledProcessError(
+            1, ["git", "clone"], stderr="Repository not found"
+        )
+        
+        with pytest.raises(ValueError) as ex:
+            repomix_loader("https://github.com/user/nonexistent")
+        assert "Failed to clone repository" in str(ex.value)
+        assert "Repository not found" in str(ex.value)
+
+    @patch('llm_fragments_repomix.shutil.which')
+    @patch('llm_fragments_repomix.subprocess.run')
+    def test_repomix_failure(self, mock_run, mock_which):
+        """Test handling of repomix execution failures"""
+        mock_which.return_value = "/usr/bin/repomix"
+        
+        # Mock successful git clone, failed repomix
+        def mock_run_side_effect(cmd, **kwargs):
+            if cmd[0] == "git":
+                return Mock(stdout="", stderr="")
+            elif cmd[0] == "repomix":
+                raise subprocess.CalledProcessError(
+                    1, ["repomix"], stderr="Repomix error"
+                )
+        
+        mock_run.side_effect = mock_run_side_effect
+        
+        with pytest.raises(ValueError) as ex:
+            repomix_loader("https://github.com/user/repo")
+        assert "Failed to run repomix" in str(ex.value)
+        assert "Repomix error" in str(ex.value)
+
+    @patch('llm_fragments_repomix.shutil.which')
+    @patch('llm_fragments_repomix.subprocess.run')
+    def test_successful_repomix_execution(self, mock_run, mock_which):
+        """Test successful repomix execution"""
+        mock_which.return_value = "/usr/bin/repomix"
+        
+        # Mock repomix output
+        repomix_output = """
+# Repository Structure
+
+## File: main.py
+```python
+print("Hello, World!")
+```
+
+## File: README.md
+```markdown
+# Test Project
+This is a test project.
+```
+"""
+        
+        def mock_run_side_effect(cmd, **kwargs):
+            if cmd[0] == "git":
+                return Mock(stdout="", stderr="")
+            elif cmd[0] == "repomix":
+                return Mock(stdout=repomix_output, stderr="")
+        
+        mock_run.side_effect = mock_run_side_effect
+        
+        fragments = repomix_loader("https://github.com/user/repo")
+        
+        assert len(fragments) == 1
+        assert str(fragments[0]) == repomix_output
+        assert fragments[0].source == "repomix:https://github.com/user/repo"
+
+    @patch('llm_fragments_repomix.shutil.which')
+    @patch('llm_fragments_repomix.subprocess.run')
+    def test_different_url_formats(self, mock_run, mock_which):
+        """Test that different valid URL formats work"""
+        mock_which.return_value = "/usr/bin/repomix"
+        
+        def mock_run_side_effect(cmd, **kwargs):
+            if cmd[0] == "git":
+                return Mock(stdout="", stderr="")
+            elif cmd[0] == "repomix":
+                return Mock(stdout="test output", stderr="")
+        
+        mock_run.side_effect = mock_run_side_effect
+        
+        valid_urls = [
+            "https://github.com/user/repo",
+            "https://git.sr.ht/~user/repo",
+            "ssh://git@github.com:user/repo.git",
+            "git@github.com:user/repo.git",
+        ]
+        
+        for url in valid_urls:
+            fragments = repomix_loader(url)
+            assert len(fragments) == 1
+            assert fragments[0].source == f"repomix:{url}"
+
+    @patch('llm_fragments_repomix.shutil.which')
+    @patch('llm_fragments_repomix.subprocess.run')
+    def test_subprocess_calls(self, mock_run, mock_which):
+        """Test that subprocess calls are made correctly"""
+        mock_which.return_value = "/usr/bin/repomix"
+        
+        def mock_run_side_effect(cmd, **kwargs):
+            if cmd[0] == "git":
+                return Mock(stdout="", stderr="")
+            elif cmd[0] == "repomix":
+                return Mock(stdout="test output", stderr="")
+        
+        mock_run.side_effect = mock_run_side_effect
+        
+        repo_url = "https://github.com/user/repo"
+        repomix_loader(repo_url)
+        
+        # Check git clone call
+        git_call = mock_run.call_args_list[0]
+        assert git_call[0][0][:3] == ["git", "clone", "--depth=1"]
+        assert git_call[0][0][3] == repo_url
+        assert git_call[1]["check"] is True
+        assert git_call[1]["capture_output"] is True
+        assert git_call[1]["text"] is True
+        
+        # Check repomix call
+        repomix_call = mock_run.call_args_list[1]
+        assert repomix_call[0][0][:2] == ["repomix", "--stdout"]
+        assert repomix_call[1]["check"] is True
+        assert repomix_call[1]["capture_output"] is True
+        assert repomix_call[1]["text"] is True
+
+    @patch('llm_fragments_repomix.shutil.which')
+    @patch('llm_fragments_repomix.subprocess.run')
+    def test_generic_exception_handling(self, mock_run, mock_which):
+        """Test handling of generic exceptions"""
+        mock_which.return_value = "/usr/bin/repomix"
+        
+        def mock_run_side_effect(cmd, **kwargs):
+            if cmd[0] == "git":
+                return Mock(stdout="", stderr="")
+            elif cmd[0] == "repomix":
+                raise OSError("Permission denied")
+        
+        mock_run.side_effect = mock_run_side_effect
+        
+        with pytest.raises(ValueError) as ex:
+            repomix_loader("https://github.com/user/repo")
+        assert "Error processing repository" in str(ex.value)
+        assert "Permission denied" in str(ex.value)
+
+    @patch('llm_fragments_repomix.shutil.which')
+    @patch('llm_fragments_repomix.subprocess.run')
+    @patch('llm_fragments_repomix.tempfile.TemporaryDirectory')
+    def test_temporary_directory_cleanup(self, mock_tempdir, mock_run, mock_which):
+        """Test that temporary directory is properly cleaned up"""
+        mock_which.return_value = "/usr/bin/repomix"
+        
+        # Create a mock context manager for TemporaryDirectory
+        mock_context = MagicMock()
+        mock_context.__enter__.return_value = "/tmp/test_dir"
+        mock_context.__exit__.return_value = None
+        mock_tempdir.return_value = mock_context
+        
+        def mock_run_side_effect(cmd, **kwargs):
+            if cmd[0] == "git":
+                return Mock(stdout="", stderr="")
+            elif cmd[0] == "repomix":
+                return Mock(stdout="test output", stderr="")
+        
+        mock_run.side_effect = mock_run_side_effect
+        
+        repomix_loader("https://github.com/user/repo")
+        
+        # Verify that the temporary directory context manager was used
+        mock_tempdir.assert_called_once()
+        mock_context.__enter__.assert_called_once()
+        mock_context.__exit__.assert_called_once()
+
+
+class TestRepomixArgumentParsing:
+    """Test argument parsing for colon-separated options"""
+    
+    def test_parse_fragment_string_url_only(self):
+        """Test parsing URL without arguments"""
+        from llm_fragments_repomix import parse_fragment_string
+        
+        url, args = parse_fragment_string("https://github.com/user/repo")
+        assert url == "https://github.com/user/repo"
+        assert args == {}
+    
+    def test_parse_fragment_string_with_compress(self):
+        """Test parsing URL with compress flag"""
+        from llm_fragments_repomix import parse_fragment_string
+        
+        url, args = parse_fragment_string("https://github.com/user/repo:compress")
+        assert url == "https://github.com/user/repo"
+        assert args == {"compress": True}
+    
+    def test_parse_fragment_string_with_include_patterns(self):
+        """Test parsing URL with include patterns"""
+        from llm_fragments_repomix import parse_fragment_string
+        
+        url, args = parse_fragment_string("https://github.com/user/repo:include=*.ts,*.js")
+        assert url == "https://github.com/user/repo"
+        assert args == {"include": "*.ts,*.js"}
+    
+    def test_parse_fragment_string_with_ignore_patterns(self):
+        """Test parsing URL with ignore patterns"""
+        from llm_fragments_repomix import parse_fragment_string
+        
+        url, args = parse_fragment_string("https://github.com/user/repo:ignore=*.log,tmp/")
+        assert url == "https://github.com/user/repo"
+        assert args == {"ignore": "*.log,tmp/"}
+    
+    def test_parse_fragment_string_multiple_args(self):
+        """Test parsing URL with multiple arguments"""
+        from llm_fragments_repomix import parse_fragment_string
+        
+        url, args = parse_fragment_string("https://github.com/user/repo:compress:include=*.py:ignore=tests/")
+        assert url == "https://github.com/user/repo"
+        assert args == {
+            "compress": True,
+            "include": "*.py",
+            "ignore": "tests/"
+        }
+    
+    def test_parse_fragment_string_complex_patterns(self):
+        """Test parsing with complex glob patterns"""
+        from llm_fragments_repomix import parse_fragment_string
+        
+        url, args = parse_fragment_string("https://github.com/user/repo:include=src/**/*.ts,**/*.md:ignore=**/*.test.ts,node_modules/")
+        assert url == "https://github.com/user/repo"
+        assert args == {
+            "include": "src/**/*.ts,**/*.md",
+            "ignore": "**/*.test.ts,node_modules/"
+        }
+    
+    def test_parse_fragment_string_boolean_flags(self):
+        """Test parsing various boolean flags"""
+        from llm_fragments_repomix import parse_fragment_string
+        
+        url, args = parse_fragment_string("https://github.com/user/repo:compress:remove-comments:remove-empty-lines")
+        assert url == "https://github.com/user/repo"
+        assert args == {
+            "compress": True,
+            "remove-comments": True,
+            "remove-empty-lines": True
+        }
+    
+    def test_parse_fragment_string_output_options(self):
+        """Test parsing output-related options"""
+        from llm_fragments_repomix import parse_fragment_string
+        
+        url, args = parse_fragment_string("https://github.com/user/repo:style=markdown:output-show-line-numbers")
+        assert url == "https://github.com/user/repo"
+        assert args == {
+            "style": "markdown",
+            "output-show-line-numbers": True
+        }
+    
+    def test_parse_fragment_string_ssh_url(self):
+        """Test parsing SSH URLs with arguments"""
+        from llm_fragments_repomix import parse_fragment_string
+        
+        url, args = parse_fragment_string("git@github.com:user/repo.git:compress:include=*.py")
+        assert url == "git@github.com:user/repo.git"
+        assert args == {
+            "compress": True,
+            "include": "*.py"
+        }
+    
+    def test_parse_fragment_string_ssh_protocol_url(self):
+        """Test parsing SSH protocol URLs with arguments"""
+        from llm_fragments_repomix import parse_fragment_string
+        
+        url, args = parse_fragment_string("ssh://git@github.com:user/repo.git:compress")
+        assert url == "ssh://git@github.com:user/repo.git"
+        assert args == {"compress": True}
+    
+    def test_parse_fragment_string_empty_args(self):
+        """Test parsing with empty argument values"""
+        from llm_fragments_repomix import parse_fragment_string
+        
+        url, args = parse_fragment_string("https://github.com/user/repo:include=")
+        assert url == "https://github.com/user/repo"
+        assert args == {"include": ""}
+    
+    def test_parse_fragment_string_duplicate_args(self):
+        """Test parsing with duplicate arguments (last one wins)"""
+        from llm_fragments_repomix import parse_fragment_string
+        
+        url, args = parse_fragment_string("https://github.com/user/repo:include=*.ts:include=*.js")
+        assert url == "https://github.com/user/repo"
+        assert args == {"include": "*.js"}
+    
+    def test_build_repomix_command_no_args(self):
+        """Test building repomix command without arguments"""
+        from llm_fragments_repomix import build_repomix_command
+        
+        cmd = build_repomix_command("/tmp/repo", {})
+        assert cmd == ["repomix", "--stdout", "/tmp/repo"]
+    
+    def test_build_repomix_command_with_compress(self):
+        """Test building repomix command with compress flag"""
+        from llm_fragments_repomix import build_repomix_command
+        
+        cmd = build_repomix_command("/tmp/repo", {"compress": True})
+        assert cmd == ["repomix", "--stdout", "--compress", "/tmp/repo"]
+    
+    def test_build_repomix_command_with_include(self):
+        """Test building repomix command with include patterns"""
+        from llm_fragments_repomix import build_repomix_command
+        
+        cmd = build_repomix_command("/tmp/repo", {"include": "*.ts,*.js"})
+        assert cmd == ["repomix", "--stdout", "--include", "*.ts,*.js", "/tmp/repo"]
+    
+    def test_build_repomix_command_with_ignore(self):
+        """Test building repomix command with ignore patterns"""
+        from llm_fragments_repomix import build_repomix_command
+        
+        cmd = build_repomix_command("/tmp/repo", {"ignore": "*.log,tmp/"})
+        assert cmd == ["repomix", "--stdout", "--ignore", "*.log,tmp/", "/tmp/repo"]
+    
+    def test_build_repomix_command_multiple_args(self):
+        """Test building repomix command with multiple arguments"""
+        from llm_fragments_repomix import build_repomix_command
+        
+        args = {
+            "compress": True,
+            "include": "*.py",
+            "ignore": "tests/",
+            "remove-comments": True
+        }
+        cmd = build_repomix_command("/tmp/repo", args)
+        expected = ["repomix", "--stdout", "--compress", "--include", "*.py", "--ignore", "tests/", "--remove-comments", "/tmp/repo"]
+        assert cmd == expected
+    
+    def test_build_repomix_command_with_style(self):
+        """Test building repomix command with style option"""
+        from llm_fragments_repomix import build_repomix_command
+        
+        cmd = build_repomix_command("/tmp/repo", {"style": "markdown"})
+        assert cmd == ["repomix", "--stdout", "--style", "markdown", "/tmp/repo"]
+    
+    def test_build_repomix_command_boolean_flags(self):
+        """Test building repomix command with various boolean flags"""
+        from llm_fragments_repomix import build_repomix_command
+        
+        args = {
+            "compress": True,
+            "remove-comments": True,
+            "remove-empty-lines": True,
+            "output-show-line-numbers": True,
+            "no-file-summary": True
+        }
+        cmd = build_repomix_command("/tmp/repo", args)
+        expected = [
+            "repomix", "--stdout",
+            "--compress",
+            "--remove-comments", 
+            "--remove-empty-lines",
+            "--output-show-line-numbers",
+            "--no-file-summary",
+            "/tmp/repo"
+        ]
+        assert cmd == expected
+    
+    def test_build_repomix_command_unsupported_arg(self):
+        """Test that unsupported arguments are ignored"""
+        from llm_fragments_repomix import build_repomix_command
+        
+        args = {
+            "compress": True,
+            "unsupported-option": "value",
+            "include": "*.py"
+        }
+        cmd = build_repomix_command("/tmp/repo", args)
+        expected = ["repomix", "--stdout", "--compress", "--include", "*.py", "/tmp/repo"]
+        assert cmd == expected
+    
+    def test_build_repomix_command_empty_string_values(self):
+        """Test that empty string values are handled correctly"""
+        from llm_fragments_repomix import build_repomix_command
+        
+        args = {
+            "include": "",
+            "ignore": "*.log"
+        }
+        cmd = build_repomix_command("/tmp/repo", args)
+        # Empty include should be ignored, ignore should be included
+        expected = ["repomix", "--stdout", "--ignore", "*.log", "/tmp/repo"]
+        assert cmd == expected
+
+
+class TestRepomixIntegrationWithArguments:
+    """Integration tests for repomix loader with arguments"""
+    
+    @patch('llm_fragments_repomix.shutil.which')
+    @patch('llm_fragments_repomix.subprocess.run')
+    def test_repomix_loader_with_compress(self, mock_run, mock_which):
+        """Test repomix loader with compress argument"""
+        mock_which.return_value = "/usr/bin/repomix"
+        
+        def mock_run_side_effect(cmd, **kwargs):
+            if cmd[0] == "git":
+                return Mock(stdout="", stderr="")
+            elif cmd[0] == "repomix":
+                # Verify compress flag is passed
+                assert "--compress" in cmd
+                return Mock(stdout="compressed output", stderr="")
+        
+        mock_run.side_effect = mock_run_side_effect
+        
+        fragments = repomix_loader("https://github.com/user/repo:compress")
+        assert len(fragments) == 1
+        assert str(fragments[0]) == "compressed output"
+        assert fragments[0].source == "repomix:https://github.com/user/repo:compress"
+    
+    @patch('llm_fragments_repomix.shutil.which')
+    @patch('llm_fragments_repomix.subprocess.run')
+    def test_repomix_loader_with_include_patterns(self, mock_run, mock_which):
+        """Test repomix loader with include patterns"""
+        mock_which.return_value = "/usr/bin/repomix"
+        
+        def mock_run_side_effect(cmd, **kwargs):
+            if cmd[0] == "git":
+                return Mock(stdout="", stderr="")
+            elif cmd[0] == "repomix":
+                # Verify include patterns are passed
+                assert "--include" in cmd
+                include_idx = cmd.index("--include")
+                assert cmd[include_idx + 1] == "*.ts,*.js"
+                return Mock(stdout="filtered output", stderr="")
+        
+        mock_run.side_effect = mock_run_side_effect
+        
+        fragments = repomix_loader("https://github.com/user/repo:include=*.ts,*.js")
+        assert len(fragments) == 1
+        assert str(fragments[0]) == "filtered output"
+    
+    @patch('llm_fragments_repomix.shutil.which')
+    @patch('llm_fragments_repomix.subprocess.run')
+    def test_repomix_loader_with_multiple_args(self, mock_run, mock_which):
+        """Test repomix loader with multiple arguments"""
+        mock_which.return_value = "/usr/bin/repomix"
+        
+        def mock_run_side_effect(cmd, **kwargs):
+            if cmd[0] == "git":
+                return Mock(stdout="", stderr="")
+            elif cmd[0] == "repomix":
+                # Verify multiple arguments are passed
+                assert "--compress" in cmd
+                assert "--include" in cmd
+                assert "--ignore" in cmd
+                include_idx = cmd.index("--include")
+                assert cmd[include_idx + 1] == "*.py"
+                ignore_idx = cmd.index("--ignore")
+                assert cmd[ignore_idx + 1] == "tests/"
+                return Mock(stdout="multi-arg output", stderr="")
+        
+        mock_run.side_effect = mock_run_side_effect
+        
+        fragments = repomix_loader("https://github.com/user/repo:compress:include=*.py:ignore=tests/")
+        assert len(fragments) == 1
+        assert str(fragments[0]) == "multi-arg output"
+    
+    @patch('llm_fragments_repomix.shutil.which')
+    @patch('llm_fragments_repomix.subprocess.run')
+    def test_repomix_loader_with_ssh_url_and_args(self, mock_run, mock_which):
+        """Test repomix loader with SSH URL and arguments"""
+        mock_which.return_value = "/usr/bin/repomix"
+        
+        def mock_run_side_effect(cmd, **kwargs):
+            if cmd[0] == "git":
+                return Mock(stdout="", stderr="")
+            elif cmd[0] == "repomix":
+                assert "--compress" in cmd
+                return Mock(stdout="ssh output", stderr="")
+        
+        mock_run.side_effect = mock_run_side_effect
+        
+        fragments = repomix_loader("git@github.com:user/repo.git:compress")
+        assert len(fragments) == 1
+        assert str(fragments[0]) == "ssh output"
+        assert fragments[0].source == "repomix:git@github.com:user/repo.git:compress"
+    
+    @patch('llm_fragments_repomix.shutil.which')
+    @patch('llm_fragments_repomix.subprocess.run')
+    def test_repomix_loader_preserves_original_source(self, mock_run, mock_which):
+        """Test that the original fragment string is preserved in source"""
+        mock_which.return_value = "/usr/bin/repomix"
+        
+        def mock_run_side_effect(cmd, **kwargs):
+            if cmd[0] == "git":
+                return Mock(stdout="", stderr="")
+            elif cmd[0] == "repomix":
+                return Mock(stdout="test output", stderr="")
+        
+        mock_run.side_effect = mock_run_side_effect
+        
+        original_string = "https://github.com/user/repo:compress:include=*.ts,*.js:ignore=*.md"
+        fragments = repomix_loader(original_string)
+        assert fragments[0].source == f"repomix:{original_string}"