Here's the refactored package structure with clean separation of concerns, proper imports, and a public API aligned with your requirements.

### 📁 `queue/__init__.py`
```python
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
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Any, Optional
import uuid

@dataclass
class Job:
    """Represents a unit of work to be processed."""
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    payload: Any = None
    status: str = "pending"  # pending | processing | completed | failed
    created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    attempts: int = 0
    max_retries: int = 3
    error: Optional[str] = None


class JobQueue:
    """Basic FIFO job queue implementation."""
    def __init__(self):
        self._jobs: list[Job] = []

    def add(self, job: Job) -> None:
        self._jobs.append(job)

    def pop(self) -> Optional[Job]:
        return self._jobs.pop(0) if self._jobs else None

    def peek(self) -> Optional[Job]:
        return self._jobs[0] if self._jobs else None

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

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

### 📁 `queue/priority.py`
```python
import heapq
from typing import Optional
from .core import Job, JobQueue


class PriorityQueue(JobQueue):
    """Priority-based job queue using a min-heap.
    Lower priority values are processed first.
    """
    def __init__(self):
        self._heap: list[tuple[int, int, Job]] = []
        self._counter: int = 0  # Tie-breaker to ensure stable ordering

    def add(self, job: Job, priority: int = 0) -> None:
        entry = (priority, self._counter, job)
        self._counter += 1
        heapq.heappush(self._heap, entry)

    def pop(self) -> Optional[Job]:
        if self._heap:
            _, _, job = heapq.heappop(self._heap)
            return job
        return None

    def peek(self) -> Optional[Job]:
        return self._heap[0][2] if self._heap else None

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

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

### 📁 `queue/retry.py`
```python
import time
from typing import Callable, Optional
from .core import Job


def process_with_retry(
    job: Job,
    func: Callable,
    max_retries: Optional[int] = None,
    base_delay: float = 1.0,
    max_delay: float = 60.0
) -> Job:
    """Execute a function with exponential backoff retry logic.
    
    Args:
        job: The job instance to update with status/attempts/errors.
        func: Callable that receives job.payload.
        max_retries: Override job.max_retries. Defaults to job.max_retries.
        base_delay: Initial delay in seconds.
        max_delay: Maximum delay cap in seconds.
        
    Returns:
        The updated Job instance.
        
    Raises:
        Exception: Re-raises the last exception after max retries exhausted.
    """
    retries = max_retries if max_retries is not None else job.max_retries

    for attempt in range(1, retries + 2):
        try:
            result = func(job.payload)
            job.status = "completed"
            job.attempts = attempt
            return job
        except Exception as e:
            job.attempts = attempt
            job.error = str(e)
            if attempt <= retries:
                delay = min(base_delay * (2 ** (attempt - 1)), max_delay)
                time.sleep(delay)
            else:
                job.status = "failed"
                raise
    return job
```

### 🔑 Key Design Decisions:
1. **Single Responsibility**: 
   - `core.py` holds the data model and basic FIFO queue
   - `priority.py` handles heap-based ordering without polluting core logic
   - `retry.py` isolates exponential backoff, making it reusable across different queue types
2. **Proper Imports**: All cross-module dependencies use relative imports (`from .core import ...`), avoiding circular imports and keeping the package self-contained.
3. **Clean Public API**: `__init__.py` explicitly defines `__all__` and only exposes the intended public interface.
4. **Type Safety**: Full type hints with modern Python 3.7+ features (`list[Job]`, `Optional`, etc.)
5. **Stable Priority Ordering**: Added a monotonic `_counter` in `PriorityQueue` to break ties deterministically when priorities match.

> 💡 **Note**: `queue` is a Python standard library module. In production, consider naming the package `job_queue` or `task_queue` to avoid shadowing `queue.Queue`. The structure above works identically with either name.