# MARX - Multi-Agentic Review eXperience

[![Build status](https://github.com/forketyfork/marx/actions/workflows/build.yml/badge.svg)](https://github.com/forketyfork/marx/actions/workflows/build.yml)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Python](https://img.shields.io/badge/language-Python-blue.svg)](https://www.python.org/)

An interactive CLI tool for automated multi-model AI code review of GitHub Pull Requests. Marx fetches open PRs and runs parallel code reviews inside Docker using three AI models (Claude, Codex, and Gemini) without modifying your local repository.

## TL;DR: Install, Configure, Run

```bash
# 1) Install (no checkout needed)
uv tool install marx-ai

# 2) Configure credentials
# Create a GitHub token at https://github.com/settings/tokens with 'repo' scope
cat > ~/.marx <<'EOF'
GITHUB_TOKEN=ghp_your_token_here
ANTHROPIC_API_KEY=your_claude_key
OPENAI_API_KEY=your_openai_key
GEMINI_API_KEY=your_gemini_key
# Optional: override repo detection
# MARX_REPO=owner/repo
EOF

# 3) Run on a repo with all agents
cd /path/to/your/git/repo
marx --pr 123 --agents claude,codex,gemini
```

## Features

- **Multi-Model AI Review**: Runs Claude, Codex, and Gemini in parallel for comprehensive code analysis
- **Containerized Checkout**: Clones the PR branch inside Docker so your local repo stays untouched
- **Intelligent PR Filtering**: Automatically filters PRs where you're not the author or reviewer
- **Docker Isolation**: All AI models run in containers with proper permissions
- **Structured Output**: JSON-formatted results with priority-based issue categorization
- **Robust Error Handling**: Graceful fallbacks and comprehensive validation
- **User-Friendly Interface**: Colored output with clear progress indicators

## Prerequisites

The following tools must be installed:

- `git` - Version control
- `gh` - GitHub CLI (must be authenticated)
- `docker` - Container runtime

## Installation

### Option 1: Install the published package from PyPI

If you simply want to use Marx and do not need the repository checked out locally, install the
published package directly from [PyPI](https://pypi.org/project/marx-ai/) using [uv](https://docs.astral.sh/uv/):

```bash
# Install globally
uv tool install marx-ai

# Or run without installing
uvx marx-ai
```

After installation the `marx` CLI will be on your `PATH` and you can run `marx --help` to verify
everything is set up correctly.

### Option 2: Using Nix (Recommended for development)

If you have [Nix](https://nixos.org/download.html) with flakes enabled:

```bash
# Clone the repository
git clone https://github.com/forketyfork/marx.git
cd marx

# Install globally to your profile
nix profile install .

# Or run without installing
nix run .

# Or enter development environment
nix develop
```

### Option 3: From source with uv

For standard Python environments when working from a clone of the repository:

```bash
# Clone the repository
git clone https://github.com/forketyfork/marx.git
cd marx

# Install globally
uv pip install .

# Or install in editable mode for development
uv pip install -e ".[dev]"
```

**Note**: If you're using Nix, `uv pip install` will fail because Nix's Python environment is read-only. Use the Nix installation methods above instead.

### Running Marx

After installation, run:
```bash
marx
```

## Quick Start with Nix (Recommended for Development)

If you have [Nix](https://nixos.org/download.html) with flakes enabled:

```bash
# Clone the repository
git clone https://github.com/forketyfork/marx.git
cd marx

# Enter the development environment
nix develop

# Or use direnv for automatic environment loading
direnv allow
```

The Nix flake provides:
- Python 3.12 with all dependencies
- System tools (git, gh, docker)
- Development tools (pytest, black, ruff, mypy)
- Just command runner for common tasks

### Using Just Commands

```bash
# See all available commands
just

# Run linters
just lint

# Run tests
just test

# Run marx
just run

# Cut a release (updates versions, commits, tags, pushes)
just release v0.1.1   # creates and pushes branch release-0.1.1 and tag v0.1.1

# Install package in editable mode
just install

# Run all checks (CI equivalent)
just check
```

## Environment Variables

### Setting up Environment Variables

Copy the example environment file and fill in your credentials:

```bash
cp .env.example .env
# Edit .env with your API keys and tokens
```

If using direnv (recommended), the `.env` file will be automatically loaded.

### Required
- `GITHUB_TOKEN` - GitHub API token (required for container access to GitHub API)
  - Create at https://github.com/settings/tokens
  - Required scope: `repo` (Full control of private repositories)
  - For public repositories only, the `public_repo` scope is sufficient
  - The token is used to fetch PR metadata, read diffs, and post reviews

### API Keys (at least one required)
The following API keys enable the respective AI models to function. You can provide one or more:

- `ANTHROPIC_API_KEY` - Anthropic API key for Claude
- `OPENAI_API_KEY` - OpenAI API key for Codex
- `GOOGLE_API_KEY` or `GEMINI_API_KEY` - Google API key for Gemini

Without API keys, the AI models will fall back to using local configuration from `~/.claude`, `~/.codex`, or `~/.gemini` directories if available.

### Optional
- `MARX_REPO` - Optional owner/name override (e.g., `owner/repo`) when auto-detection fails

## Configuration

Marx supports two authentication methods for AI models:

### Method 1: API Keys (Recommended for CI/CD)
Set environment variables with your API keys:
```bash
export ANTHROPIC_API_KEY="your-key-here"
export OPENAI_API_KEY="your-key-here"
export GOOGLE_API_KEY="your-key-here"
```

### Method 2: Local Configuration Directories
Marx can use AI model configuration directories from your home directory:

- `~/.claude` - Claude CLI configuration
- `~/.codex` - Codex CLI configuration
- `~/.gemini` - Gemini CLI configuration

These directories are mounted read-only into the Docker container during execution. This method is useful for development when you've already authenticated via the respective CLI tools.

### Global Config File (`~/.marx`)
Marx also looks for a configuration file at `~/.marx`. Any values defined here act as defaults and are only applied when the corresponding environment variable is unset. The file uses a simple `KEY=value` format and supports comments starting with `#` on their own line.

Example:

```
# Default repository for GitHub operations
MARX_REPO=acmecorp/my-app

# API keys (omit or override with environment variables as needed)
GITHUB_TOKEN=ghp_example
OPENAI_API_KEY="sk-123"
```

This file is loaded automatically when the CLI starts. Environment variables still take precedence, allowing you to override individual values for a single run.

### Customizing the review prompt

The default review instructions provided to each agent are stored in the package at
`marx/prompts/review_prompt.md`. You can supply your own prompt template by either setting the
`MARX_REVIEW_PROMPT_PATH` environment variable or adding `REVIEW_PROMPT_PATH=/path/to/prompt.md`
to your `~/.marx` configuration file. The template supports the placeholders `{pr_number}`, `{repo}`,
`{commit_sha}`, `{agent}`, and `{container_workspace_dir}`, which are replaced at runtime.

### Deduplication prompt

When more than one agent is selected, Marx automatically invokes an agent for a follow-up
deduplication pass. By default, the first agent in the `--agents` list is used, but you can
override this with the `--dedupe-with` option. This step reads the individual review outputs,
asks the agent to merge duplicate issues, and writes the consolidated results to
`runs/<pr>/dedup-review.json`, ensuring the `agent` field lists all reporting agents as a
comma-separated value. You can customize the deduplication instructions by providing a
template at `MARX_DEDUP_PROMPT_PATH` or `DEDUP_PROMPT_PATH` (in `~/.marx`). The bundled
template lives at `marx/prompts/dedup_prompt.md`. The Docker runner copies this file from
`.marx/dedup-review.json` in the workspace back to the run directory, so keep custom output
names aligned with that location. The template is rendered with Python string formatting, so
escape any literal braces as `{{` and `}}` when editing to avoid malformed prompts.

## Usage

```bash
marx [OPTIONS]
```

Use `marx --help` for a quick reminder of prerequisites, environment variables, and example commands.

### Options

- `--help` - Show help message and exit
- `--version` - Show version and exit
- `--pr <number>` - Specify PR number directly (skip interactive selection)
- `--agents <agents>` - Comma- or space-separated list of agents to run (claude, codex, gemini). Append `:<model>` to override the model used by that agent.
  - Default: all agents
  - **Claude** accepts aliases like `opus`, `sonnet`, and `haiku`, or full release identifiers such as `claude-sonnet-4-5-20250929`. See the [Claude models documentation](https://docs.claude.com/en/docs/about-claude/models/overview) for available models.
  - **Codex** works with OpenAI models including `gpt-5` and `gpt-5-codex`, plus any custom providers configured in `~/.codex/config.toml`.
  - **Gemini** supports model IDs such as `gemini-2.5-flash-lite`, `gemini-2.5-flash`, and `gemini-2.5-pro`. See the [Gemini models documentation](https://ai.google.dev/gemini-api/docs/models) for the complete list of available models.
- `--repo <owner/repo>` - Repository in the format owner/repo (e.g., acmecorp/my-app)
  - Overrides automatic repository detection
- `--resume` - Reuse artifacts from the previous run and skip AI execution
- `--dedupe-with <agent>` - Agent to use for deduplication (claude, codex, gemini). Append `:<model>` to override the model.
  - Default: first agent from `--agents`

### Examples

```bash
# Interactive mode with all agents (default)
marx

# Review PR #123 with all agents
marx --pr 123

# Review PR #123 with Claude only
marx --pr 123 --agents claude

# Interactive mode with Codex and Gemini
marx --agents codex,gemini

# Run Claude Opus with Codex defaults
marx --agents "claude:opus,codex"

# Review with Gemini 2.5 Pro only
marx --agents "gemini:gemini-2.5-pro"

# Review PRs in specific repository
marx --repo acmecorp/my-app

# Review specific PR in specific repository
marx --pr 123 --repo acmecorp/my-app

# Review specific PR with custom Claude + Gemini models
marx --pr 456 --agents "claude:sonnet,gemini:gemini-1.5-pro"

# Resume from previous run without rerunning agents
marx --resume --pr 123

# Use Claude Opus for deduplication while running all agents
marx --pr 123 --dedupe-with claude:opus

# Run Codex and Gemini, use Claude for deduplication
marx --agents codex,gemini --dedupe-with claude
```

## How It Works

### 1. Setup & Validation
- Checks for required dependencies (git, gh, docker)
- Builds the configured Docker image (default: `marx:latest`) if not present
- Validates `GITHUB_TOKEN` environment variable
- Confirms current directory is a git repository

### 2. Repository Detection
Determines repository slug (owner/name) using three methods in order:
1. `MARX_REPO` environment variable
2. `gh repo view` command
3. Git remote URL parsing (fallback)

### 3. PR Discovery
- Gets current GitHub user via GitHub API
- Fetches open PRs with metadata (title, author, reviewers, line changes)
- Filters PRs where:
  - You are NOT the author
  - You are NOT a reviewer
  - Has at least one reviewer assigned
- Handles both flat array and nested `nodes[]` API response formats

### 4. PR Selection & Checkout
- If `--pr` is specified: validates PR exists and gets branch name
- Otherwise: displays formatted PR list with colors and statistics and prompts for selection
- Fetches the PR and gets commit SHA
- Clones the repository inside the Docker container and checks out the PR head (or specific commit SHA)

### 5. Parallel AI Code Review
- If `--agents` is specified: runs only the selected agents
- Otherwise: runs all three agents (claude, codex, gemini)

Each AI model receives a detailed prompt instructing it to:
- Gather PR context using `gh` commands
- Review code for bugs, security issues, performance problems, etc.
- Output findings in structured JSON format

Selected models run simultaneously in isolated Docker containers with:
- Repository clone inside the container
- Run artifacts directory mounted from the host
- Config directories from home
- GitHub token for API access

### 6. Results Merging & Display
- Validates all JSON outputs
- Merges reviews from all three models
- Sorts issues by priority (P0 → P1 → P2)
- Displays formatted output with:
  - PR summary and title
  - Description from each AI model
  - Issue counts by priority
  - Detailed issue breakdown

## Output Format

### Review JSON Structure

Each AI model produces JSON output with this structure:

```json
{
  "pr_summary": {
    "number": 123,
    "title": "PR Title",
    "description": "Brief description of changes"
  },
  "issues": [
    {
      "agent": "claude|codex|gemini",
      "priority": "P0|P1|P2",
      "file": "path/to/file.js",
      "line": 42,
      "category": "bug|security|performance|quality|style",
      "description": "Detailed description of the issue",
      "proposed_fix": "Concrete suggestion on how to fix it"
    }
  ]
}
```

### Priority Definitions

- **P0 (Critical)**: Must be fixed - security vulnerabilities, bugs causing crashes/data loss
- **P1 (Important)**: Should be fixed - logic bugs, performance problems, poor error handling
- **P2 (Nice-to-have)**: Suggestions - code style, minor optimizations

### Output Files

All files are saved in the run artifacts directory (`runs/pr-{number}-{branch}/`):

- `claude-review.json` - Claude's review
- `codex-review.json` - Codex's review
- `gemini-review.json` - Gemini's review
- `dedup-review.json` - Deduplicated issues when multiple agents run
- `merged-review.json` - Combined review from all models

## Example Workflow

```bash
# Run marx
marx

# The tool will:
# 1. Detect your repository
# 2. Show available PRs
# 3. Prompt you to select one
# 4. Clone the PR inside Docker and run AI reviews in parallel
# 5. Display merged results and write artifacts to runs/pr-<number>-<branch>/

# If you want a local checkout to apply fixes:
gh pr checkout 123

# Review artifacts are available at:
ls runs/pr-123-<branch>/
```

## Docker Image

Marx uses a Docker image containing:
- **AI CLI Tools**: Claude, Codex, Gemini
- **GitHub Tools**: `gh` (GitHub CLI)
- **Search & Navigation**: `rg` (ripgrep), `fd`, `tree`
- **Code Refactoring**: `fastmod`, `ast-grep` (with `sg` alias)
- **Development Tools**: git and other utilities

The image is built automatically on first run using the Dockerfile in this repository.

### Customizing the Docker image

You can run Marx with a different Docker image by setting the `MARX_DOCKER_IMAGE`
environment variable or by adding `DOCKER_IMAGE=your/image:tag` to your `~/.marx`
configuration file. The environment variable takes precedence over the config file,
and both fall back to the bundled `marx:latest` image when unspecified.

When supplying a custom image make sure it satisfies the baseline requirements expected
by the runner script:

- A Linux base with `/bin/bash`, core utilities, and the ability to create users via `useradd`
- `git` and `gh` (GitHub CLI)
- Search and navigation tools: `rg`, `fd`, `tree`
- Code editing helpers: `fastmod`, `ast-grep` (available as `sg`)
- The CLI tools for any agents you intend to run (`claude`, `codex`, `gemini`)

Images missing these tools will fail at runtime, so verify your custom build before
invoking Marx.

## Error Handling

Marx includes robust error handling:
- Non-JSON outputs from AI models are handled gracefully with empty reviews
- Failed API calls are caught and reported with detailed error messages
- Docker errors and container stderr are both captured and displayed
- Invalid JSON is replaced with valid fallback structures
- All temporary files are cleaned up automatically
- Helpful hints are provided when authentication or configuration issues occur

## Security Considerations

- AI models run in isolated Docker containers
- Config directories are mounted read-only
- GitHub token is passed as an environment variable
- No destructive git operations are performed
- User input is validated before use

## Troubleshooting

### "GITHUB_TOKEN environment variable is not set"
Set your GitHub token: `export GITHUB_TOKEN=ghp_your_token_here`

### "Unable to determine repository automatically"
Set the repository manually: `export MARX_REPO=owner/repo`

### "Missing required dependencies"
Install the missing tools listed in the error message.

### AI model fails or returns non-JSON output
Marx will automatically handle this by creating an empty review. Error details are displayed in the terminal output, including:
- Docker errors (if Docker command failed)
- Container stderr output (errors from the AI CLI tool)
- Helpful hints for common issues (missing/invalid credentials)

Common causes:
- Missing authentication: Set API key environment variables (e.g., `ANTHROPIC_API_KEY`) or run the CLI auth command (e.g., `claude auth` for Claude)
- Invalid API keys or credentials in `~/.claude/`, `~/.codex/`, or `~/.gemini/`
- Network connectivity issues
- API quota/rate limiting

**Tip**: For CI/CD environments, using API key environment variables is more reliable than local configuration directories.

## Development & Testing

### Nix Development Workflow (Recommended)

The project includes a Nix flake for reproducible development environments:

```bash
# Enter development shell
nix develop

# Or use direnv for automatic loading (recommended)
echo "use flake" > .envrc
direnv allow

# Use just for common tasks
just             # List all commands
just check       # Run all checks (lint + type-check + test)
just lint        # Run linters
just test        # Run tests
just run --pr 123  # Run marx
```

#### Setting up direnv

1. Install direnv: `nix-env -iA nixpkgs.direnv` or see [direnv installation](https://direnv.net/docs/installation.html)
2. Add hook to your shell (e.g., `eval "$(direnv hook bash)"` for bash)
3. Allow the directory: `direnv allow`

Now the environment will automatically load when you `cd` into the project!

#### Just Command Reference

```bash
just install       # Install package in editable mode
just lint          # Run all linters (black, ruff, mypy)
just format        # Format code with black
just fix           # Auto-fix linting issues
just test          # Run all tests
just test-cov      # Run tests with coverage
just test-file FILE  # Run specific test file
just clean         # Clean build artifacts
just docker-build  # Build Docker image
just info          # Show environment info
```

### Python Version

The Python codebase includes a comprehensive test suite. To run tests manually:

```bash
# Install development dependencies
uv pip install -e ".[dev]"

# Run tests with coverage
pytest

# Run tests with verbose output
pytest -v

# Run specific test file
pytest tests/test_github.py

# Type checking with mypy
mypy marx

# Code formatting with black
black marx tests

# Linting with ruff
ruff check marx tests
```

### Project Structure

```
marx/
├── marx/          # Main package
│   ├── __init__.py
│   ├── cli.py          # CLI entry point and orchestration
│   ├── config.py       # Configuration and constants
│   ├── docker_runner.py # Docker container orchestration
│   ├── exceptions.py   # Custom exceptions
│   ├── github.py       # GitHub API client
│   ├── review.py       # Review processing and merging
│   └── ui.py           # Terminal UI and formatting
├── tests/              # Test suite
│   ├── conftest.py     # Pytest fixtures
│   ├── test_github.py  # GitHub client tests
│   └── test_review.py  # Review processing tests
├── pyproject.toml      # Project configuration
├── requirements.txt    # Dependencies
└── README.md           # This file
```

## Publishing

Marx ships with an automated publication pipeline defined in [`.github/workflows/publish.yml`](.github/workflows/publish.yml).
The workflow builds source and wheel distributions and uploads them as a workflow artifact on every run. Because uploading
artifacts requires elevated GitHub token capabilities, the workflow explicitly grants the `actions: write` permission at the
workflow level. When a GitHub release is published, the same workflow also pushes the distributions to PyPI using the
`PYPI_API_TOKEN` repository secret.

### Preparing a release

1. Update the version number in `pyproject.toml`.
2. Run `just build` locally to confirm the package can be built.
3. Commit and push the release changes, then create a Git tag (for example `v1.2.3`).
4. Draft a GitHub release for the tag and click **Publish** to trigger the PyPI deployment.

### Configuring credentials

- Create a PyPI [API token](https://pypi.org/help/#apitoken).
- Add the token to the repository secrets as `PYPI_API_TOKEN` (set the username to `__token__`).
- Ensure the token has permission to upload to the `marx-ai` project on PyPI.

Manual runs of the workflow via **Run workflow** in the Actions tab will build and attach the distributions without
publishing them, making it easy to validate the packaging process before performing a release.

## Contributing

Contributions are welcome! Please ensure:

- Code passes `mypy` type checking
- Code passes `ruff` linting
- Code is formatted with `black`
- Tests pass with `pytest`
- New features include tests
- Follow Python best practices and PEP 8

## License

See LICENSE file for details.
