Here's the complete refactored package structure. Each file follows a single responsibility principle, uses relative imports, and maintains a clean public API.

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

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

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

### `queue/core.py`
```python
"""Core job dataclass and basic FIFO queue implementation."""

from dataclasses import dataclass, field
from datetime import datetime
from queue import Queue
from typing import Any, Optional


@dataclass
class Job:
    """Represents a unit of work to be processed."""
    id: str
    payload: Any
    status: str = "pending"
    created_at: datetime = field(default_factory=datetime.now)
    retries: int = 0
    max_retries: int = 3

    def __repr__(self) -> str:
        return f"Job(id={self.id!r}, status={self.status!r}, retries={self.retries})"


class JobQueue:
    """Thread-safe FIFO queue for Job instances."""

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

    def enqueue(self, job: Job) -> None:
        """Add a job to the end of the queue."""
        self._queue.put(job)

    def dequeue(self) -> Optional[Job]:
        """Remove and return the next job, or None if empty."""
        try:
            return self._queue.get_nowait()
        except Exception:
            return None

    def is_empty(self) -> bool:
        """Check if the queue contains no jobs."""
        return self._queue.empty()

    def size(self) -> int:
        """Return the number of jobs in the queue."""
        return self._queue.qsize()
```

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

import heapq
from typing import Any, Optional

from .core import Job, JobQueue


class PriorityQueue(JobQueue):
    """Queue that processes jobs based on priority (lower number = higher priority)."""

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

    def enqueue(self, job: Job, priority: int = 0) -> None:
        """Add a job with a given priority."""
        self._counter += 1
        # Tuple: (priority, insertion_order, job)
        heapq.heappush(self._heap, (priority, self._counter, job))

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

    def is_empty(self) -> bool:
        return len(self._heap) == 0

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

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

import logging
import time
from typing import Any, Callable, Optional

from .core import Job

logger = logging.getLogger(__name__)


def process_with_retry(
    job: Job,
    handler: Callable[[Job], Any],
    max_retries: Optional[int] = None
) -> Any:
    """
    Execute a job handler with exponential backoff retry logic.

    Args:
        job: The job to process.
        handler: Callable that accepts a Job and returns its result.
        max_retries: Override job's default max_retries.

    Returns:
        The result of the handler on success.

    Raises:
        Exception: The last exception encountered after exhausting retries.
    """
    retries = max_retries if max_retries is not None else job.max_retries
    last_exception: Optional[Exception] = None

    for attempt in range(retries + 1):
        try:
            result = handler(job)
            job.status = "completed"
            return result
        except Exception as e:
            last_exception = e
            job.retries += 1
            job.status = "failed"

            if attempt < retries:
                backoff = 2 ** attempt  # 1s, 2s, 4s, 8s...
                logger.warning(
                    f"Job {job.id} failed (attempt {attempt + 1}/{retries + 1}). "
                    f"Retrying in {backoff}s..."
                )
                time.sleep(backoff)
            else:
                logger.error(
                    f"Job {job.id} failed after {retries + 1} attempts. "
                    f"Final error: {e}"
                )
                job.status = "exhausted"

    raise last_exception
```

### Key Design Decisions:
1. **Single Responsibility**: 
   - `core.py` handles job definition & basic FIFO operations
   - `priority.py` handles ordering logic via `heapq`
   - `retry.py` handles execution resilience & backoff
2. **Clean Imports**: All inter-module imports use relative syntax (`.core`, `.priority`), making the package self-contained and easy to move.
3. **Public API**: `__init__.py` explicitly exports only what consumers need, hiding internal implementation details.
4. **Type Safety**: Full type hints with `typing` module for better IDE support and static analysis.
5. **Thread Safety**: `queue.Queue` is used in `core.py` for safe concurrent access. `PriorityQueue` uses `heapq` (not thread-safe by default); add `threading.Lock` if concurrent priority operations are needed.
6. **Naming Note**: The directory name `queue/` conflicts with Python's standard library `queue` module. In production, consider renaming to `jobqueue/` or `taskqueue/` to avoid shadowing issues.