Here's the complete refactored package. Each file is designed with a single responsibility, proper relative imports, type hints, and a clean public API.

### `queue/__init__.py`
```python
"""Public API for the job queue package."""

from .core import Job, JobQueue
from .priority import PriorityQueue
from .retry import process_with_retry

__all__ = ["Job", "JobQueue", "PriorityQueue", "process_with_retry"]
```

### `queue/core.py`
```python
"""Core data structures and basic FIFO queue operations."""

from dataclasses import dataclass, field
from typing import Any, Optional, Iterator
from collections import deque


@dataclass
class Job:
    """Represents a unit of work to be processed."""
    id: str
    payload: Any
    priority: int = 0
    status: str = "pending"
    retry_count: int = 0
    max_retries: int = 3
    result: Optional[Any] = None
    error: Optional[str] = None


class JobQueue:
    """Basic FIFO queue for Job instances."""

    def __init__(self) -> None:
        self._queue: deque[Job] = deque()

    def add(self, job: Job) -> None:
        """Add a job to the end of the queue."""
        job.status = "pending"
        self._queue.append(job)

    def remove(self) -> Optional[Job]:
        """Remove and return the job at the front of the queue."""
        return self._queue.popleft() if self._queue else None

    def peek(self) -> Optional[Job]:
        """Return the job at the front without removing it."""
        return self._queue[0] if self._queue else None

    def is_empty(self) -> bool:
        """Check if the queue contains any jobs."""
        return len(self._queue) == 0

    def __len__(self) -> int:
        return len(self._queue)

    def __iter__(self) -> Iterator[Job]:
        return iter(self._queue)
```

### `queue/priority.py`
```python
"""Priority-based queue implementation using a min-heap."""

import heapq
from typing import Optional, Iterator
from .core import Job


class PriorityQueue:
    """Queue that orders jobs by priority (lower value = higher priority)."""

    def __init__(self) -> None:
        self._heap: list[tuple[int, int, Job]] = []
        self._counter: int = 0  # Stable ordering for equal priorities

    def add(self, job: Job) -> None:
        """Add a job to the priority queue."""
        job.status = "pending"
        # Tuple format: (priority, insertion_order, job)
        heapq.heappush(self._heap, (job.priority, self._counter, job))
        self._counter += 1

    def remove(self) -> Optional[Job]:
        """Remove and return the highest priority job."""
        if not self._heap:
            return None
        _, _, job = heapq.heappop(self._heap)
        return job

    def peek(self) -> Optional[Job]:
        """Return the highest priority job without removing it."""
        return self._heap[0][2] if self._heap else None

    def is_empty(self) -> bool:
        """Check if the priority queue is empty."""
        return len(self._heap) == 0

    def __len__(self) -> int:
        return len(self._heap)

    def __iter__(self) -> Iterator[Job]:
        # Return jobs in priority order without modifying the heap
        yield from (job for _, _, job in sorted(self._heap))
```

### `queue/retry.py`
```python
"""Retry logic with exponential backoff for job processing."""

import time
from typing import Callable, Any, Optional
from .core import Job


def process_with_retry(
    job: Job,
    func: Callable[[Any], Any],
    max_retries: Optional[int] = None,
    base_delay: float = 1.0,
    backoff_factor: float = 2.0
) -> Job:
    """
    Execute a function with exponential backoff retry logic.
    
    Args:
        job: The job to process
        func: Callable that accepts job.payload and returns a result
        max_retries: Override job's max_retries (defaults to job.max_retries)
        base_delay: Initial delay in seconds
        backoff_factor: Multiplier for each subsequent retry
        
    Returns:
        The updated Job instance with result or error state
    """
    retries = max_retries if max_retries is not None else job.max_retries
    
    for attempt in range(retries + 1):
        try:
            job.status = "running"
            job.result = func(job.payload)
            job.status = "completed"
            return job
        except Exception as e:
            job.error = str(e)
            job.retry_count = attempt + 1
            job.status = "failed"
            
            if attempt == retries:
                break
                
            # Exponential backoff: base_delay * (backoff_factor ^ attempt)
            delay = base_delay * (backoff_factor ** attempt)
            time.sleep(delay)
            job.status = "pending"  # Reset for next attempt
            
    return job
```

### Key Design Decisions:
1. **Single Responsibility**: 
   - `core.py` handles data modeling & basic FIFO operations
   - `priority.py` isolates heap-based ordering logic
   - `retry.py` contains only execution & backoff mechanics
   - `__init__.py` acts as a clean facade
2. **Relative Imports**: All cross-module imports use `.` notation, making the package self-contained and importable as `queue.core`, etc.
3. **No Side Effects in Queues**: `remove()` simply returns the job without mutating its status. Status transitions are handled by the retry processor or caller, keeping queues stateless.
4. **Stable Priority Ordering**: The `_counter` in `PriorityQueue` prevents `heapq` comparison errors when two jobs share the same priority.
5. **Type Safety**: Full type hints with `typing` module for modern IDE support and static analysis.