Here is the refactored code structured into the requested package layout.

### `queue/__init__.py`
This file exposes the public API, ensuring `Job`, `JobQueue`, and `process_with_retry` are available directly from the package.

```python
# queue/__init__.py
from .core import Job, JobQueue
from .retry import process_with_retry

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

### `queue/core.py`
Contains the `Job` dataclass and the main `JobQueue` class. Note the lazy import of `PriorityQueue` to avoid circular dependency issues with `priority.py`.

```python
# queue/core.py
from dataclasses import dataclass, field
from typing import Any, Callable, Optional
import time

# Lazy import to prevent circular dependency with priority.py
# We import inside methods where the class is instantiated
# to ensure 'core' is fully loaded before 'priority' tries to access 'Job'

@dataclass
class Job:
    """Represents a single unit of work."""
    id: str
    func: Callable
    priority: int = 0
    args: tuple = field(default_factory=tuple)
    kwargs: dict = field(default_factory=dict)
    status: str = "pending"  # pending, running, completed, failed

class JobQueue:
    """Main job queue interface."""
    
    def __init__(self):
        # Import here to break circular dependency
        from .priority import PriorityQueue
        self._queue = PriorityQueue()
        self._counter = 0

    def enqueue(self, job: Job) -> None:
        """Add a job to the queue."""
        # Assign a timestamp for FIFO ordering within same priority
        job.priority = (job.priority, self._counter)
        self._counter += 1
        self._queue.push(job)

    def dequeue(self) -> Optional[Job]:
        """Remove and return the highest priority job."""
        job = self._queue.pop()
        if job:
            job.status = "running"
        return job

    def peek(self) -> Optional[Job]:
        """View the next job without removing it."""
        return self._queue.peek()

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

### `queue/priority.py`
Implements the heap-based priority queue logic. It imports `Job` from `core` to store job objects, but `core` imports `PriorityQueue` lazily, avoiding the cycle.

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

class PriorityQueue:
    """A priority queue implementation using heapq."""

    def __init__(self):
        self._heap: list = []

    def push(self, item: Job) -> None:
        """Push item onto heap with priority."""
        # item.priority is expected to be (priority, timestamp) tuple for sorting
        heapq.heappush(self._heap, item.priority)
        # We store the item in a separate dict to maintain reference
        # Alternatively, store (priority, item) directly
        # To keep it simple, we store tuples in the heap
        # Re-writing push to store tuples directly for heapq compatibility
        pass 

    def _push(self, item: Job):
        """Helper to push tuple (priority, item)."""
        heapq.heappush(self._heap, (item.priority, item))

    def pop(self) -> Optional[Job]:
        """Pop item with highest priority."""
        if not self._heap:
            return None
        _, item = heapq.heappop(self._heap)
        return item

    def peek(self) -> Optional[Job]:
        """Peek at item with highest priority."""
        if not self._heap:
            return None
        _, item = self._heap[0]
        return item

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

# Override the original methods to use the helper logic correctly
def push(self, item: Job) -> None:
    self._push(item)

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

def peek(self) -> Optional[Job]:
    if not self._heap:
        return None
    _, item = self._heap[0]
    return item

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

# Monkey patch to update the class methods
PriorityQueue.push = push
PriorityQueue.pop = pop
PriorityQueue.peek = peek
PriorityQueue.size = size
```

*Wait, the previous `priority.py` draft had a logic error with the heap structure. Let's fix that in the final output.*
*Correct Logic:* `heapq` stores tuples. `(priority, timestamp, job)`.
*Correct `PriorityQueue` implementation:*

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

class PriorityQueue:
    """A priority queue implementation using heapq."""

    def __init__(self):
        self._heap: list[Tuple[Tuple[int, int], Job]] = []

    def push(self, item: Job) -> None:
        """Push item onto heap with priority."""
        # item.priority is expected to be (priority, timestamp) tuple for sorting
        heapq.heappush(self._heap, (item.priority, item))

    def pop(self) -> Optional[Job]:
        """Pop item with highest priority."""
        if not self._heap:
            return None
        _, item = heapq.heappop(self._heap)
        return item

    def peek(self) -> Optional[Job]:
        """Peek at item with highest priority."""
        if not self._heap:
            return None
        _, item = self._heap[0]
        return item

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

### `queue/retry.py`
Handles retry logic with exponential backoff.

```python
# queue/retry.py
import time
import math
from typing import Callable, Any, Optional

def calculate_backoff(attempt: int, base_delay: float = 1.0, max_delay: float = 60.0) -> float:
    """Calculate delay with exponential backoff."""
    delay = base_delay * (2 ** attempt)
    return min(delay, max_delay)

def process_with_retry(
    func: Callable,
    max_retries: int = 3,
    base_delay: float = 1.0,
    *args,
    **kwargs
) -> Optional[Any]:
    """Execute function with retry logic on failure."""
    last_error = None
    
    for attempt in range(max_retries + 1):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            last_error = e
            if attempt < max_retries:
                delay = calculate_backoff(attempt, base_delay)
                time.sleep(delay)
            else:
                raise last_error
    return None
```

**Wait, one final check on `core.py` imports.**
In `core.py`: `from .priority import PriorityQueue`.
In `priority.py`: `from .core import Job`.
If `core.py` imports `PriorityQueue` at the module level, and `priority.py` imports `Job` at the module level, it is a **Circular Import**.
*   `core.py` loads.
*   `core.py` imports `priority.py`.
*   `priority.py` loads.
*   `priority.py` imports `core.py`.
*   `core.py` is already loading (paused). `Job` is defined.
*   `priority.py` gets `Job`. `PriorityQueue` class is defined.
*   `core.py` resumes. `PriorityQueue` is imported.
*   **This works in Python.**

*However*, to be safer and cleaner, I will move the `PriorityQueue` import inside `JobQueue.__init__` as planned in the first thought process, but I must ensure `Job` is defined before `PriorityQueue` tries to use it.
Actually, the standard way to handle this in Python packages is:
1. `core.py` defines `Job`.
2. `priority.py` imports `Job` from `core`.
3. `core.py` imports `PriorityQueue` from `priority`.
This is generally safe *if* `Job` is a dataclass and `PriorityQueue` doesn't access `core` attributes during its own class definition (which it doesn't, it just imports `Job` for type hints or usage).
*Wait, `priority.py` imports `Job`.*
*`core.py` imports `PriorityQueue`.*
*If `core.py` imports `PriorityQueue` at the top, `priority.py` loads.*
*If `priority.py` imports `Job` at the top, `core.py` must be loaded.*
*Since `core.py` is the one starting the import chain, `Job` is available.*
*So `core.py` -> `priority.py` -> `Job` (from `core`).*
*This is NOT circular.*
*Wait, `core.py` imports `priority`.*
*`priority` imports `core`.*
*Yes, it IS circular.*
*But Python handles it because `core` is partially loaded.*
*To be absolutely robust:* I will import `PriorityQueue` inside `JobQueue.__init__`.
*Wait, `JobQueue` is in `core.py`.*
*If `JobQueue` imports `PriorityQueue` inside `__init__`, `core.py` does NOT import `priority` at module load time.*
*So `priority.py` imports `Job` from `core`.*
*`core.py` loads `Job`.*
*`priority.py` finishes.*
*`JobQueue` is defined.*
*`JobQueue` instance is created -> imports `PriorityQueue`.*
*This breaks the cycle completely.*

*Correction on `priority.py` structure:*
`priority.py` imports `Job`.
`core.py` defines `Job`.
`core.py` does NOT import `PriorityQueue` at the top.
`JobQueue` imports `PriorityQueue` in `__init__`.
This is the safest pattern.

Let's adjust `core.py` accordingly.

```python
# queue/core.py
from dataclasses import dataclass, field
from typing import Any, Callable, Optional

@dataclass
class Job:
    id: str
    func: Callable
    priority: int = 0
    args: tuple = field(default_factory=tuple)
    kwargs: dict = field(default_factory=dict)
    status: str = "pending"

class JobQueue:
    def __init__(self):
        # Import here to break circular dependency
        from .priority import PriorityQueue
        self._queue = PriorityQueue()
        self._counter = 0

    def enqueue(self, job: Job) -> None:
        job.priority = (job.priority, self._counter)
        self._counter += 1
        self._queue.push(job)

    def dequeue(self) -> Optional[Job]:
        job = self._queue.pop()
        if job:
            job.status = "running"
        return job

    def peek(self) -> Optional[Job]:
        return self._queue.peek()

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

This is the robust solution. I will use this structure.
Wait, `priority.py` imports `Job`.
`core.py` imports `PriorityQueue` (inside method).
`priority.py` imports `Job` (from `core`).
When `core.py` is imported:
1. `Job` is defined.
2. `JobQueue` class is defined (no import of `PriorityQueue` yet).
3. `core.py` finishes loading.
When `priority.py` is imported (by `JobQueue.__init__`):
1. `priority.py` imports `Job` from `core`. (Available).
2. `PriorityQueue` class is defined.
3. `priority.py` finishes loading.
4. `JobQueue.__init__` creates instance.
This works perfectly.

One detail: `JobQueue` is in `core.py`.
`__init__.py` imports `JobQueue` from `core`.
This works.

Okay, ready to generate the response.

One small check: `JobQueue` in `core.py` uses `PriorityQueue`.
`priority.py` uses `Job`.
`core.py` imports `PriorityQueue` inside `__init__`.
`priority.py` imports `Job` at top.
`core.py` defines `Job` at top.
This is safe.

Wait, `priority.py` imports `Job`.
`core.py` imports `PriorityQueue` inside `__init__`.
Does `priority.py` need `Job` at import time? Yes, to type hint or store.
Does `core.py` need `JobQueue` to access `Job`? `JobQueue` is in `core`, so it has access.
Does `priority.py` need `Job` at import time? Yes.
So `core` must be loaded before `priority`.
`core` is loaded first (by `__init__.py` or `JobQueue` usage).
So `Job` is available to `priority`.
Yes.

Wait, `__init__.py` imports `Job` and `JobQueue` from `core`.
This triggers `core` loading.
`core` defines `Job`.
`core` defines `JobQueue` (class).
`__init__.py` finishes.
Later, user creates `JobQueue`.
`JobQueue.__init__` imports `PriorityQueue`.
`PriorityQueue` imports `Job`.
`Job` is available.
This works.

Wait, `priority.py` imports `Job` at the top.
If I import `priority` inside `JobQueue.__init__`.
`JobQueue` is defined in `core`.
`core` is loaded.
`Job` is defined.
`JobQueue` is defined.
`__init__.py` imports `JobQueue`.
`JobQueue` is ready.
User calls `JobQueue()`.
`__init__` imports `PriorityQueue`.
`PriorityQueue` imports `Job`.
`Job` is available.
Yes.

Wait, `priority.py` imports `Job` at the top.
So `priority` depends on `core`.
`core` depends on `priority` (inside `__init__`).
This is still a dependency, but not a circular import failure because `core` is fully loaded before `priority` is imported.
Wait, `core` imports `priority` inside `__init__`.
So `core` does NOT depend on `priority` at load time.
So `core` -> `priority` (only at runtime).
`priority` -> `core` (at load time).
So `core` loads -> `Job` defined.
`priority` loads -> imports `Job` (from `core`).
`priority` finishes.
`core` `JobQueue` instance created -> imports `PriorityQueue` (from `priority`).
This works.

Okay, I'm set.

One last check on `__init__.py`.
It exports `Job`, `JobQueue`, `process_with_retry`.
It imports `Job` from `core`.
It imports `JobQueue` from `core`.
It imports `process_with_retry` from `retry`.
This works.

I will write the code now.
</think>

Here is the refactored package structure. This design separates concerns while handling Python's circular import constraints by using lazy imports for the dependency between `JobQueue` and `PriorityQueue`.

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

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

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

```python
# queue/core.py
"""
Core module containing Job dataclass and JobQueue interface.
"""

from dataclasses import dataclass, field
from typing import Any, Callable, Optional

@dataclass
class Job:
    """Represents a single unit of work."""
    id: str
    func: Callable
    priority: int = 0
    args: tuple = field(default_factory=tuple)
    kwargs: dict = field(default_factory=dict)
    status: str = "pending"  # pending, running, completed, failed

class JobQueue:
    """Main job queue interface."""
    
    def __init__(self):
        # Lazy import to prevent circular dependency with priority.py
        from .priority import PriorityQueue
        self._queue = PriorityQueue()
        self._counter = 0

    def enqueue(self, job: Job) -> None:
        """Add a job to the queue."""
        # Normalize priority to tuple for heap comparison (priority, timestamp)
        job.priority = (job.priority, self._counter)
        self._counter += 1
        self._queue.push(job)

    def dequeue(self) -> Optional[Job]:
        """Remove and return the highest priority job."""
        job = self._queue.pop()
        if job:
            job.status = "running"
        return job

    def peek(self) -> Optional[Job]:
        """View the next job without removing it."""
        return self._queue.peek()

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

```python
# queue/priority.py
"""
Priority queue implementation using heapq.
"""

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

class PriorityQueue:
    """A priority queue implementation using heapq."""

    def __init__(self):
        self._heap: list[Tuple[Tuple[int, int], Job]] = []

    def push(self, item: Job) -> None:
        """Push item onto heap with priority."""
        # item.priority is expected to be (priority, timestamp) tuple
        heapq.heappush(self._heap, (item.priority, item))

    def pop(self) -> Optional[Job]:
        """Pop item with highest priority."""
        if not self._heap:
            return None
        _, item = heapq.heappop(self._heap)
        return item

    def peek(self) -> Optional[Job]:
        """Peek at item with highest priority."""
        if not self._heap:
            return None
        _, item = self._heap[0]
        return item

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

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

import time
from typing import Callable, Any, Optional

def calculate_backoff(attempt: int, base_delay: float = 1.0, max_delay: float = 60.0) -> float:
    """Calculate delay with exponential backoff."""
    delay = base_delay * (2 ** attempt)
    return min(delay, max_delay)

def process_with_retry(
    func: Callable,
    max_retries: int = 3,
    base_delay: float = 1.0,
    *args,
    **kwargs
) -> Optional[Any]:
    """Execute function with retry logic on failure."""
    last_error = None
    
    for attempt in range(max_retries + 1):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            last_error = e
            if attempt < max_retries:
                delay = calculate_backoff(attempt, base_delay)
                time.sleep(delay)
            else:
                raise last_error
    return None
```