# Project export: merj

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 12.0
- Tagline: Merj is your AI-powered merge copilot. It reviews both branches, understands intent with CodeRabbit, and uses Claude to suggest secure conflict resolutions automatically. One command. Zero headaches.
- Devpost: https://devpost.com/software/merj
- GitHub: https://github.com/Anay-jo/merj
- Video: https://www.youtube.com/embed/R1SPVzI1epU?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (CodeRabbit: Best Use of CodeRabbit AI)
- Team: 5 GitHub contributor(s) — Anay-jo (12 commits), joshuxchn (12 commits), iavramov (4 commits), ayushsridhar (2 commits), Claude (1 commits)

## Devpost submission (written by the team)

### What it does

## Inspiration Every developer knows that sinking feeling when a git pull command results in a merge conflict. The resulting scramble to pursue multiple lines of code is tedious and severely detrimental to the development process. What it does What if you never had to worry about merge conflicts again? That is why we built merj, a robust command-line tool that harnesses the power of modern Artificial Intelligence to streamline this process for developers. When a developer runs a pull command from their terminal, our software automatically scans for potential merge conflicts. It uses codebase context to resolve them effectively while preserving the goals of both branches.

### How we built it

merj is a tool we built on top of Git to minimize the learning curve for developers using our software. At a high level, merj has two states immediately after being called: a clean state and a conflict state. When the state is clean, our merj pull command behaves like git pull and updates the codebase; however, if our git pull results in a merge conflict, we immediately start our troubleshooting logic. The first thing we do is take a diff between the remote node and the last common ancestor node of the divergent nodes, and a diff between the local node and the same last common ancestor node. We then run a Retrieval Augmented Generation pipeline using the voyageai API to embed text, and ChromaDB to store vector embeddings, to retrieve code chunks from our codebase that are most relevant to the merge conflict, providing context for future LLM calls. After we get the code context, we use CodeRabbit’s powerful agent to summarize the changes between each divergent node and its common ancestor, providing semantic context alongside our code context. Finally, we combine all this context with the current merge conflict in Claude 3.5 Sonnet to perform our final resolution.

### Challenges we ran into

System design Integration LLM Formatting One of the key challenges that we spent a lot of time working on was designing the entire end-to-end system. This was the first time any member of our team had created a command-line interface tool or built something related to version control, so we had to gain a deep understanding of the inner workings of merge conflicts to develop an effective plan. Another major problem we ran into was integrating all our components. Our program consists of many different elements, such as version control, Retrieval Augmented Generation, CodeRabbit’s API, and LLM calling, which made it very hard to integrate into a single end-to-end pipeline. Lastly, we spent time optimizing the format of our LLM prompt to get the desired output for our clients.

### Accomplishments we're proud of

The main accomplishment we are proud of is our ability to integrate many different software components, with which we have minimal experience, into a single, cohesive software application that solves a practical problem many developers have faced. We were also really excited to learn about CodeRabbit and integrate their powerful ability to semantically synthesize information in git histories to enhance the quality of our final product response.

### What we learned

Throughout the hackathon, our team learned practical skills in version control, Retrieval Augmented Generation, and command-line interfaces. We were very excited to explore many new tools in the software space and to persevere through the learning curves to deliver a high-quality final product.

### What's next

The future of merj falls into two different buckets. The first bucket focuses on scaling merj to work for many people across many different repositories and large codebases while reducing command latency. The second bucket of merj is potentially integrating a live component into the git mechanism. This will work as follows: One person will push their code, and every other member of the project will receive an update that someone has pushed code, allowing them to automatically merge the new commit into their current working directory with just a click of the button.

## README (from the GitHub repository)

# Merj - AI-Powered Git Merge Conflict Resolver

🏆 **Winner — Best Use of CodeRabbit AI @ Cal Hacks 12.0** · 🚀 **Productionized at [CodeRabbit](https://www.coderabbit.ai), now serving 1000+ users**

[📺 Demo Video](https://www.youtube.com/watch?v=R1SPVzI1epU) · [📄 Devpost](https://devpost.com/software/merj) · [✍️ merj's journey to production (CodeRabbit blog)](https://www.coderabbit.ai/blog/how-a-hackathon-project-turned-into-my-work-at-coderabbit)

Merj automatically detects and resolves Git merge conflicts using Claude AI, CodeRabbit code reviews, and intelligent code context from your repository.

## Table of Contents
- [What is Merj?](#what-is-merj)
- [Quick Start](#quick-start)
- [Complete Setup Guide](#complete-setup-guide)
- [Getting API Keys](#getting-api-keys)
- [Running Merj](#running-merj)
- [How It Works](#how-it-works)
- [Troubleshooting](#troubleshooting)
- [Testing Your Setup](#testing-your-setup)
- [Cost Estimates](#cost-estimates)

## What is Merj?

Merj transforms the tedious process of resolving Git merge conflicts into an intelligent, semi-automated workflow:

1. **Detects** merge conflicts automatically after `git pull`
2. **Analyzes** both sides of the conflict using CodeRabbit
3. **Understands** your codebase using RAG (Retrieval-Augmented Generation)
4. **Resolves** conflicts intelligently using Claude AI
5. **Presents** solutions for your review and approval

Instead of manually editing conflict markers, you get AI-powered resolutions with explanations.

## Quick Start

Get Merj running in 5 minutes:

```bash
# 1. Clone the repository
git clone https://github.com/Anay-jo/MergeConflictResolver.git
cd MergeConflictResolver

# 2. Run quick setup (Node.js dependencies only)
npm install && npm link

# 3. Set up GitHub authentication
merj auth

# 4. Set Claude AI key (required)
export ANTHROPIC_API_KEY="sk-ant-..."

# 5. Try it!
cd your-git-repo
merj pull
```

For full functionality with CodeRabbit and RAG, continue to [Complete Setup Guide](#complete-setup-guide).

## Complete Setup Guide

### Prerequisites

- **Node.js** v18+ ([Download](https://nodejs.org/))
- **Python** 3.8+ ([Download](https://www.python.org/downloads/))
- **Git** 2.0+
- **GitHub Account** with Personal Access Token
- **API Keys** for Claude AI and Voyage AI (see [Getting API Keys](#getting-api-keys))

### Step 1: Clone and Install Node.js Components

```bash
# Clone the repository
git clone https://github.com/Anay-jo/MergeConflictResolver.git
cd MergeConflictResolver

# Install Node.js dependencies
npm install

# Make 'merj' command available globally
npm link

# Verify installation
merj --help
```

### Step 2: Set Up Python Environment (for RAG Pipeline)

```bash
# Create Python virtual environment
python3 -m venv venv

# Activate virtual environment
# On macOS/Linux:
source venv/bin/activate
# On Windows:
# venv\Scripts\activate

# Install Python dependencies
pip install -r requirements.txt
pip install -r flask_backend/requirements.txt
```

### Step 3: Install CodeRabbit CLI

```bash
# Install CodeRabbit CLI globally
curl -fsSL https://cli.coderabbit.ai/install.sh | sh

# Reload your shell configuration
source ~/.zshrc  # or ~/.bashrc for bash users

# Verify installation
coderabbit --version

# Log in to CodeRabbit (opens browser)
coderabbit auth login
```

### Step 4: Configure All API Keys

Create a `.env` file in the project root (or set environment variables):

```bash
# Required API Keys
export ANTHROPIC_API_KEY="sk-ant-api03-xxxxx"  # Claude AI (Required)
export VOYAGE_API_KEY="pa-xxxxx"                # Embeddings (Required for RAG)

# Optional Configuration
export MAIN_REF="origin/main"                   # Your main branch
export MODEL="claude-3-5-sonnet-20241022"       # Claude model to use
```

### Step 5: Authenticate GitHub

```bash
# Run authentication command
merj auth

# Enter your GitHub Personal Access Token when prompted
# The token will be stored securely in ~/.merjrc
```

### Step 6: Start the Flask Backend (for RAG)

```bash
# In a separate terminal, activate Python environment
source venv/bin/activate

# Start Flask backend
cd flask_backend
python app.py

# You should see: "Running on http://127.0.0.1:5000"
```

## Getting API Keys

### 1. Anthropic (Claude AI) - **REQUIRED**

Claude AI analyzes and resolves your merge conflicts.

1. Visit [https://console.anthropic.com](https://console.anthropic.com)
2. Sign up or log in
3. Go to **API Keys** section
4. Click **Create Key**
5. Copy the key (starts with `sk-ant-api03-`)
6. Set it: `export ANTHROPIC_API_KEY="your-key-here"`

**Pricing**: ~$0.01-0.02 per conflict resolution

### 2. Voyage AI (Code Embeddings) - **Required for RAG**

Voyage AI creates semantic embeddings of your code for intelligent context retrieval.

1. Visit [https://www.voyageai.com](https://www.voyageai.com)
2. Sign up for an account
3. Go to Dashboard → **API Keys**
4. Create a new key
5. Copy the key (starts with `pa-`)
6. Set it: `export VOYAGE_API_KEY="your-key-here"`

**Pricing**: ~$0.001 per 1000 tokens

### 3. GitHub Personal Access Token - **REQUIRED**

1. Visit [https://github.com/settings/tokens](https://github.com/settings/tokens)
2. Click **Generate new token (classic)**
3. Name it (e.g., "Merj CLI")
4. Select scopes:
   - ✅ `repo` (Full control of private repositories)
   - ✅ `read:org` (Read org and team membership)
5. Click **Generate token**
6. Copy immediately (won't be shown again!)
7. Use with: `merj auth`

### 4. CodeRabbit Account - 

CodeRabbit provides intelligent code review insights for both sides of the conflict.

1. Visit [https://coderabbit.ai](https://coderabbit.ai)
2. Sign up for free account
3. Install CLI (see Step 3 above)
4. Authenticate: `coderabbit auth login`

## Running Merj

### Basic Workflow

```bash
# 1. Ensure Flask backend is running (in separate terminal)
cd flask_backend && python app.py

# 2. Navigate to your git repository
cd /path/to/your/repo

# 3. Pull changes (Merj will handle conflicts)
merj pull

# 4. Follow the prompts to review and accept/reject resolutions
```

### What Happens During Conflict Resolution

When you run `merj pull` and conflicts are detected:

1. **Detection Phase**
   - Git pull executes and conflicts are identified
   - Conflicted files are listed

2. **Analysis Phase** (for each conflict)
   - CodeRabbit reviews changes on both branches
   - RAG pipeline extracts relevant code context
   - Context is saved to `rag_output/`

3. **Resolution Phase**
   - Claude AI receives:
     - The conflicted file with markers
     - CodeRabbit's analysis
     - Similar code patterns from your codebase
   - Claude generates a clean, merged version

4. **Review Phase**
   - You're shown the AI's resolution
   - Options: Accept, Reject, or View details
   - Accepted resolutions are staged in git

5. **Completion**
   - All accepted files are committed
   - Summary shows resolved/rejected counts

### Example Session

```bash
$ merj pull

🔍 Checking authentication...
✅ Authenticated as: joshuachen

📦 Repository: merj/test-repo
🌿 Current branch: feature-branch
🎯 Remote: origin

Pulling from origin...

⚠️  Merge conflicts detected in 2 files:
  - src/auth.py
  - src/database.py

Starting AI-powered resolution...

[1/2] Resolving: src/auth.py
📊 CodeRabbit: Found 3 code quality issues
🧠 Claude AI: Analyzing conflict...
✨ Resolution ready!

The conflict is between:
- LOCAL: Added password hashing with bcrypt
- REMOTE: Added rate limiting for login attempts

Proposed resolution combines both features safely.

Accept this resolution? (Y/n): Y
✅ Resolution applied to src/auth.py

[2/2] Resolving: src/database.py
...

Summary:
✅ Resolved: 2 files
❌ Rejected: 0 files
⚠️  Failed: 0 files
```

### Advanced Commands

```bash
# Force push changes
merj push --force

# Use a different Claude model
MODEL=claude-3-opus-20240229 merj pull

# Debug mode (verbose output)
DEBUG=true merj pull

# Skip CodeRabbit analysis
SKIP_CODERABBIT=true merj pull
```

## How It Works



[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 18 recognized source files, 162 KB.
- Flask (technology) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- AI coding agent: Claude Code — evidence: commit authorship or trailers

## Codebase structure (from repository index)

### Files (26 of 26)

```
.gitignore
bin/index.js
bin/merj.js
bin/resolve_with_claude.js
flask_backend/app.py
flask_backend/rag_pipeline/demo_chroma_db/chroma.sqlite3
flask_backend/README.md
flask_backend/requirements.txt
lib/auth.js
lib/git.js
my_chroma_db/chroma.sqlite3
package.json
rag_output/llm_context.txt
rag_output/rag_chunks.json
rag_pipeline/chroma.py
rag_pipeline/chunker.py
rag_pipeline/conflict_processor.py
rag_pipeline/embedder.py
rag_pipeline/local_remote_rag.py
rag_pipeline/PIPELINE_WORKFLOW.md
README.md
requirements.txt
scripts/demo_conflict.sh
scripts/full_demo_run.sh
scripts/load_reviews.js
scripts/review_two_sides_with_cr.py
```

### Dependencies

- flask_backend/requirements.txt: chromadb, Flask@==2.3.3, Flask-CORS@==4.0.0, tree-sitter-languages, voyageai
- package.json: @octokit/rest@^20.0.2, commander@^11.1.0, dotenv@^16.3.1, inquirer@^8.2.6, parse-diff@^0.11.1, simple-git@^3.20.0
- requirements.txt: numpy@>=1.24.0, orjson@>=3.10.0, tqdm@>=4.66.0, tree-sitter@==0.20.4, tree-sitter-languages@>=1.10.0, voyageai@>=0.2.0

### Recent commits (newest first)

- Update README with accolades and resources
- cleanup
- cleanup
- delete files
- Working version
- Integrate AI conflict resolution into merj pull with testing suite
- revert
- test
- link
- linking
- Add Claude AI merge resolution with user confirmation prompts
- Scripts
- test
- backend
- Remove fileto
- Remove local to remote
- Backend
- rag_pipeline
- Main branch changes
- Initial test file

## Key source files (fetched from GitHub, selected and truncated for size)

### rag_pipeline/PIPELINE_WORKFLOW.md

```markdown
# RAG Pipeline Workflow Documentation

## Table of Contents
1. [Architecture Overview](#architecture-overview)
2. [Component Breakdown](#component-breakdown)
3. [Data Flow](#data-flow)
4. [Component Connections](#component-connections)
5. [Storage Schema](#storage-schema)
6. [Execution Paths](#execution-paths)
7. [Integration Points](#integration-points)
8. [Configuration & Deployment](#configuration--deployment)
9. [Testing](#testing)

## Architecture Overview

The RAG (Retrieval-Augmented Generation) pipeline is designed to enhance merge conflict resolution by providing relevant context from the codebase history.

```
                MergeConflictResolver RAG Pipeline Flow
                ========================================

1. Git Merge Conflict Detection (merj pull)
              ↓
2. CodeRabbit Analysis (review_two_sides_with_cr.py)
              ↓
3. LCA Detection & Chunking (chunk_lca.py)
              ↓
4. Code Embedding (embedder.py → Voyage AI)
              ↓
5. Vector Storage (chroma.py → ChromaDB)
              ↓
6. RAG Retrieval (local_remote_rag.py)
              ↓
7. Context Enhancement for Conflict Resolution
```

### Internal Workflow Stages

```
Stage 1: PREPARATION (chunk_lca.py)
├── Find LCA (git merge-base)
├── Create worktree at LCA
├── Chunk repository → CodeChunk objects
├── Embed chunks → vectors
└── Store in ChromaDB collection

Stage 2: RETRIEVAL (local_remote_rag.py)
├── Receive conflict chunks
├── Embed incoming chunks
├── Query ChromaDB (k-NN search)
├── Filter by distance threshold
└── Return similar code context

Stage 3: DEMONSTRATION (demo.py)
└── Shows full pipeline on sample code
```

## Component Breakdown

### Core Pipeline Components

| File | Purpose | Key Functions |
|------|---------|---------------|
| `chunker.py` | Tree-sitter based code parser | `chunk_repository()`, `chunk_file()` |
| `embedder.py` | Voyage AI integration | `embed_chunk()`, `embed_chunks()` |
| `chroma.py` | ChromaDB storage | `insert_to_chroma()` |

### Integration Components

| File | Purpose | Key Functions |
|------|---------|---------------|
| `chunk_lca.py` | LCA chunking orchestrator | `create_lca_worktree()`, `main()` |
| `local_remote_rag.py` | RAG retrieval system | `process_chunks()`, `query_similar_chunks()` |

### CLI Components

| File | Purpose | Key Functions |
|------|---------|---------------|
| `bin/merj.js` | Main CLI entry point | `pull()`, `hasConflicts()` |
| `scripts/review_two_sides_with_cr.py` | CodeRabbit integration | `detect_rebase_context()`, `add_worktree()` |

## Data Flow

### CodeChunk Data Structure

```python
@dataclass
class CodeChunk:
    file_path: str       # Path to source file
    language: str        # Programming language
    signature: str       # Function/class signature
    content: str         # Actual code content
    chunk_type: str      # function/class/imports
    start_line: int      # Line range start
    end_line: int        # Line range end
    node_types: List[str] # AST n
[truncated — 8825 more characters]
```

### requirements.txt

```
# Core dependencies for Tree-sitter Code Chunker
tree-sitter==0.20.4
tree-sitter-languages>=1.10.0

# Embedding dependencies
voyageai>=0.2.0
numpy>=1.24.0

orjson>=3.10.0

tqdm>=4.66.0
```

### package.json

```
{
  "name": "Merj",
  "version": "1.0.0",
  "description": "A CLI that automatically handles and assists with merge conflicts using AI and CodeRabbit.",
  "main": "bin/index.js",
  "bin": {
    "merj": "./bin/index.js"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "cr:review": "python3 scripts/review_two_sides_with_cr.py"
  },
  "dependencies": {
    "@octokit/rest": "^20.0.2",
    "commander": "^11.1.0",
    "dotenv": "^16.3.1",
    "inquirer": "^8.2.6",
    "parse-diff": "^0.11.1",
    "simple-git": "^3.20.0"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/Anay-jo/MergeConflictResolver.git"
  },
  "keywords": [
    "git",
    "CLI",
    "merge",
    "LLM",
    "agentic",
    "AI",
    "CodeRabbit"
  ],
  "author": "Anay, Sam, Ayush, and Josh",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/Anay-jo/MergeConflictResolver/issues"
  },
  "homepage": "https://github.com/Anay-jo/MergeConflictResolver#readme",
  "engines": {
    "node": ">=18.0.0"
  }
}

```

### flask_backend/requirements.txt

```
Flask==2.3.3
Flask-CORS==4.0.0
chromadb
voyageai
tree-sitter-languages

```

### flask_backend/app.py

```python
#!/usr/bin/env python3
"""
Flask Backend for Merj Merge Conflict Tool
Receives diff data from merj.js and processes it through RAG pipeline
Includes LCA detection and knowledge base creation
"""

from flask import Flask, request, jsonify
from flask_cors import CORS
import json
import sys
import os
import tempfile
import shutil
import subprocess
from pathlib import Path
from datetime import datetime

# Add parent directory to path for rag_pipeline imports
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'scripts'))

# Import RAG pipeline components
from rag_pipeline.local_remote_rag import process_git_diff_json
from rag_pipeline.chunker import Chunker
from rag_pipeline.embedder import embed_chunks
from rag_pipeline.chroma import insert_to_chroma

# Import LCA detection from scripts
try:
    from review_two_sides_with_cr import detect_rebase_context, git, repo_root
except ImportError:
    # Fallback implementations if script not available
    def git(*args, cwd=None):
        """Run git command and return output."""
        cmd = ["git"] + list(args)
        p = subprocess.run(cmd, cwd=cwd, text=True, capture_output=True)
        if p.returncode != 0:
            raise RuntimeError(f"git command failed: {' '.join(cmd)}\n{p.stderr or p.stdout}")
        return p.stdout.strip()

    def repo_root():
        """Get repository root."""
        return git("rev-parse", "--show-toplevel")

    def detect_rebase_context(repo):
        """Detect LCA and branch tips."""
        # Get current HEAD
        local_tip = git("rev-parse", "HEAD", cwd=repo)
        # Get main branch reference
        main_ref = os.environ.get("MAIN_REF", "origin/main")
        # Find merge base (LCA)
        base = git("merge-base", local_tip, main_ref, cwd=repo)
        return base, local_tip, main_ref

# Initialize Flask app
app = Flask(__name__)
CORS(app)  # Enable CORS for requests from Node.js frontend

# Global cache for LCA collections
lca_cache = {}

def create_worktree(repo_path: str, commit: str, worktree_name: str = None) -> str:
    """
    Create a git worktree for a specific commit.
    Returns the path to the worktree.
    """
    if worktree_name is None:
        # Generate unique name based on timestamp
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        worktree_name = f"lca_worktree_{timestamp}"

    # Create worktree in temp directory
    worktree_path = os.path.join(tempfile.gettempdir(), worktree_name)

    # Remove if exists
    if os.path.exists(worktree_path):
        cleanup_worktree(repo_path, worktree_path)

    # Create new worktree
    git("worktree", "add", worktree_path, commit, cwd=repo_path)
    return worktree_path

def cleanup_worktree(repo_path: str, worktree_path: str):
    """
    Clean up a git worktree.
    """
    try:
        # Remove worktree from git
        git("worktree", "remove", worktree_path, "--force", cwd=repo_path)
    except:
        # If git removal fails, try manual cleanup
        if os.path.exists(worktree_path):
            shutil.rmtree(worktree_path, ignore_errors=True)

def get_or_create_lca_collection(lca_commit: str, repo_path: str, collection_prefix: str = "lca") -> str:
    """
    Get or create a ChromaDB collection for the LCA commit.
    Returns the collection name.
    """
    collection_name = f"{collection_prefix}_{lca_commit[:8]}"

    # Check cache
    if collection_name in lca_cache:
        print(f"✅ Using cached LCA collection: {collection_name}")
        return collection_name

    # Check if collection exists in ChromaDB
    try:
        import chromadb
        client = chromadb.PersistentClient(path='./rag_pipeline/demo_chroma_db')
        existing_collections = [col.name for col in client.list_collections()]

        if collection_name in existing_collections:
            print(f"✅ Found existing LCA collection: {collection_name}")
            lca_cache[collection_name] = True
            return collection_name
    except Exception as e:
        print(f"⚠️  ChromaDB check failed: {e}")

    # Create new collection from LCA
    print(f"🔨 Creating new LCA collection: {collection_name}")
    worktree_path = None

    try:
        # Create worktree at LCA
        worktree_path = create_worktree(repo_path, lca_commit)
        print(f"📁 Created worktree at: {worktree_path}")

        # Initialize chunker
        chunker = Chunker()

        # Process all Python files in the worktree
        all_chunks = []
        for root, dirs, files in os.walk(worktree_path):
            # Skip hidden directories
            dirs[:] = [d for d in dirs if not d.startswith('.')]

            for file in files:
                if file.endswith('.py'):
                    file_path = os.path.join(root, file)
                    try:
                        # Chunk the file directly using its path
                        # Use full Python config with top_level_nodes
                        config = {
                            "language": "python",
                            "top_level_nodes": {
                                "function_definition",
                                "class_definition",
                                "decorated_definition"
                            }
                        }
                        file_chunks = chunker.chunk_file(Path(file_path), config)
                        all_chunks.extend(file_chunks)
                    except Exception as e:
                        print(f"⚠️  Error processing {file_path}: {e}")

        print(f"📊 Chunked {len(all_chunks)} code objects from LCA")

        if all_chunks:
            # Embed chunks - this returns list of {"chunk": chunk, "embedding": vector}
            results = embed_chunks(all_chunks)
            print(f"🔢 Generated {len(results)} embeddings")

            # Store in ChromaDB
            insert_to_chroma(
                results,
                collection_name=collection_na
[truncated — 9062 more characters]
```

### bin/index.js

```javascript
#!/usr/bin/env node
/* eslint-disable no-console */

const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');
const { program } = require('commander');
const inquirer = require('inquirer');
const simpleGit = require('simple-git');
const { Octokit } = require('@octokit/rest');
require('dotenv').config();

const PKG = safeRequire(path.join(__dirname, '..', 'package.json')) || { version: '1.0.0' };
const SCRIPT_HELPER = path.resolve(__dirname, '..', 'scripts', 'review_two_sides_with_cr.py');

/* --------------------------- small utilities --------------------------- */

function safeRequire(p) {
  try { return require(p); } catch { return null; }
}

function run(cmd, args, opts = {}) {
  return spawnSync(cmd, args, { encoding: 'utf-8', stdio: 'pipe', ...opts });
}

function runInherit(cmd, args, opts = {}) {
  return spawnSync(cmd, args, { stdio: 'inherit', ...opts });
}

function assertInGitRepo() {
  const r = run('git', ['rev-parse', '--show-toplevel']);
  if (r.status !== 0) {
    console.error('❌ Not a git repository. Run inside a repo with .git present.');
    process.exit(1);
  }
  return r.stdout.trim();
}

function hasConflicts(cwd = process.cwd()) {
  const r = run('git', ['ls-files', '-u'], { cwd });
  return r.status === 0 && r.stdout.trim().length > 0;
}

function getCurrentBranch() {
  const r = run('git', ['rev-parse', '--abbrev-ref', 'HEAD']);
  return r.status === 0 ? r.stdout.trim() : 'unknown';
}

function getRemoteUrl(remote = 'origin') {
  const r = run('git', ['remote', 'get-url', remote]);
  return r.status === 0 ? r.stdout.trim() : null;
}

function getRepoInfo() {
  const remoteUrl = getRemoteUrl('origin') || '';
  const m = remoteUrl.match(/github\.com[:/](.+?)\/(.+?)(?:\.git)?$/i);
  const owner = m ? m[1] : 'unknown';
  const repo = m ? m[2] : 'unknown';
  return {
    remoteUrl,
    owner,
    repo,
    currentBranch: getCurrentBranch(),
  };
}

function ensureDotenvAt(repoRoot) {
  const envPath = path.join(repoRoot, '.env');
  if (!fs.existsSync(envPath)) fs.writeFileSync(envPath, '', 'utf-8');
  return envPath;
}

function getAuthenticatedClient() {
  const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN || process.env.GITHUB_PAT;
  if (!token) return null;
  try {
    return new Octokit({ auth: token });
  } catch {
    return null;
  }
}

async function checkAuth(octokit) {
  try {
    const { data } = await octokit.rest.users.getAuthenticated();
    return { authenticated: true, username: data.login };
  } catch (e) {
    return { authenticated: false, error: e.message };
  }
}

async function pushToRemote(remote = 'origin', branch = getCurrentBranch(), opts = {}) {
  const git = simpleGit();
  try {
    await git.push(remote, branch, opts.force ? ['--force-with-lease'] : []);
    return { success: true, message: `Pushed ${branch} to ${remote}` };
  } catch (e) {
    return { success: false, message: e.message };
  }
}

function getUpstream() {
  const r = run('git', ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}']);
  if (r.status !== 0) return null;
  const s = r.stdout.trim(); // "origin/main"
  const slash = s.indexOf('/');
  if (slash === -1) return null;
  return { remote: s.slice(0, slash), branch: s.slice(slash + 1) };
}

function readJsonSafe(p) {
  try { return JSON.parse(fs.readFileSync(p, 'utf-8')); }
  catch { return null; }
}

function readTextSafe(p) {
  try { return fs.readFileSync(p, 'utf-8'); }
  catch { return null; }
}

/* ----------------------- CodeRabbit side-review hook ------------------- */

function runCodeRabbitSideReviews(mainRef = 'origin/main', repoRoot = process.cwd()) {
  if (!fs.existsSync(SCRIPT_HELPER)) {
    console.error(`⚠️  Helper not found at ${SCRIPT_HELPER}`);
    return null;
  }
  const env = { ...process.env, MAIN_REF: mainRef };
  const py = run('python3', [SCRIPT_HELPER], { env, cwd: repoRoot });

  if (py.status !== 0) {
    const msg = (py.stderr || py.stdout || 'CodeRabbit helper failed.').trim();
    console.error(`⚠️  ${msg}`);
    return null;
  }

  const lines = py.stdout.trim().split('\n').filter(Boolean);
  if (lines.length < 2) return null;

  const mainPath  = lines[0];
  const localPath = lines[1];

  // The helper now writes plain text (.txt). Try JSON first; fallback to text display.
  const mainTxt  = readTextSafe(mainPath);
  const localTxt = readTextSafe(localPath);

  let mainReview = null, localReview = null;
  try { mainReview = JSON.parse(mainTxt || ''); } catch {}
  try { localReview = JSON.parse(localTxt || ''); } catch {}

  return { mainReview, localReview, mainTxt, localTxt };
}

function summarizeCodeRabbit(label, data, rawText) {
  console.log(`\n🧠 CodeRabbit (${label})`);

  const arr = Array.isArray(data) ? data
            : Array.isArray(data?.issues) ? data.issues
            : Array.isArray(data?.comments) ? data.comments
            : [];

  if (arr.length > 0) {
    arr.slice(0, 10).forEach((f, i) => {
      const file = f.file || f.path || f.filename || 'unknown';
      const line = f.line || f.start_line || f.position || '?';
      const msg  = f.message || f.body || f.summary || (typeof f === 'string' ? f : JSON.stringify(f).slice(0, 140));
      console.log(`  ${i + 1}. ${file}:${line} — ${msg}`);
    });
    if (arr.length > 10) console.log(`  …and ${arr.length - 10} more`);
    return;
  }

  // Fallback: print plain text lines from the CLI output
  if (rawText && rawText.trim()) {
    rawText.trim().split('\n').slice(0, 30).forEach(ln => console.log('  ' + ln));
  } else {
    console.log('  (no findings or parse error)');
  }
}

/* --------------------------------- CLI -------------------------------- */

program
  .name('merj')
  .description('A CLI that automatically resolves merge conflicts upon git pulls')
  .version(PKG.version || '1.0.0');

program
  .command('auth')
  .description('Set up GitHub authentication with Personal Access Token')
  .action(async () => {
    const repoRoot = assert
[truncated — 10121 more characters]
```

### scripts/full_demo_run.sh

```shell
#!/usr/bin/env bash
set -euo pipefail

echo "🧹 Cleaning old /tmp/merj-demo.* directories..."
rm -rf /tmp/merj-demo.*

echo "🚀 Running initial demo_conflict.sh..."
scripts/demo_conflict.sh

# 2️⃣ MAIN-side risky changes
MAIN="$(ls -d /tmp/merj-demo.*/work-main | head -1)"
echo "🧭 MAIN repo detected at: $MAIN"
cd "$MAIN"

cat >> app.txt <<'EOF'
function risky() {
  console.log("debug");
  const secret = "hardcoded-api-key-123";
  eval("console.log('danger')");
}
EOF

echo "📝 Committing MAIN-side risky code..."
git add app.txt
git commit -m "main: risky eval + debug + hardcoded secret"
git push origin main

# 3️⃣ FEATURE-side conflicting/smelly changes
FEAT="$(ls -d /tmp/merj-demo.*/work-feature | head -1)"
echo " FEATURE repo detected at: $FEAT"
cd "$FEAT"

echo "🧼 Resetting FEATURE branch (abort merges/rebases, clean untracked)..."
git rebase --abort 2>/dev/null || true
git merge  --abort 2>/dev/null || true
git reset --hard
git clean -fd

git checkout -B feature
sed -i '' 's/line1: base/line1: FEATURE async+logger/' app.txt 2>/dev/null || sed -i 's/line1: base/line1: FEATURE async+logger/' app.txt

cat >> app.txt <<'EOF'

// feature adds minor smells
function featureStuff() {
  var unused = 123;
  try { JSON.parse("{bad json"); } catch (e) {}
}
EOF

echo " Committing FEATURE-side conflicting code..."
git add app.txt
git commit -m "feature: unused var + empty catch + conflict"
git branch --set-upstream-to=origin/main feature

# 4️⃣ Run merj to simulate pull + conflict resolution
echo "⚔️  Triggering merj pull to produce conflict & run CodeRabbit..."
merj pull --main origin/main || true

echo "Full demo complete!"
echo
echo "Main repo:    $MAIN"
echo "Feature repo: $FEAT"
echo "Remote repo:  $(ls -d /tmp/merj-demo.*/remote.git | head -1)"
```

### lib/auth.js

```javascript
const fs = require('fs');
const path = require('path');
const os = require('os');
const { Octokit } = require('@octokit/rest');

// Path to config file in user's home directory
const CONFIG_FILE = path.join(os.homedir(), '.merjrc');

/**
 * Get stored GitHub token from config file
 */
function getStoredToken() {
  try {
    if (fs.existsSync(CONFIG_FILE)) {
      const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
      return config.github?.token || null;
    }
  } catch (error) {
    console.error('Error reading config file:', error.message);
  }
  return null;
}

/**
 * Store GitHub token in config file
 */
function storeToken(token) {
  try {
    const config = {
      github: {
        token: token
      }
    };
    fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
    // Set restrictive permissions (read/write for owner only)
    fs.chmodSync(CONFIG_FILE, 0o600);
    return true;
  } catch (error) {
    console.error('Error storing token:', error.message);
    return false;
  }
}

/**
 * Initialize authenticated GitHub client
 */
function getAuthenticatedClient() {
  const token = getStoredToken();
  
  if (!token) {
    return null;
  }
  
  return new Octokit({
    auth: token
  });
}

/**
 * Test authentication by making a simple API call
 */
async function testAuthentication() {
  const octokit = getAuthenticatedClient();
  
  if (!octokit) {
    return { authenticated: false, error: 'No token found' };
  }
  
  try {
    const { data } = await octokit.rest.users.getAuthenticated();
    return { 
      authenticated: true, 
      username: data.login,
      user: data
    };
  } catch (error) {
    return { 
      authenticated: false, 
      error: error.message 
    };
  }
}

module.exports = {
  getStoredToken,
  storeToken,
  getAuthenticatedClient,
  testAuthentication,
  CONFIG_FILE
};


```

### rag_pipeline/embedder.py

```python
#!/usr/bin/env python3
"""
embedder.py - Simple code embedding using Voyager AI Code-3.
"""

import os
from typing import List, Dict
import voyageai


def embed_chunk(chunk, api_key: str = None) -> List[float]:
    """
    Embed a single code chunk.

    Args:
        chunk: A CodeChunk object with 'content' attribute
        api_key: Voyager AI API key (or set VOYAGE_API_KEY env var)

    Returns:
        List of floats representing the embedding vector (1024 dimensions)
    """
    # Get API key
    key = api_key or os.environ.get("VOYAGE_API_KEY")
    if not key:
        raise ValueError("No API key provided. Pass api_key or set VOYAGE_API_KEY")

    # Initialize client
    client = voyageai.Client(api_key=key)

    # Embed the chunk content
    result = client.embed(
        [chunk.content],  # API expects a list
        model="voyage-code-3",
        input_type="document"
    )

    # Return the embedding vector
    return result.embeddings[0] if result.embeddings else []


def embed_chunks(chunks: List, api_key: str = None) -> List[Dict]:
    """
    Embed all chunks.

    Args:
        chunks: List of CodeChunk objects
        api_key: Voyager AI API key (or set VOYAGE_API_KEY env var)

    Returns:
        List of dictionaries with 'chunk' and 'embedding' keys
    """
    # Get API key
    key = api_key or os.environ.get("VOYAGE_API_KEY")
    if not key:
        raise ValueError("No API key provided. Pass api_key or set VOYAGE_API_KEY")

    # Initialize client
    client = voyageai.Client(api_key=key)

    # Collect all content
    texts = [chunk.content for chunk in chunks]

    # Embed all at once (more efficient than one by one)
    result = client.embed(
        texts,
        model="voyage-code-3",
        input_type="document"
    )

    # Combine chunks with their embeddings
    embedded = []
    for chunk, embedding in zip(chunks, result.embeddings):
        embedded.append({
            "chunk": chunk,
            "embedding": embedding
        })

    return embedded


# Simple usage example
if __name__ == "__main__":
    print("Simple embedder for code chunks")
    print("Usage:")
    print("  from embedder import embed_chunk, embed_chunks")
    print("  vector = embed_chunk(chunk, api_key='...')")
    print("  results = embed_chunks(chunks, api_key='...')")
```

### scripts/demo_conflict.sh

```shell
#!/usr/bin/env bash
set -euo pipefail

# ── paths
ROOT="$(pwd)"
TMP_BASE="$(mktemp -d /tmp/merj-demo.XXXXXX)"
REMOTE="$TMP_BASE/remote.git"
WORK_MAIN="$TMP_BASE/work-main"
WORK_FEAT="$TMP_BASE/work-feature"

echo "📦 Creating bare remote at $REMOTE"
git init --bare "$REMOTE" >/dev/null

echo "Seeding repo (initial commit on main)"
SEED="$TMP_BASE/seed"
git clone "$REMOTE" "$SEED" >/dev/null
pushd "$SEED" >/dev/null
  git checkout -b main >/dev/null
  echo 'line1: base' > app.txt
  echo 'line2: stable' >> app.txt
  git add app.txt
  git commit -m "initial" >/dev/null
  git push -u origin main >/dev/null
popd >/dev/null

echo "🧭 Clone for main workflow → $WORK_MAIN"
git clone "$REMOTE" "$WORK_MAIN" >/dev/null

echo "Clone for feature workflow → $WORK_FEAT"
git clone "$REMOTE" "$WORK_FEAT" >/dev/null

echo " Make change on main (conflicting line)"
pushd "$WORK_MAIN" >/dev/null
  git checkout main >/dev/null
  sed -i '' 's/line1: base/line1: MAIN adds retry/' app.txt 2>/dev/null || true
  if ! grep -q 'MAIN adds retry' app.txt; then
    # Linux sed
    sed -i 's/line1: base/line1: MAIN adds retry/' app.txt
  fi
  git add app.txt
  git commit -m "main: change line1" >/dev/null
  git push >/dev/null
popd >/dev/null

echo "Create feature branch from old base and change same line differently"
pushd "$WORK_FEAT" >/dev/null
  git checkout -b feature >/dev/null
  sed -i '' 's/line1: base/line1: FEATURE adds async+logger/' app.txt 2>/dev/null || true
  if ! grep -q 'FEATURE adds async+logger' app.txt; then
    sed -i 's/line1: base/line1: FEATURE adds async+logger/' app.txt
  fi
  git add app.txt
  git commit -m "feature: change line1 differently" >/dev/null

  echo
  echo "Running: merj pull --main=origin/main  (should cause a conflict)"
  echo
  # Use your globally linked CLI
  merj pull --main=origin/main || true

  echo
  echo "Check for CodeRabbit outputs (if CLI installed):"
  ls -l /tmp/coderabbit_main.json /tmp/coderabbit_local.json 2>/dev/null || echo "(No CodeRabbit JSONs; either no CLI or no findings.)"
  echo

  echo "🔧 Resolve the conflict however your flow proceeds next (LLM/keep-local/keep-incoming), then:"
  echo "   git add app.txt && git merge --continue"
popd >/dev/null

echo
echo "Demo setup complete."
echo "   Feature repo: $WORK_FEAT"
echo "   Main repo:    $WORK_MAIN"
echo "   Remote:       $REMOTE"


```

[9 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]