This refactoring separates concerns effectively. Here is the content for each file in the `queue/` package.

***

### 📁 `queue/priority.py`

This module handles the underlying heap structure necessary for priority queuing.

```python
import heapq
from typing import List, Tuple, Any

class PriorityQueue:
    """
    A min-heap implementation used to store Jobs based on their priority.
    Lower priority number means higher priority.
    """
    def __init__(self):
        # The heap stores tuples: (priority, insertion_index, job)
        # insertion_index is used to maintain stable ordering for items with the same priority.
        self._queue: List[Tuple[int, int, Any]] = []
        self._counter = 0

    def put(self, priority: int, job: Any) -> None:
        """Adds a job to the queue."""
        heapq.heappush(self._queue, (priority, self._counter, job))
        self._counter += 1

    def get(self) -> Any:
        """Removes and returns the job with the highest priority."""
        if not self._queue:
            raise IndexError("get from empty priority queue")
        # The job object is the third element in the tuple
        return heapq.heappop(self._queue)[2]

    def empty(self) -> bool:
        """Checks if the queue is empty."""
        return not self._queue
    
    def __len__(self) -> int:
        return len(self._queue)

# Example usage (for testing/internal use):
# pq = PriorityQueue()
# pq.put(5, "Low priority task")
# pq.put(1, "High priority task")
# print(pq.get()) # Output: High priority task
```

### 📁 `queue/core.py`

This module defines the fundamental data structures (`Job`) and the main queue manager (`JobQueue`), which uses `PriorityQueue`.

```python
from dataclasses import dataclass
from queue.priority import PriorityQueue
import time
from typing import Callable, Any

@dataclass(frozen=True)
class Job:
    """
    Represents a single task to be executed by the queue.
    """
    task_id: str
    func: Callable[..., Any]
    args: tuple = ()
    kwargs: dict = None
    priority: int = 0
    max_retries: int = 3
    
    def __post_init__(self):
        if self.kwargs is None:
            self.kwargs = {}

class JobQueue:
    """
    Manages the collection of jobs and provides methods for adding and retrieving them.
    """
    def __init__(self):
        self._queue = PriorityQueue()
        print("JobQueue initialized.")

    def enqueue(self, job: Job) -> None:
        """
        Adds a Job object to the queue based on its priority.
        """
        self._queue.put(job.priority, job)
        print(f"Enqueued job {job.task_id} with priority {job.priority}.")

    def dequeue(self) -> Job:
        """
        Retrieves and removes the highest priority Job from the queue.
        Raises IndexError if the queue is empty.
        """
        return self._queue.get()

    def is_empty(self) -> bool:
        """Checks if the queue contains any jobs."""
        return self._queue.empty()
    
    def __len__(self) -> int:
        return len(self._queue)
```

### 📁 `queue/retry.py`

This module encapsulates the complex logic for handling retries and exponential backoff, keeping the core queue logic clean.

```python
import time
import random
from typing import Callable, Any

def process_with_retry(job: 'Job') -> Any:
    """
    Executes a job function, handling failures using exponential backoff.
    
    Args:
        job: The Job object containing the function and arguments.
        
    Returns:
        The result of the function execution.
        
    Raises:
        Exception: If the job fails after exhausting all retries.
    """
    max_retries = job.max_retries
    base_delay = 2  # seconds
    
    for attempt in range(max_retries + 1):
        try:
            print(f"\n--- Executing Job {job.task_id} (Attempt {attempt + 1}/{max_retries + 1}) ---")
            
            # Execute the job function
            result = job.func(*job.args, **job.kwargs)
            
            print(f"Job {job.task_id} completed successfully.")
            return result

        except Exception as e:
            print(f"Job {job.task_id} failed on attempt {attempt + 1}. Error: {e}")
            
            if attempt < max_retries:
                # Calculate exponential backoff delay: 2^attempt + jitter
                delay = (base_delay ** attempt) + random.uniform(0, 1)
                print(f"Retrying job {job.task_id} in {delay:.2f} seconds...")
                time.sleep(delay)
            else:
                print(f"Job {job.task_id} failed permanently after {max_retries + 1} attempts.")
                # Re-raise the exception if all retries are exhausted
                raise e

```

### 📁 `queue/__init__.py`

This file serves as the public API entry point, making the core classes easily accessible without requiring users to know the internal module structure.

```python
from .core import Job, JobQueue
from .retry import process_with_retry

# Define what is exposed when 'from queue import *' is used
__all__ = [
    "Job",
    "JobQueue",
    "process_with_retry"
]

# Optionally, provide a simple convenience function for quick setup
def create_job(task_id: str, func: callable, priority: int = 0, **kwargs) -> Job:
    """Helper function to quickly create a Job instance."""
    return Job(task_id=task_id, func=func, kwargs=kwargs, priority=priority)
```

***

### Summary of Responsibilities

| File | Responsibility | Key Components | Dependencies |
| :--- | :--- | :--- | :--- |
| `priority.py` | Heap/Priority Management | `PriorityQueue` | `heapq` |
| `core.py` | Data & Queue Structure | `Job` (dataclass), `JobQueue` | `PriorityQueue` |
| `retry.py` | Execution & Failure Handling | `process_with_retry` (function) | `time`, `random` |
| `__init__.py` | Public API Exposure | `Job`, `JobQueue`, `process_with_retry` | `.core`, `.retry` |