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

### `queue/__init__.py`
```python
"""
Job Queue Package
-----------------
Provides FIFO and priority-based job queues with built-in retry logic.
"""
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
from collections import deque
import uuid
import time


@dataclass
class Job:
    """Represents a unit of work to be processed."""
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    payload: Any = None
    priority: int = 0
    status: str = "pending"
    retry_count: int = 0
    created_at: float = field(default_factory=time.time)


class JobQueue:
    """Basic FIFO job queue implementation."""
    
    def __init__(self) -> None:
        self._queue: deque[Job] = deque()

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

    def dequeue(self) -> Optional[Job]:
        """Remove and return the first job in the queue."""
        if self._queue:
            return self._queue.popleft()
        return None

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

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

### `queue/priority.py`
```python
"""Priority queue implementation using a min-heap."""
import heapq
from typing import Optional
from .core import Job


class PriorityQueue:
    """Priority-based job queue. Lower priority values are dequeued first."""
    
    def __init__(self) -> None:
        # Heap stores tuples of (priority, insertion_order, job)
        self._heap: list[tuple[int, int, Job]] = []
        self._counter: int = 0

    def enqueue(self, job: Job) -> None:
        """Add a job to the queue respecting its priority."""
        heapq.heappush(self._heap, (job.priority, self._counter, job))
        self._counter += 1

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

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

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

### `queue/retry.py`
```python
"""Retry logic with exponential backoff."""
import time
from typing import Callable, Any, Optional
from .core import Job


def process_with_retry(
    job: Job,
    func: Callable[..., Any],
    max_retries: int = 3,
    base_delay: float = 1.0
) -> Any:
    """
    Execute a job's function with exponential backoff retry logic.
    
    Args:
        job: The Job instance to process.
        func: Callable that accepts the job's payload.
        max_retries: Maximum number of retry attempts.
        base_delay: Initial delay in seconds before first retry.
        
    Returns:
        The result of the function call.
        
    Raises:
        Exception: The last exception raised if all retries are exhausted.
    """
    last_exception: Optional[Exception] = None

    for attempt in range(max_retries + 1):
        try:
            job.status = "running"
            result = func(job.payload)
            job.status = "completed"
            return result
        except Exception as e:
            last_exception = e
            job.retry_count = attempt + 1
            job.status = "failed"
            
            if attempt < max_retries:
                delay = base_delay * (2 ** attempt)
                time.sleep(delay)
            else:
                raise last_exception
```

### Key Design Decisions:
1. **Relative Imports**: All internal modules use `.` prefix imports (e.g., `from .core import Job`) which is the standard for Python packages.
2. **Interface Consistency**: `JobQueue` and `PriorityQueue` share the same public methods (`enqueue`, `dequeue`, `is_empty`, `size`), allowing them to be used interchangeably where appropriate.
3. **Stable Priority Sorting**: `PriorityQueue` uses a counter alongside the priority value in the heap to guarantee FIFO ordering for jobs with equal priority.
4. **State Tracking**: `process_with_retry` mutates the `Job` instance to reflect `status` and `retry_count`, keeping execution state synchronized with the data model.
5. **Clean Public API**: `__init__.py` re-exports only the intended public components via `__all__`, keeping internal implementation details hidden from package consumers.

### Usage Example:
```python
from queue import Job, PriorityQueue, process_with_retry

def send_email(payload: dict) -> str:
    if payload.get("fail"):
        raise ConnectionError("SMTP timeout")
    return "sent"

pq = PriorityQueue()
job = Job(payload={"to": "user@example.com", "fail": True})
pq.enqueue(job)

retrieved_job = pq.dequeue()
process_with_retry(retrieved_job, send_email, max_retries=2, base_delay=0.1)
```