Now I have a complete picture. Here is the full mapping.

---

## mirage/ Source Directory Structure (all .py files)

```
mirage/
├── __init__.py
├── types.py
├── backend/
│   ├── __init__.py
│   ├── base.py
│   ├── filetype.py
│   ├── loader.py
│   ├── local/
│   │   ├── __init__.py
│   │   ├── local.py
│   │   └── memory.py
│   ├── network/
│   │   ├── __init__.py
│   │   ├── http.py
│   │   └── proxy.py
│   └── object_storage/
│       ├── __init__.py
│       ├── r2.py
│       └── s3.py
├── cli/
│   ├── __init__.py
│   ├── app.py
│   ├── client.py
│   ├── config.py
│   ├── helpers.py
│   └── commands/
│       ├── __init__.py
│       ├── command.py
│       ├── observe.py
│       ├── server.py
│       └── workspace.py
├── commands/
│   ├── __init__.py
│   ├── builtin/
│   │   ├── __init__.py
│   │   ├── handlers.py
│   │   ├── nav.py
│   │   ├── net.py
│   │   ├── read.py
│   │   ├── search.py
│   │   └── write.py
│   ├── custom/
│   │   ├── __init__.py
│   │   ├── arg_resolver.py
│   │   ├── executor.py
│   │   └── registry.py
│   └── remote/
│       ├── __init__.py
│       ├── auth.py
│       ├── client.py
│       ├── fastapi/
│       │   ├── __init__.py
│       │   └── app.py
│       └── grpc/
│           ├── __init__.py
│           ├── server.py
│           └── proto/
│               ├── __init__.py
│               ├── command_pb2.py
│               └── command_pb2_grpc.py
├── fuse/
│   ├── __init__.py
│   ├── base.py
│   ├── fs.py
│   ├── loader.py
│   ├── registry.py
│   ├── backend/
│   │   ├── __init__.py
│   │   ├── default.py
│   │   ├── local/
│   │   │   ├── __init__.py
│   │   │   ├── local.py
│   │   │   └── memory.py
│   │   └── object_storage/
│   │       ├── __init__.py
│   │       └── s3.py
│   ├── filetype/
│   │   ├── __init__.py
│   │   ├── csv.py
│   │   └── parquet.py
│   └── remote/
│       ├── __init__.py
│       └── remote.py
├── observe/
│   ├── __init__.py
│   ├── entries.py
│   ├── inspector.py
│   ├── session_log.py
│   └── fastapi/
│       ├── __init__.py
│       └── router.py
├── runtime/
│   ├── __init__.py
│   ├── base.py
│   ├── local.py
│   ├── memory.py
│   ├── remote.py
│   └── accessors/
│       ├── __init__.py
│       ├── base.py
│       ├── local.py
│       └── remote.py
├── server/
│   ├── __init__.py
│   ├── constants.py
│   ├── routes.py
│   ├── fastapi/
│   │   ├── __init__.py
│   │   ├── app.py
│   │   ├── config.py
│   │   ├── daemon.py
│   │   └── routers/
│   │       ├── __init__.py
│   │       ├── command_router.py
│   │       ├── io_router.py
│   │       └── workspace_router.py
│   └── service/
│       ├── __init__.py
│       ├── base.py
│       ├── cache.py
│       └── local.py
└── workspace/
    ├── __init__.py
    ├── mount.py
    ├── sync.py
    └── cache/
        ├── __init__.py
        ├── entry.py
        ├── policy.py
        └── store.py
```

---

## tests/ Directory Structure — Full Mapping

### Top-level

| File | Style | Tests source file(s) |
|------|-------|----------------------|
| `tests/conftest.py` | fixtures only | `mirage/backend/local/memory.py`, `mirage/workspace/__init__.py`, `mirage/types.py` |
| `tests/test_types.py` | plain functions | `mirage/types.py` — `FileStat`, `Inode`, `SyncResult`, `TransferStats`, `CommandStats` |

---

### tests/backend/

| File | Style | Tests source file(s) |
|------|-------|----------------------|
| `tests/backend/test_configs.py` | plain functions | `mirage/backend/object_storage/s3.py` (`S3Config`), `mirage/backend/object_storage/r2.py` (`R2Config`), `mirage/commands/custom/registry.py` (`ExternalCommand`) |
| `tests/backend/test_loader.py` | plain functions | `mirage/backend/loader.py` (`load_backend_class`) |
| `tests/backend/test_local.py` | plain functions + 1 fixture | `mirage/backend/local/local.py` (`LocalBackend`) |
| `tests/backend/test_memory.py` | plain functions | `mirage/backend/local/memory.py` (`MemoryBackend`) — via `memory_backend` fixture from conftest |
| `tests/backend/test_proxy.py` | plain functions + fixture | `mirage/backend/network/proxy.py` (`ProxyBackend`), `mirage/server/fastapi/app.py` |

---

### tests/cli/

| File | Style | Tests source file(s) |
|------|-------|----------------------|
| `tests/cli/test_app.py` | plain functions | `mirage/cli/app.py`, `mirage/cli/commands/workspace.py` (`mount`), `mirage/fuse/fs.py` |
| `tests/cli/test_client.py` | plain functions | `mirage/cli/client.py` (`MirageClient`, `ClientError`) |
| `tests/cli/test_config.py` | plain functions | `mirage/cli/config.py` (`load_workspace_yaml`, `load_yaml_config`, `parse_mount_uri`, `parse_mount_uri_full`, `parse_mount_uri_with_mode`, `parse_mounts_from_cli`, `split_mount_spec`) |

---

### tests/commands/

| File | Style | Tests source file(s) |
|------|-------|----------------------|
| `tests/commands/test_client.py` | plain functions | `mirage/commands/remote/client.py` (`CommandClient`, `HTTPCommandClient`, `GRPCCommandClient`, `CommandClientError`) |
| `tests/commands/test_handlers.py` | plain functions | `mirage/commands/builtin/handlers.py` (all individual `_ls`, `_cat`, `_grep`, etc. handler functions and the `COMMANDS` dict) |
| `tests/commands/test_init.py` | plain functions | `mirage/commands/__init__.py` (`COMMANDS`, `COMMAND_COST`) |
| `tests/commands/test_nav.py` | plain functions | `mirage/commands/builtin/nav.py` (`ls`, `stat`, `pwd`, `cd`, `find`, `tree`, `du`) |
| `tests/commands/test_read.py` | plain functions + 1 fixture | `mirage/commands/builtin/read.py` (`cat`, `diff`, `file`, `head`, `md5`, `tail`, `wc`) |
| `tests/commands/test_search.py` | plain functions | `mirage/commands/builtin/search.py` (`grep`, `jq`, `sort_lines`, `tr`, `uniq`, `cut`) |
| `tests/commands/test_write.py` | plain functions | `mirage/commands/builtin/write.py` (`cp`, `echo`, `mkdir`, `mv`, `rm`, `sed`, `tee`, `touch`) |

---

### tests/fuse/

| File | Style | Tests source file(s) |
|------|-------|----------------------|
| `tests/fuse/test_default.py` | plain functions | `mirage/fuse/backend/default.py` (`DefaultFuseOp`), `mirage/fuse/backend/local/memory.py` (`MemoryFuseOp`) |
| `tests/fuse/test_format_ops.py` | plain functions | `mirage/fuse/filetype/csv.py` (`CsvFuseOp`), `mirage/fuse/filetype/parquet.py` (`ParquetFuseOp`) |
| `tests/fuse/test_fs.py` | plain functions + fixtures | `mirage/fuse/fs.py` (`MirageFS`, `_MIRAGE_DIR`, `_MIRAGE_WHOAMI`, `mount_background`) |
| `tests/fuse/test_loader.py` | plain functions | `mirage/fuse/loader.py` (`load_fuse_op_class`, `build_registry_from_config`) |
| `tests/fuse/test_registry.py` | plain functions + 1 module-level class `ParquetOp` (a test helper, not a test class) | `mirage/fuse/registry.py` (`FuseOpRegistry`), `mirage/fuse/__init__.py` (`FUSE_OPS`, `FuseOp`) |
| `tests/fuse/test_remote.py` | plain functions | `mirage/fuse/remote/remote.py` (`RemoteFuseOp`) |

---

### tests/integration/

| File | Style | Tests source file(s) |
|------|-------|----------------------|
| `tests/integration/test_remote_transfer.py` | plain functions | `mirage/commands/custom/registry.py` (`CommandRegistry`, `ExternalCommand`), `mirage/commands/remote/client.py` (`CommandClient`) — integration scenario exercising file transfer round-trips |

---

### tests/observe/

| File | Style | Tests source file(s) |
|------|-------|----------------------|
| `tests/observe/test_api.py` | plain functions + fixtures | `mirage/observe/fastapi/router.py` (observe API routes), `mirage/server/fastapi/app.py` — tests all `/observe/...` HTTP endpoints |
| `tests/observe/test_entries.py` | plain functions | `mirage/observe/entries.py` (`CommandEntry`, `ParsedCommand`, `SessionEntry`, `WorkspaceInfo`) |
| `tests/observe/test_inspector.py` | plain functions + 1 module-level `@dataclass _FakeSession` helper | `mirage/observe/inspector.py` (`global_status`, `list_sessions`, `workspace_status`, `workspace_commands`, `workspace_memory`, `workspace_transfers`) |
| `tests/observe/test_session_log.py` | plain functions | `mirage/observe/session_log.py` (`SessionLog`) |

---

### tests/runtime/

| File | Style | Tests source file(s) |
|------|-------|----------------------|
| `tests/runtime/test_accessors.py` | plain functions | `mirage/runtime/accessors/local.py` (`LocalCommandsAccessor`, `LocalFuseOpsAccessor`, `LocalWorkspaceOpsAccessor`), `mirage/runtime/__init__.py` (`MemoryRuntime`) |
| `tests/runtime/test_local.py` | plain functions | `mirage/runtime/local.py` (`LocalRuntime`) |
| `tests/runtime/test_memory.py` | plain functions + 1 fixture | `mirage/runtime/memory.py` (`MemoryRuntime`) |
| `tests/runtime/test_remote.py` | plain functions + fixture | `mirage/runtime/remote.py` (`RemoteRuntime`), `mirage/server/fastapi/app.py` |
| `tests/runtime/test_runtime.py` | plain functions + 1 fixture | `mirage/runtime/base.py` (`Runtime`, `CommandNotAllowedError`) |

---

### tests/server/

| File | Style | Tests source file(s) |
|------|-------|----------------------|
| `tests/server/conftest.py` | fixtures only | `mirage/server/fastapi/app.py`, `mirage/observe/session_log.py` |
| `tests/server/test_app.py` | plain functions | `mirage/server/fastapi/app.py` (all workspace CRUD, exec, fuse registration, runtime, fuse-ops endpoints) |
| `tests/server/test_arg_resolver.py` | plain functions | `mirage/commands/custom/arg_resolver.py` (`classify_args`) |
| `tests/server/test_file_cache.py` | plain functions | `mirage/server/service/cache.py` (`FileCache`) |
| `tests/server/test_io_router.py` | plain functions + fixtures | `mirage/server/fastapi/routers/io_router.py` (all `/io/...` endpoints) |
| `tests/server/test_registry.py` | plain functions + 1 module-level helper class `_CustomMethodBackend` | `mirage/commands/custom/registry.py` (`CommandRegistry`, `ExternalCommand`), `mirage/server/constants.py` (`_parse_size`) |
| `tests/server/test_service.py` | plain functions + 1 fixture | `mirage/server/service/local.py` (`LocalService`) |

---

### tests/types/

| File | Style | Tests source file(s) |
|------|-------|----------------------|
| `tests/types/test_types.py` | plain functions | `mirage/types.py` — `CommandResult`, `FilePayload`, `RemoteCommandOptions`, `BackendType` |

---

### tests/workspace/

| File | Style | Tests source file(s) |
|------|-------|----------------------|
| `tests/workspace/cache/test_policy.py` | plain functions | `mirage/workspace/cache/policy.py` (`metadata_matches`), `mirage/workspace/cache/entry.py` (`CacheEntry`) |
| `tests/workspace/cache/test_store.py` | plain functions | `mirage/workspace/cache/store.py` (`LRUCache`) |
| `tests/workspace/test_cache_entry.py` | plain functions | `mirage/workspace/cache/entry.py` (`CacheEntry`) |
| `tests/workspace/test_init.py` | plain functions | `mirage/workspace/__init__.py` (`Workspace`) — consistency policy behavior (`ConsistencyPolicy.NONE/ALWAYS/TTL/LAZY`) |
| `tests/workspace/test_lock.py` | plain functions | `mirage/workspace/__init__.py` (`Workspace.lock_paths`, `_lock_path`, `unlock_paths`) |
| `tests/workspace/test_mount.py` | plain functions | `mirage/workspace/mount.py` (`MountRegistry`), `mirage/workspace/__init__.py` (`Workspace`) |
| `tests/workspace/test_sync.py` | plain functions + 2 fixtures | `mirage/workspace/sync.py` (`DirtyTracker`) |
| `tests/workspace/test_write_ops.py` | plain functions | `mirage/workspace/__init__.py` (`Workspace`) — write/read/sync/rm/mv/cp/reset operations |

---

## Key Observations

**Style:** Every test file uses **plain functions** (`def test_xxx`). There are no `class TestXxx` test classes anywhere in the suite. The only classes defined in test files are small helper/stub classes defined at module level (e.g., `ParquetOp` in `test_registry.py`, `_CustomMethodBackend` in `test_registry.py`, `_FakeSession` dataclass in `test_inspector.py`, `FailingBackend` inside a function in `test_sync.py`).

**Source files with no dedicated test file:** `mirage/backend/filetype.py`, `mirage/backend/base.py`, `mirage/backend/network/http.py`, `mirage/cli/helpers.py`, `mirage/cli/commands/command.py`, `mirage/cli/commands/observe.py`, `mirage/cli/commands/server.py`, `mirage/commands/builtin/net.py`, `mirage/commands/custom/executor.py`, `mirage/commands/remote/auth.py`, `mirage/commands/remote/fastapi/app.py`, `mirage/commands/remote/grpc/server.py`, `mirage/fuse/base.py`, `mirage/fuse/backend/local/local.py`, `mirage/fuse/backend/object_storage/s3.py`, `mirage/observe/fastapi/router.py` (tested via `test_api.py`), `mirage/runtime/accessors/base.py`, `mirage/runtime/accessors/remote.py`, `mirage/server/fastapi/config.py`, `mirage/server/fastapi/daemon.py`, `mirage/server/fastapi/routers/command_router.py`, `mirage/server/fastapi/routers/workspace_router.py`, `mirage/server/routes.py`, `mirage/server/service/base.py`.

agentId: ae502e010c901dd71 (for resuming to continue this agent's work if needed)
<usage>total_tokens: 108883
tool_uses: 51
duration_ms: 103488</usage>

Prompt:
Thoroughly explore the tests/ directory structure. For each test file:
1. List the full path
2. Note if it uses classes (class TestXxx) or plain functions (def test_xxx)
3. Note what source file(s) it tests (based on imports and test content)

Also list the full mirage/ source directory structure (all .py files) so we can map source files to test files.

Use `find tests -name '*.py' -type f` and `find mirage -name '*.py' -type f` style exploration. Read each test file to check for class usage.

Be very thorough - I need a complete mapping.


Good - all tests already use plain functions, so requirement #1 is already satisfied. The main work is requirement #2: reorganizing test files to mirror the source structure.

Let me now launch a Plan agent to design the reorganization.

Let me map out the current vs target test structure:

**Current test files and what they should become:**

1. `tests/conftest.py` → stays (global fixtures)
2. `tests/test_types.py` → `tests/test_types.py` (stays, maps to `mirage/types.py`)

3. `tests/backend/test_configs.py` → needs splitting:
   - S3Config tests → `tests/backend/object_storage/test_s3.py`
   - R2Config tests → `tests/backend/object_storage/test_r2.py`
   - ExternalCommand tests → `tests/commands/custom/test_registry.py`

Actually, I need to reconsider - that file tests config models, so I should look more carefully at what belongs where.

4. `tests/backend/test_loader.py` → `tests/backend/test_loader.py` (stays, maps to `mirage/backend/loader.py`)
5. `tests/backend/test_local.py` → `tests/backend/local/test_local.py`

For the memory backend tests, I'm not seeing `tests/backend/test_memory.py` in the directory listing, though there is `tests/runtime/test_memory.py` which tests `mirage/runtime/memory.py`. Let me reconsider the actual mapping between source files and their corresponding tests. I should establish a clear 1:1 mapping where each source file has its test file in the appropriate location. Continuing through the test file mappings, I see the object storage modules need tests organized in their own subdirectory, and the CLI tests are already properly structured. Now I'm mapping out the commands directory where some tests need to be reorganized into their respective subdirectories. I'm continuing to map out the test file reorganization, moving the registry test from the server directory and consolidating the FUSE-related tests under their proper module structure. Looking at the remaining test file mappings for the observe and runtime modules, I need to reorganize the FastAPI router tests into their own directory structure and consolidate the runtime base tests into a more specific file. I'm seeing that some test files are misplaced in the directory structure and need to be reorganized, while others that test multiple source files need to be split apart. Now I'm looking at the `tests/backend/test_configs.py` file to understand what it covers.

It's testing S3Config, R2Config, and ExternalCommand from different modules, so those tests should be moved to their respective locations—S3Config and R2Config to their own test files, and ExternalCommand to the commands/custom directory. I also notice that `tests/server/test_registry.py` and `tests/server/test_arg_resolver.py` are testing files that belong in the commands/custom directory, so those need to be relocated as well. This is getting complicated enough that I should probably bring in the Plan agent to help organize this refactoring.

Actually, let me map out the key moves needed here. There are several test files that need to shift to new directories to match the source code structure—tests for local and proxy backends should move into their own subdirectories, and the command tests for handlers, navigation, read, search, and write operations all need to move under the builtin commands directory.

The remote client tests go into a remote subdirectory, while the FUSE tests split between backend and remote folders. Server tests get reorganized too—the FastAPI app and router tests move into a fastapi subdirectory, the cache and service tests consolidate under service, and the argument resolver and registry tests migrate to the custom commands folder. Finally, the workspace cache entry test moves into a cache subdirectory with a renamed file.

Now I'm looking at which test files need to be split across multiple locations. Some files like the backend configs test contain tests for different storage backends that should each get their own file, and the FUSE format operations test has separate test classes for different file types that belong in their own filetype-specific modules.

For the MemoryFuseOp tests, I'm wondering if they should live in a nested path under the backend directory to match the source structure more closely. Then there are a few test files that should be renamed to better reflect what they're testing, and the rest can stay where they are since they're already well-organized.

But I'm realizing there's a conflict with the workspace tests—test_init.py, test_lock.py, and test_write_ops.py all cover code in mirage/workspace/__init__.py, which violates the one-test-file-per-source-file rule. Following that strictly means I'd need to merge test_lock.py and test_write_ops.py into test_init.py, though I'm uncertain if that's the right call for such a large file. but that would make test_init.py very large. Let me think about this...

Actually, the user said "each source file should have exactly one test file for example backend/abc/def.py should have tests/backend/abc/test_def.py." This is about the naming/location convention. It also implies 1:1 mapping. So yes, all tests for workspace/__init__.py should be in tests/workspace/test_init.py.

But let me be practical. If the resulting file would be huge, we might want to keep them separate but at least name them consistently. Let me plan this and present it to the user for approval.

Actually, the user said "each source file should have exactly one test file." I think the strict interpretation is:
- 1 source file → 1 test file (merge if multiple exist)

Let me also note: `tests/observe/test_api.py` tests `mirage/observe/fastapi/router.py`, which should become `tests/observe/fastapi/test_router.py`.

OK, I have a clear picture now. Let me map out the reorganization: test_configs.py needs to be split since it covers three source files, test_format_ops.py covers two files so that needs splitting too, test_default.py tests both default and memory fuse ops so those should be separated, test_registry.py and test_arg_resolver.py are in the wrong location under tests/server/ when they should follow the source structure, and test_write_ops.py and test_lock.py both test workspace/__init__.py so they should merge into test_init.py. I'm realizing the user's rule is straightforward: each source file gets exactly one test file mirrored in the tests directory with a `test_` prefix. So I need to map out which test files currently exist, identify any duplicates or misplacements, and figure out which ones need consolidation based on what they're actually testing.


Good news: all tests already use plain `def test_xxx` functions — no classes to convert. The main work is reorganizing test files to mirror the source structure 1:1.

Let me design the plan.


Agent

Plan test file reorganization

Prompt:
Design a plan to reorganize the test directory so each source file has exactly one test file mirroring its path: `mirage/a/b/c.py` → `tests/a/b/test_c.py`.

**Current source structure:**
```
mirage/
├── types.py
├── backend/
│   ├── base.py, filetype.py, loader.py
│   ├── local/ (local.py, memory.py)
│   ├── network/ (http.py, proxy.py)
│   └── object_storage/ (r2.py, s3.py)
├── cli/ (app.py, client.py, config.py, helpers.py)
│   └── commands/ (command.py, observe.py, server.py, workspace.py)
├── commands/
│   ├── __init__.py
│   ├── builtin/ (handlers.py, nav.py, net.py, read.py, search.py, write.py)
│   ├── custom/ (arg_resolver.py, executor.py, registry.py)
│   └── remote/ (auth.py, client.py, fastapi/app.py, grpc/server.py)
├── fuse/
│   ├── base.py, fs.py, loader.py, registry.py
│   ├── backend/ (default.py, local/local.py, local/memory.py, object_storage/s3.py)
│   ├── filetype/ (csv.py, parquet.py)
│   └── remote/ (remote.py)
├── observe/ (entries.py, inspector.py, session_log.py, fastapi/router.py)
├── runtime/ (base.py, local.py, memory.py, remote.py)
│   └── accessors/ (base.py, local.py, remote.py)
├── server/
│   ├── constants.py, routes.py
│   ├── fastapi/ (app.py, config.py, daemon.py, routers/command_router.py, routers/io_router.py, routers/workspace_router.py)
│   └── service/ (base.py, cache.py, local.py)
└── workspace/ (__init__.py, mount.py, sync.py)
    └── cache/ (entry.py, policy.py, store.py)
```

**Current test files and what source files they test:**

| Current test file | Tests source file(s) |
|---|---|
| tests/conftest.py | Fixtures only — stays |
| tests/test_types.py | mirage/types.py |
| tests/backend/test_configs.py | object_storage/s3.py (S3Config), object_storage/r2.py (R2Config), commands/custom/registry.py (ExternalCommand) |
| tests/backend/test_loader.py | backend/loader.py |
| tests/backend/test_local.py | backend/local/local.py |
| tests/backend/test_proxy.py | backend/network/proxy.py |
| tests/cli/test_app.py | cli/app.py + cli/commands/workspace.py |
| tests/cli/test_client.py | cli/client.py |
| tests/cli/test_config.py | cli/config.py |
| tests/commands/test_client.py | commands/remote/client.py |
| tests/commands/test_handlers.py | commands/builtin/handlers.py |
| tests/commands/test_init.py | commands/__init__.py |
| tests/commands/test_nav.py | commands/builtin/nav.py |
| tests/commands/test_read.py | commands/builtin/read.py |
| tests/commands/test_search.py | commands/builtin/search.py |
| tests/commands/test_write.py | commands/builtin/write.py |
| tests/fuse/test_default.py | fuse/backend/default.py + fuse/backend/local/memory.py |
| tests/fuse/test_format_ops.py | fuse/filetype/csv.py + fuse/filetype/parquet.py |
| tests/fuse/test_fs.py | fuse/fs.py |
| tests/fuse/test_loader.py | fuse/loader.py |
| tests/fuse/test_registry.py | fuse/registry.py + fuse/__init__.py |
| tests/fuse/test_remote.py | fuse/remote/remote.py |
| tests/integration/test_remote_transfer.py | Cross-cutting integration test |
| tests/observe/test_api.py | observe/fastapi/router.py |
| tests/observe/test_entries.py | observe/entries.py |
| tests/observe/test_inspector.py | observe/inspector.py |
| tests/observe/test_session_log.py | observe/session_log.py |
| tests/runtime/test_accessors.py | runtime/accessors/local.py |
| tests/runtime/test_local.py | runtime/local.py |
| tests/runtime/test_memory.py | runtime/memory.py |
| tests/runtime/test_remote.py | runtime/remote.py |
| tests/runtime/test_runtime.py | runtime/base.py |
| tests/server/conftest.py | Fixtures — stays |
| tests/server/test_app.py | server/fastapi/app.py |
| tests/server/test_arg_resolver.py | commands/custom/arg_resolver.py |
| tests/server/test_file_cache.py | server/service/cache.py |
| tests/server/test_io_router.py | server/fastapi/routers/io_router.py |
| tests/server/test_registry.py | commands/custom/registry.py |
| tests/server/test_service.py | server/service/local.py |
| tests/types/test_types.py | mirage/types.py (duplicate?) |
| tests/workspace/cache/test_policy.py | workspace/cache/policy.py |
| tests/workspace/cache/test_store.py | workspace/cache/store.py |
| tests/workspace/test_cache_entry.py | workspace/cache/entry.py |
| tests/workspace/test_init.py | workspace/__init__.py |
| tests/workspace/test_lock.py | workspace/__init__.py |
| tests/workspace/test_mount.py | workspace/mount.py |
| tests/workspace/test_sync.py | workspace/sync.py |
| tests/workspace/test_write_ops.py | workspace/__init__.py |

**Design the reorganization:**

1. List all files that need to MOVE (with old → new paths)
2. List files that need SPLITTING (one test file tests multiple source files)
3. List files that need MERGING (multiple test files test one source file: test_init.py + test_lock.py + test_write_ops.py all test workspace/__init__.py)
4. List files that need RENAMING (e.g. test_runtime.py → test_base.py)
5. List files that stay where they are
6. Note special cases (integration tests, conftest.py files, duplicate test files)

The plan should result in every test file at path `tests/X/Y/test_Z.py` testing exactly `mirage/X/Y/Z.py`.

Keep the plan practical: use git mv for moves, and for splits/merges, list exactly what functions go where.


Now I have all the information needed. Let me compile the complete, precise reorganization plan.

---

# Test Directory Reorganization Plan

This plan establishes the invariant: `tests/X/Y/test_Z.py` tests exclusively `mirage/X/Y/Z.py`. Every decision below is grounded in actual file contents read above.

---

## Target State: Complete File Mapping

```
mirage/types.py                             → tests/test_types.py
mirage/backend/loader.py                    → tests/backend/test_loader.py
mirage/backend/local/local.py              → tests/backend/local/test_local.py
mirage/backend/local/memory.py             → tests/backend/local/test_memory.py
mirage/backend/network/proxy.py            → tests/backend/network/test_proxy.py
mirage/backend/object_storage/s3.py        → tests/backend/object_storage/test_s3.py
mirage/backend/object_storage/r2.py        → tests/backend/object_storage/test_r2.py
mirage/cli/app.py                          → tests/cli/test_app.py
mirage/cli/client.py                       → tests/cli/test_client.py
mirage/cli/config.py                       → tests/cli/test_config.py
mirage/cli/commands/workspace.py           → tests/cli/commands/test_workspace.py
mirage/commands/__init__.py               → tests/commands/test___init__.py
mirage/commands/builtin/handlers.py        → tests/commands/builtin/test_handlers.py
mirage/commands/builtin/nav.py             → tests/commands/builtin/test_nav.py
mirage/commands/builtin/read.py            → tests/commands/builtin/test_read.py
mirage/commands/builtin/search.py          → tests/commands/builtin/test_search.py
mirage/commands/builtin/write.py           → tests/commands/builtin/test_write.py
mirage/commands/custom/arg_resolver.py     → tests/commands/custom/test_arg_resolver.py
mirage/commands/custom/registry.py         → tests/commands/custom/test_registry.py
mirage/commands/remote/client.py           → tests/commands/remote/test_client.py
mirage/fuse/fs.py                          → tests/fuse/test_fs.py
mirage/fuse/loader.py                      → tests/fuse/test_loader.py
mirage/fuse/registry.py                    → tests/fuse/test_registry.py
mirage/fuse/backend/default.py             → tests/fuse/backend/test_default.py
mirage/fuse/backend/local/memory.py        → tests/fuse/backend/local/test_memory.py
mirage/fuse/filetype/csv.py                → tests/fuse/filetype/test_csv.py
mirage/fuse/filetype/parquet.py            → tests/fuse/filetype/test_parquet.py
mirage/fuse/remote/remote.py               → tests/fuse/remote/test_remote.py
mirage/observe/entries.py                  → tests/observe/test_entries.py
mirage/observe/inspector.py                → tests/observe/test_inspector.py
mirage/observe/session_log.py              → tests/observe/test_session_log.py
mirage/observe/fastapi/router.py           → tests/observe/fastapi/test_router.py
mirage/runtime/base.py                     → tests/runtime/test_base.py
mirage/runtime/local.py                    → tests/runtime/test_local.py
mirage/runtime/memory.py                   → tests/runtime/test_memory.py
mirage/runtime/remote.py                   → tests/runtime/test_remote.py
mirage/runtime/accessors/local.py          → tests/runtime/accessors/test_local.py
mirage/server/fastapi/app.py               → tests/server/fastapi/test_app.py
mirage/server/fastapi/routers/io_router.py → tests/server/fastapi/routers/test_io_router.py
mirage/server/service/cache.py             → tests/server/service/test_cache.py
mirage/server/service/local.py             → tests/server/service/test_local.py
mirage/server/constants.py                 → tests/server/test_constants.py (extracted)
mirage/workspace/__init__.py              → tests/workspace/test___init__.py
mirage/workspace/mount.py                  → tests/workspace/test_mount.py
mirage/workspace/sync.py                   → tests/workspace/test_sync.py
mirage/workspace/cache/entry.py            → tests/workspace/cache/test_entry.py
mirage/workspace/cache/policy.py           → tests/workspace/cache/test_policy.py
mirage/workspace/cache/store.py            → tests/workspace/cache/test_store.py
tests/conftest.py                          → stays
tests/server/conftest.py                   → moves to tests/server/fastapi/conftest.py
tests/integration/                         → stays as-is (cross-cutting)
```

---

## Section 1: Files That Need RENAMING Only (same directory, different name)

These files are already in roughly the right place but have the wrong name relative to the source file they test.

| Current path | New path | Reason |
|---|---|---|
| `tests/runtime/test_runtime.py` | `tests/runtime/test_base.py` | Tests `runtime/base.py`, not `runtime/runtime.py` |
| `tests/workspace/test_cache_entry.py` | `tests/workspace/cache/test_entry.py` | Tests `workspace/cache/entry.py`; must move into cache/ subdirectory |
| `tests/server/test_file_cache.py` | `tests/server/service/test_cache.py` | Tests `server/service/cache.py` |

```bash
# Simple renames (plus directory change for the last two):
git mv tests/runtime/test_runtime.py tests/runtime/test_base.py
git mv tests/workspace/test_cache_entry.py tests/workspace/cache/test_entry.py
git mv tests/server/test_file_cache.py tests/server/service/test_cache.py
```

---

## Section 2: Files That Need MOVING (different directory path)

These files test a single clearly-identified source file but are not located in the mirrored directory.

| Current path | New path | What it tests |
|---|---|---|
| `tests/backend/test_local.py` | `tests/backend/local/test_local.py` | `backend/local/local.py` |
| `tests/backend/test_memory.py` | `tests/backend/local/test_memory.py` | `backend/local/memory.py` |
| `tests/backend/test_proxy.py` | `tests/backend/network/test_proxy.py` | `backend/network/proxy.py` |
| `tests/backend/test_loader.py` | `tests/backend/test_loader.py` | stays — `backend/loader.py` is at backend root |
| `tests/commands/test_nav.py` | `tests/commands/builtin/test_nav.py` | `commands/builtin/nav.py` |
| `tests/commands/test_handlers.py` | `tests/commands/builtin/test_handlers.py` | `commands/builtin/handlers.py` |
| `tests/commands/test_read.py` | `tests/commands/builtin/test_read.py` | `commands/builtin/read.py` |
| `tests/commands/test_search.py` | `tests/commands/builtin/test_search.py` | `commands/builtin/search.py` |
| `tests/commands/test_write.py` | `tests/commands/builtin/test_write.py` | `commands/builtin/write.py` |
| `tests/commands/test_client.py` | `tests/commands/remote/test_client.py` | `commands/remote/client.py` |
| `tests/server/test_arg_resolver.py` | `tests/commands/custom/test_arg_resolver.py` | `commands/custom/arg_resolver.py` |
| `tests/server/test_registry.py` | `tests/commands/custom/test_registry.py` (partial) | `commands/custom/registry.py` — see splitting notes |
| `tests/server/test_app.py` | `tests/server/fastapi/test_app.py` | `server/fastapi/app.py` |
| `tests/server/test_io_router.py` | `tests/server/fastapi/routers/test_io_router.py` | `server/fastapi/routers/io_router.py` |
| `tests/server/test_service.py` | `tests/server/service/test_local.py` | `server/service/local.py` |
| `tests/observe/test_api.py` | `tests/observe/fastapi/test_router.py` | `observe/fastapi/router.py` |
| `tests/fuse/test_default.py` | `tests/fuse/backend/test_default.py` (partial) | `fuse/backend/default.py` — see splitting notes |
| `tests/fuse/test_format_ops.py` | split into two — see Section 3 | `fuse/filetype/csv.py` + `fuse/filetype/parquet.py` |
| `tests/fuse/test_registry.py` | `tests/fuse/test_registry.py` (stays, partial) | `fuse/registry.py` — see splitting notes |
| `tests/runtime/test_accessors.py` | `tests/runtime/accessors/test_local.py` (partial) | `runtime/accessors/local.py` — see splitting notes |

```bash
# Create needed __init__.py-backed directories first, then:
git mv tests/backend/test_local.py tests/backend/local/test_local.py
git mv tests/backend/test_memory.py tests/backend/local/test_memory.py
git mv tests/backend/test_proxy.py tests/backend/network/test_proxy.py
git mv tests/commands/test_nav.py tests/commands/builtin/test_nav.py
git mv tests/commands/test_handlers.py tests/commands/builtin/test_handlers.py
git mv tests/commands/test_read.py tests/commands/builtin/test_read.py
git mv tests/commands/test_search.py tests/commands/builtin/test_search.py
git mv tests/commands/test_write.py tests/commands/builtin/test_write.py
git mv tests/commands/test_client.py tests/commands/remote/test_client.py
git mv tests/server/test_arg_resolver.py tests/commands/custom/test_arg_resolver.py
git mv tests/server/test_app.py tests/server/fastapi/test_app.py
git mv tests/server/test_io_router.py tests/server/fastapi/routers/test_io_router.py
git mv tests/server/test_service.py tests/server/service/test_local.py
git mv tests/observe/test_api.py tests/observe/fastapi/test_router.py
```

---

## Section 3: Files That Need SPLITTING (one test file covers multiple source files)

### 3a. `tests/backend/test_configs.py` → split into 3 files

**Current content analysis:**
- `test_s3config_defaults`, `test_s3config_immutable` → tests `backend/object_storage/s3.py` (S3Config)
- `test_r2config_fields`, `test_r2config_immutable` → tests `backend/object_storage/r2.py` (R2Config)
- `test_external_command_fields`, `test_external_command_immutable` → tests `commands/custom/registry.py` (ExternalCommand)

**Destination files:**

`tests/backend/object_storage/test_s3.py` — new file:
```python
import pytest
from pydantic import ValidationError
from mirage.backend.object_storage.s3 import S3Config

def test_s3config_defaults():
    ...

def test_s3config_immutable():
    ...
```

`tests/backend/object_storage/test_r2.py` — new file:
```python
import pytest
from pydantic import ValidationError
from mirage.backend.object_storage.r2 import R2Config

def test_r2config_fields():
    ...

def test_r2config_immutable():
    ...
```

`tests/commands/custom/test_registry.py` — ExternalCommand tests fold into the existing `tests/server/test_registry.py` content after that file is moved here (see 3b below):
- `test_external_command_fields` and `test_external_command_immutable` are added to `tests/commands/custom/test_registry.py`

### 3b. `tests/server/test_registry.py` → move + partial split

**Current content analysis:**
- The bulk of the file (all `test_builtin_ls`, `test_register_external`, `test_run_external_*`, `test_unknown_command`, `test_external_overrides_builtin`, `test_backend_method_*`, `test_list_commands_*`, `test_run_builtin_cat`, `test_external_command_*`, `test_dispatch_*`, `test_registry_*`) tests `commands/custom/registry.py`
- `test_parse_size_units` (line 290-295) — imports from `mirage.server.constants` — tests `server/constants.py`

**Action:** Move the whole file to `tests/commands/custom/test_registry.py`, then extract `test_parse_size_units` into a new `tests/server/test_constants.py`.

`tests/commands/custom/test_registry.py` — all tests except `test_parse_size_units`, plus the two ExternalCommand model tests from `tests/backend/test_configs.py`

`tests/server/test_constants.py` — new file containing only:
```python
from mirage.server.constants import _parse_size

def test_parse_size_units():
    assert _parse_size("100B") == 100
    assert _parse_size("10KB") == 10240
    assert _parse_size("1MB") == 1048576
    assert _parse_size("2GB") == 2147483648
    assert _parse_size("100") == 100
```

### 3c. `tests/fuse/test_default.py` → split into 2 files

**Current content analysis:**
- `test_default_eager_small_file`, `test_default_lazy_large_file`, `test_buffered_write_flush`, `test_buffered_write_new_file`, `test_flush_noop_without_writes` → tests `fuse/backend/default.py` (DefaultFuseOp)
- `test_memory_always_eager` → tests `fuse/backend/local/memory.py` (MemoryFuseOp)

**Destination files:**

`tests/fuse/backend/test_default.py` — the 5 DefaultFuseOp tests

`tests/fuse/backend/local/test_memory.py` — new file:
```python
from mirage.backend.local.memory import MemoryBackend
from mirage.fuse.backend.local.memory import MemoryFuseOp
from mirage.types import MountMode
from mirage.workspace import Workspace

def test_memory_always_eager():
    ws = Workspace({"/": MemoryBackend()}, mode=MountMode.WRITE)
    big = b"y" * (2 * 1024 * 1024)
    ws.write("/big.bin", big)
    op = MemoryFuseOp()
    ctx = op.on_open(ws, "/big.bin", len(big))
    assert ctx["eager"] is True
```

### 3d. `tests/fuse/test_format_ops.py` → split into 2 files

**Current content analysis:**
- `test_parquet_*` (4 tests) → tests `fuse/filetype/parquet.py`
- `test_csv_*` (3 tests) → tests `fuse/filetype/csv.py`
- `_make_parquet_bytes` helper is used only by parquet tests

**Destination files:**

`tests/fuse/filetype/test_parquet.py`:
```python
import io
import pyarrow as pa
import pyarrow.parquet as pq
from mirage.backend.local.memory import MemoryBackend
from mirage.fuse.filetype.parquet import ParquetFuseOp
from mirage.types import MountMode
from mirage.workspace import Workspace

def _make_parquet_bytes() -> bytes:
    ...

def test_parquet_read_returns_text(): ...
def test_parquet_schema_in_output(): ...
def test_parquet_row_count(): ...
def test_parquet_error_handling(): ...
```

`tests/fuse/filetype/test_csv.py`:
```python
from mirage.backend.local.memory import MemoryBackend
from mirage.fuse.filetype.csv import CsvFuseOp
from mirage.types import MountMode
from mirage.workspace import Workspace

def test_csv_small_passthrough(): ...
def test_csv_large_preview(): ...
def test_csv_large_has_preview_marker(): ...
```

### 3e. `tests/fuse/test_registry.py` → split into 2 files

**Current content analysis:**
- `test_fuse_ops_dict_has_default`, `test_fuse_ops_dict_has_all_backends`, `test_fuse_ops_dict_has_format_ops` → these test `fuse/__init__.py` (the `FUSE_OPS` dict exported from it)
- `test_registry_*`, `test_workspace_read_*` → tests `fuse/registry.py`

**Destination files:**

`tests/fuse/test_registry.py` — keep only `FuseOpRegistry` tests:
- `test_registry_resolve_default`
- `test_registry_resolve_custom`
- `test_registry_first_match_wins`
- `test_registry_list_ops`
- `test_registry_set_default`
- `test_registry_backend_aware_memory`
- `test_registry_backend_aware_local`
- `test_registry_pattern_overrides_backend`
- `test_workspace_read_partial`
- `test_workspace_read_full`

`tests/fuse/test___init__.py` — new file for `fuse/__init__.py`:
```python
from mirage.fuse import FUSE_OPS, FuseOp

def test_fuse_ops_dict_has_default():
    assert "default" in FUSE_OPS

def test_fuse_ops_dict_has_all_backends():
    assert "s3" in FUSE_OPS
    assert "memory" in FUSE_OPS
    assert "local" in FUSE_OPS

def test_fuse_ops_dict_has_format_ops():
    assert "parquet" in FUSE_OPS
    assert "csv" in FUSE_OPS
```

Note: The `ParquetOp` class definition in `test_registry.py` is only used by `test_registry_resolve_custom` and `test_registry_first_match_wins`, so it stays in `test_registry.py`.

### 3f. `tests/cli/test_app.py` → split into 2 files

**Current content analysis:**
- `test_help` and `_run` helper → tests `cli/app.py` (invokes `mirage.cli.app` as module)
- `test_mount_fuse_backend_mounts_at_root`, `test_mount_fuse_backend_with_extra_spec`, `test_mount_fuse_no_backend_no_spec_raises`, `test_mount_exec_kill` → tests `cli/commands/workspace.py` (specifically the `mount` function)
- `_fuse_defaults` helper is used only by workspace command tests

**Destination files:**

`tests/cli/test_app.py` — keep only:
```python
import os, subprocess, sys

def _run(args, env=None, check=True):
    ...

def test_help():
    ...
```

`tests/cli/commands/test_workspace.py` — new file:
```python
import tempfile
from unittest.mock import patch
import pytest

def _run(args, env=None, check=True):  # may inline _run if needed
    ...

def _fuse_defaults() -> dict:
    ...

def test_mount_fuse_backend_mounts_at_root(): ...
def test_mount_fuse_backend_with_extra_spec(): ...
def test_mount_fuse_no_backend_no_spec_raises(): ...

@pytest.mark.skip(reason="requires live server")
def test_mount_exec_kill(): ...
```

Note: `_run` is used by `test_mount_fuse_no_backend_no_spec_raises` and `test_mount_exec_kill` in `test_workspace.py`. The helper can either be duplicated or imported from `tests/cli/test_app.py` (but cross-test imports are fragile). The cleanest approach is to inline the `_run` helper in both files since it is small.

### 3g. `tests/runtime/test_accessors.py` → split into 2 files

**Current content analysis:**
- `test_local_commands_*`, `test_local_fuse_ops_*`, `test_local_workspace_ops_*` → tests `runtime/accessors/local.py`
- `test_runtime_has_accessors`, `test_runtime_register_command_then_run` → these test `runtime/memory.py` (MemoryRuntime) — the accessor integration through MemoryRuntime

**Destination files:**

`tests/runtime/accessors/test_local.py`:
```python
from mirage.backend.local.memory import MemoryBackend
from mirage.commands.custom.registry import CommandRegistry
from mirage.fuse.registry import FuseOpRegistry
from mirage.runtime.accessors.local import (LocalCommandsAccessor,
                                            LocalFuseOpsAccessor,
                                            LocalWorkspaceOpsAccessor)
from mirage.types import MountMode
from mirage.workspace import Workspace

def test_local_commands_list(): ...
def test_local_commands_register(): ...
def test_local_fuse_ops_list(): ...
def test_local_fuse_ops_register(): ...
def test_local_fuse_ops_register_unknown(): ...
def test_local_workspace_ops_status(): ...
def test_local_workspace_ops_sync(): ...
def test_local_workspace_ops_status_after_write(): ...
```

`tests/runtime/test_memory.py` — the two accessor-via-runtime tests fold into the existing `test_memory.py`:
```python
# append to existing test_memory.py:
def test_runtime_has_accessors():
    ws = _make_ws()
    rt = MemoryRuntime(ws)
    assert hasattr(rt, "commands")
    ...

def test_runtime_register_command_then_run():
    ...
```

---

## Section 4: Files That Need MERGING (multiple test files → one file)

### 4a. `tests/workspace/test_init.py` + `tests/workspace/test_lock.py` + `tests/workspace/test_write_ops.py` → `tests/workspace/test___init__.py`

All three files test `mirage/workspace/__init__.py`:
- `test_init.py` — tests consistency policies (read from `Workspace.__init__` / `Workspace.cat`)
- `test_lock.py` — tests `lock_paths`, `unlock_paths`, `_lock_path` (all on `Workspace`)
- `test_write_ops.py` — tests `write`, `rm`, `mv`, `cp`, `sync`, `reset`, `stat`, `exists` (all on `Workspace`)

**Action:** Merge all three into `tests/workspace/test___init__.py`.

The merged file should have clear section comments delineating the three concern groups. Shared helpers like `_make_ws` exist in both `test_init.py` and `test_write_ops.py` with slightly different signatures; the merged file needs both with distinct names:

```python
# Helpers
def _make_ws_consistency(consistency=ConsistencyPolicy.NONE, ttl_seconds=300):
    # from test_init.py
    ...

def _make_ws(mode=MountMode.WRITE, sync_policy=SyncPolicy.STAGED):
    # from test_write_ops.py
    ...

# --- Consistency policy tests (from test_init.py) ---
def test_consistency_none_caches_forever(): ...
def test_consistency_always_refetches(): ...
...

# --- Lock tests (from test_lock.py) ---
def test_lock_paths_blocks_write(): ...
...

# --- Write operation tests (from test_write_ops.py) ---
def test_write_and_cat(): ...
...
```

```bash
# After manually creating merged file:
git rm tests/workspace/test_init.py
git rm tests/workspace/test_lock.py
git rm tests/workspace/test_write_ops.py
git mv <merged-file> tests/workspace/test___init__.py
```

### 4b. `tests/test_types.py` + `tests/types/test_types.py` → `tests/test_types.py`

These are a duplicate case. Both test `mirage/types.py`:
- `tests/test_types.py` — tests `FileStat`, `Inode`, `SyncResult`, `TransferStats`, `CommandStats`
- `tests/types/test_types.py` — tests `CommandResult`, `FilePayload`, `RemoteCommandOptions`, `BackendType`

**Action:** Merge both into `tests/test_types.py` (the correct path for `mirage/types.py`). Delete `tests/types/` directory entirely.

The merged `tests/test_types.py`:
```python
import pytest
from pydantic import ValidationError
from mirage.types import (
    BackendType, CommandResult, CommandStats, FilePayload,
    FileStat, Inode, RemoteCommandOptions, SyncResult, TransferStats
)

# From tests/test_types.py:
def test_filestat_defaults(): ...
def test_filestat_immutable(): ...
def test_inode_defaults(): ...
def test_inode_mutable(): ...
def test_syncresult_fields(): ...
def test_syncresult_immutable(): ...
def test_transferstats_mutable(): ...
def test_commandstats_defaults(): ...

# From tests/types/test_types.py:
def test_command_result_defaults(): ...
def test_command_result_with_files(): ...
def test_file_payload_with_data(): ...
def test_file_payload_checksum_only(): ...
def test_remote_command_options_defaults(): ...
def test_remote_command_options_custom(): ...
def test_remote_command_options_only_two_fields(): ...
def test_backend_type_values(): ...
def test_backend_type_string_equality(): ...
def test_backend_type_members(): ...
```

```bash
git rm tests/types/test_types.py
git rm tests/types/__init__.py
# Rewrite tests/test_types.py with merged content
```

---

## Section 5: Files That Stay Where They Are

| File | Reason |
|---|---|
| `tests/conftest.py` | Root fixtures (`memory_backend`, `write_ws`, `staged_ws`) — stays |
| `tests/backend/test_loader.py` | Already mirrors `backend/loader.py` |
| `tests/fuse/test_fs.py` | Already mirrors `fuse/fs.py` |
| `tests/fuse/test_loader.py` | Already mirrors `fuse/loader.py` |
| `tests/observe/test_entries.py` | Already mirrors `observe/entries.py` |
| `tests/observe/test_inspector.py` | Already mirrors `observe/inspector.py` |
| `tests/observe/test_session_log.py` | Already mirrors `observe/session_log.py` |
| `tests/runtime/test_local.py` | Already mirrors `runtime/local.py` |
| `tests/runtime/test_remote.py` | Already mirrors `runtime/remote.py` |
| `tests/cli/test_client.py` | Already mirrors `cli/client.py` |
| `tests/workspace/test_mount.py` | Already mirrors `workspace/mount.py` |
| `tests/workspace/test_sync.py` | Already mirrors `workspace/sync.py` |
| `tests/workspace/cache/test_policy.py` | Already mirrors `workspace/cache/policy.py` |
| `tests/workspace/cache/test_store.py` | Already mirrors `workspace/cache/store.py` |

---

## Section 6: Special Cases

### 6a. conftest.py files

**`tests/conftest.py`** — stays at root. Provides `memory_backend`, `write_ws`, `staged_ws` fixtures used across many test files. No change needed.

**`tests/server/conftest.py`** — currently provides a `client` fixture (TestClient) and `clear_sessions` autouse fixture. After `tests/server/test_app.py` moves to `tests/server/fastapi/test_app.py`, this conftest needs to move too.

- `tests/server/conftest.py` → `tests/server/fastapi/conftest.py`

Note: `tests/server/test_io_router.py` defines its own local `client` and `clear_sessions` fixtures (duplicating conftest). After moving to `tests/server/fastapi/routers/test_io_router.py`, those local fixtures can be removed and the file can rely on the parent `tests/server/fastapi/conftest.py`.

```bash
git mv tests/server/conftest.py tests/server/fastapi/conftest.py
```

### 6b. Integration tests

**`tests/integration/test_remote_transfer.py`** — this is a cross-cutting integration test that tests the interaction between `commands/custom/registry.py` and `commands/remote/client.py` together. It does not map to a single source file.

**Decision:** Keep `tests/integration/` directory and file exactly as-is. Integration tests are explicitly exempt from the one-to-one mapping rule.

### 6c. New `__init__.py` files needed in new directories

The following directories will be newly created and need `__init__.py` files:
```
tests/backend/local/__init__.py
tests/backend/network/__init__.py
tests/backend/object_storage/__init__.py
tests/commands/builtin/__init__.py
tests/commands/custom/__init__.py
tests/commands/remote/__init__.py
tests/cli/commands/__init__.py
tests/fuse/backend/__init__.py
tests/fuse/backend/local/__init__.py
tests/fuse/filetype/__init__.py
tests/fuse/remote/__init__.py
tests/observe/fastapi/__init__.py
tests/runtime/accessors/__init__.py
tests/server/fastapi/__init__.py
tests/server/fastapi/routers/__init__.py
tests/server/service/__init__.py
```

### 6d. `tests/commands/test_init.py` rename

`tests/commands/test_init.py` tests `mirage/commands/__init__.py`. It should be renamed to `tests/commands/test___init__.py` (double underscore) to follow Python conventions for test files that test `__init__.py` modules.

```bash
git mv tests/commands/test_init.py tests/commands/test___init__.py
```

---

## Section 7: Commands Custom Registry — `test_commands_has_ls` in `test_init.py`

Looking carefully at `tests/commands/test_init.py`: it imports `from mirage.commands import COMMAND_COST, COMMANDS` and tests the `COMMANDS` and `COMMAND_COST` dicts. These are defined in `mirage/commands/__init__.py`. The file is correctly testing `__init__.py`. Rename only.

---

## Complete Execution Order

1. **Create new `__init__.py` files** in all new directories listed in 6c.

2. **Simple git mv renames** (Section 1 renames, 6d rename):
   ```bash
   git mv tests/runtime/test_runtime.py tests/runtime/test_base.py
   git mv tests/workspace/test_cache_entry.py tests/workspace/cache/test_entry.py
   git mv tests/server/test_file_cache.py tests/server/service/test_cache.py
   git mv tests/commands/test_init.py tests/commands/test___init__.py
   ```

3. **Pure git mv moves** (Section 2 — no content changes needed):
   ```bash
   git mv tests/backend/test_local.py tests/backend/local/test_local.py
   git mv tests/backend/test_memory.py tests/backend/local/test_memory.py
   git mv tests/backend/test_proxy.py tests/backend/network/test_proxy.py
   git mv tests/commands/test_nav.py tests/commands/builtin/test_nav.py
   git mv tests/commands/test_handlers.py tests/commands/builtin/test_handlers.py
   git mv tests/commands/test_read.py tests/commands/builtin/test_read.py
   git mv tests/commands/test_search.py tests/commands/builtin/test_search.py
   git mv tests/commands/test_write.py tests/commands/builtin/test_write.py
   git mv tests/commands/test_client.py tests/commands/remote/test_client.py
   git mv tests/server/test_arg_resolver.py tests/commands/custom/test_arg_resolver.py
   git mv tests/server/test_app.py tests/server/fastapi/test_app.py
   git mv tests/server/test_io_router.py tests/server/fastapi/routers/test_io_router.py
   git mv tests/server/test_service.py tests/server/service/test_local.py
   git mv tests/observe/test_api.py tests/observe/fastapi/test_router.py
   git mv tests/server/conftest.py tests/server/fastapi/conftest.py
   ```

4. **Content-modifying operations** (Sections 3 and 4 — require editing):
   - Split `tests/backend/test_configs.py` → `tests/backend/object_storage/test_s3.py` + `tests/backend/object_storage/test_r2.py` + merge ExternalCommand tests into `tests/commands/custom/test_registry.py`
   - Move+split `tests/server/test_registry.py` → `tests/commands/custom/test_registry.py` (bulk) + `tests/server/test_constants.py` (`test_parse_size_units` only)
   - Split `tests/fuse/test_default.py` → `tests/fuse/backend/test_default.py` + `tests/fuse/backend/local/test_memory.py`
   - Split `tests/fuse/test_format_ops.py` → `tests/fuse/filetype/test_parquet.py` + `tests/fuse/filetype/test_csv.py`
   - Split `tests/fuse/test_registry.py` → keep `tests/fuse/test_registry.py` (FuseOpRegistry tests) + create `tests/fuse/test___init__.py` (FUSE_OPS dict tests)
   - Split `tests/cli/test_app.py` → keep `tests/cli/test_app.py` (app/help test) + create `tests/cli/commands/test_workspace.py`
   - Split `tests/runtime/test_accessors.py` → `tests/runtime/accessors/test_local.py` + merge 2 MemoryRuntime tests into `tests/runtime/test_memory.py`
   - Merge `tests/workspace/test_init.py` + `tests/workspace/test_lock.py` + `tests/workspace/test_write_ops.py` → `tests/workspace/test___init__.py`
   - Merge `tests/test_types.py` + `tests/types/test_types.py` → `tests/test_types.py`, delete `tests/types/`

5. **Remove obsolete files** after merges:
   ```bash
   git rm tests/backend/test_configs.py
   git rm tests/fuse/test_default.py
   git rm tests/fuse/test_format_ops.py
   git rm tests/workspace/test_init.py
   git rm tests/workspace/test_lock.py
   git rm tests/workspace/test_write_ops.py
   git rm tests/types/test_types.py
   git rm tests/types/__init__.py
   git rm tests/server/test_registry.py
   git rm tests/runtime/test_accessors.py
   ```

---

## Summary Table

| Category | Count | Files |
|---|---|---|
| Stays unchanged | 14 | conftest.py (root), test_loader.py (backend), test_fs.py, test_loader.py (fuse), test_entries.py, test_inspector.py, test_session_log.py, test_local.py (runtime), test_remote.py (runtime), test_client.py (cli), test_mount.py, test_sync.py, cache/test_policy.py, cache/test_store.py |
| Rename only | 4 | test_runtime.py→test_base.py, test_cache_entry.py→cache/test_entry.py, test_file_cache.py→service/test_cache.py, test_init.py→test___init__.py (commands) |
| Move only | 15 | backend/test_local.py, backend/test_memory.py, backend/test_proxy.py, 5 commands/builtin/*, commands/remote/test_client.py, commands/custom/test_arg_resolver.py, server/fastapi/test_app.py, server/fastapi/routers/test_io_router.py, server/service/test_local.py, observe/fastapi/test_router.py, server/conftest.py |
| Split | 7 | test_configs.py, test_registry.py (server), fuse/test_default.py, fuse/test_format_ops.py, fuse/test_registry.py, cli/test_app.py, runtime/test_accessors.py |
| Merge | 2 | workspace (3→1), types (2→1) |
| New files created | 11 | test_s3.py, test_r2.py, fuse/backend/test_default.py, fuse/backend/local/test_memory.py, filetype/test_csv.py, filetype/test_parquet.py, fuse/test___init__.py, cli/commands/test_workspace.py, runtime/accessors/test_local.py, server/test_constants.py, workspace/test___init__.py |
| Integration (exempt) | 1 | integration/test_remote_transfer.py |

---

### Critical Files for Implementation

- `/Users/agent/strukto/mirage/tests/backend/test_configs.py` - Source for 3-way split; ExternalCommand tests go to commands/custom/test_registry.py, S3/R2 config tests go to new object_storage/ files
- `/Users/agent/strukto/mirage/tests/server/test_registry.py` - Source for the largest split; bulk moves to tests/commands/custom/test_registry.py while test_parse_size_units extracts to tests/server/test_constants.py
- `/Users/agent/strukto/mirage/tests/workspace/test_init.py` - One of three files merging into tests/workspace/test___init__.py; contains the `_make_ws` helper that conflicts with test_write_ops.py's helper (needs renaming during merge)
- `/Users/agent/strukto/mirage/tests/fuse/test_registry.py` - Needs the FUSE_OPS dict tests split out to tests/fuse/test___init__.py while registry tests stay in place
- `/Users/agent/strukto/mirage/tests/cli/test_app.py` - Split point where workspace command tests separate from the CLI entry point test; _run helper needs careful handling in both resulting files

agentId: ab9926470f463a379 (for resuming to continue this agent's work if needed)
<usage>total_tokens: 118326
tool_uses: 53
duration_ms: 225962</usage>

Prompt:
Design a plan to reorganize the test directory so each source file has exactly one test file mirroring its path: `mirage/a/b/c.py` → `tests/a/b/test_c.py`.

**Current source structure:**
```
mirage/
├── types.py
├── backend/
│   ├── base.py, filetype.py, loader.py
│   ├── local/ (local.py, memory.py)
│   ├── network/ (http.py, proxy.py)
│   └── object_storage/ (r2.py, s3.py)
├── cli/ (app.py, client.py, config.py, helpers.py)
│   └── commands/ (command.py, observe.py, server.py, workspace.py)
├── commands/
│   ├── __init__.py
│   ├── builtin/ (handlers.py, nav.py, net.py, read.py, search.py, write.py)
│   ├── custom/ (arg_resolver.py, executor.py, registry.py)
│   └── remote/ (auth.py, client.py, fastapi/app.py, grpc/server.py)
├── fuse/
│   ├── base.py, fs.py, loader.py, registry.py
│   ├── backend/ (default.py, local/local.py, local/memory.py, object_storage/s3.py)
│   ├── filetype/ (csv.py, parquet.py)
│   └── remote/ (remote.py)
├── observe/ (entries.py, inspector.py, session_log.py, fastapi/router.py)
├── runtime/ (base.py, local.py, memory.py, remote.py)
│   └── accessors/ (base.py, local.py, remote.py)
├── server/
│   ├── constants.py, routes.py
│   ├── fastapi/ (app.py, config.py, daemon.py, routers/command_router.py, routers/io_router.py, routers/workspace_router.py)
│   └── service/ (base.py, cache.py, local.py)
└── workspace/ (__init__.py, mount.py, sync.py)
    └── cache/ (entry.py, policy.py, store.py)
```

**Current test files and what source files they test:**

| Current test file | Tests source file(s) |
|---|---|
| tests/conftest.py | Fixtures only — stays |
| tests/test_types.py | mirage/types.py |
| tests/backend/test_configs.py | object_storage/s3.py (S3Config), object_storage/r2.py (R2Config), commands/custom/registry.py (ExternalCommand) |
| tests/backend/test_loader.py | backend/loader.py |
| tests/backend/test_local.py | backend/local/local.py |
| tests/backend/test_proxy.py | backend/network/proxy.py |
| tests/cli/test_app.py | cli/app.py + cli/commands/workspace.py |
| tests/cli/test_client.py | cli/client.py |
| tests/cli/test_config.py | cli/config.py |
| tests/commands/test_client.py | commands/remote/client.py |
| tests/commands/test_handlers.py | commands/builtin/handlers.py |
| tests/commands/test_init.py | commands/__init__.py |
| tests/commands/test_nav.py | commands/builtin/nav.py |
| tests/commands/test_read.py | commands/builtin/read.py |
| tests/commands/test_search.py | commands/builtin/search.py |
| tests/commands/test_write.py | commands/builtin/write.py |
| tests/fuse/test_default.py | fuse/backend/default.py + fuse/backend/local/memory.py |
| tests/fuse/test_format_ops.py | fuse/filetype/csv.py + fuse/filetype/parquet.py |
| tests/fuse/test_fs.py | fuse/fs.py |
| tests/fuse/test_loader.py | fuse/loader.py |
| tests/fuse/test_registry.py | fuse/registry.py + fuse/__init__.py |
| tests/fuse/test_remote.py | fuse/remote/remote.py |
| tests/integration/test_remote_transfer.py | Cross-cutting integration test |
| tests/observe/test_api.py | observe/fastapi/router.py |
| tests/observe/test_entries.py | observe/entries.py |
| tests/observe/test_inspector.py | observe/inspector.py |
| tests/observe/test_session_log.py | observe/session_log.py |
| tests/runtime/test_accessors.py | runtime/accessors/local.py |
| tests/runtime/test_local.py | runtime/local.py |
| tests/runtime/test_memory.py | runtime/memory.py |
| tests/runtime/test_remote.py | runtime/remote.py |
| tests/runtime/test_runtime.py | runtime/base.py |
| tests/server/conftest.py | Fixtures — stays |
| tests/server/test_app.py | server/fastapi/app.py |
| tests/server/test_arg_resolver.py | commands/custom/arg_resolver.py |
| tests/server/test_file_cache.py | server/service/cache.py |
| tests/server/test_io_router.py | server/fastapi/routers/io_router.py |
| tests/server/test_registry.py | commands/custom/registry.py |
| tests/server/test_service.py | server/service/local.py |
| tests/types/test_types.py | mirage/types.py (duplicate?) |
| tests/workspace/cache/test_policy.py | workspace/cache/policy.py |
| tests/workspace/cache/test_store.py | workspace/cache/store.py |
| tests/workspace/test_cache_entry.py | workspace/cache/entry.py |
| tests/workspace/test_init.py | workspace/__init__.py |
| tests/workspace/test_lock.py | workspace/__init__.py |
| tests/workspace/test_mount.py | workspace/mount.py |
| tests/workspace/test_sync.py | workspace/sync.py |
| tests/workspace/test_write_ops.py | workspace/__init__.py |

**Design the reorganization:**

1. List all files that need to MOVE (with old → new paths)
2. List files that need SPLITTING (one test file tests multiple source files)
3. List files that need MERGING (multiple test files test one source file: test_init.py + test_lock.py + test_write_ops.py all test workspace/__init__.py)
4. List files that need RENAMING (e.g. test_runtime.py → test_base.py)
5. List files that stay where they are
6. Note special cases (integration tests, conftest.py files, duplicate test files)

The plan should result in every test file at path `tests/X/Y/test_Z.py` testing exactly `mirage/X/Y/Z.py`.

Keep the plan practical: use git mv for moves, and for splits/merges, list exactly what functions go where.


The user is waiting for me to finalize the plan. Let me review the Plan agent's output, verify it against the codebase, and write the final plan to the plan file. Let me check a few critical files to validate the plan, then write it.

Let me check if `tests/backend/test_memory.py` exists (the plan references it for a move but I'm not sure it exists).


Let me verify a few critical details before finalizing the plan.


/Users/agent/strukto/mirage/tests/__init__.py
/Users/agent/strukto/mirage/tests/cli/__init__.py
/Users/agent/strukto/mirage/tests/commands/__init__.py
/Users/agent/strukto/mirage/tests/server/__init__.py
/Users/agent/strukto/mirage/tests/workspace/__init__.py
/Users/agent/strukto/mirage/tests/workspace/cache/__init__.py
/Users/agent/strukto/mirage/tests/backend/__init__.py
/Users/agent/strukto/mirage/tests/runtime/__init__.py
/Users/agent/strukto/mirage/tests/observe/__init__.py
/Users/agent/strukto/mirage/tests/fuse/__init__.py
/Users/agent/strukto/mirage/tests/types/__init__.py
/Users/agent/strukto/mirage/tests/integration/__init__.py


Bash

Check existing test directories


tests/backend/:
__init__.py
__pycache__
test_configs.py
test_loader.py
test_local.py
test_memory.py
test_proxy.py

tests/types/:
__init__.py
__pycache__
test_types.py


Conversation compacted


This session is being continued from a previous conversation that ran out of context. The summary below covers the earlier portion of the conversation.

Analysis:
Let me chronologically analyze the conversation:

1. **Initial Request**: User asked to refactor `mirage/backends` by grouping similar backends into subfolders (e.g., s3 and r2 as object storage).

2. **Brainstorming Phase**: Used brainstorming skill. Explored all backend files. Identified groupings:
   - object_storage: s3.py, r2.py
   - local: local.py, memory.py
   - network: http.py, proxy.py
   - base.py and loader.py stay at top level

3. **First Refactoring (backends subfolders)**: Created subdirectories, moved files with git mv, created __init__.py files, updated all imports across 50+ files. Tests passed (609).

4. **User asked about `_guess_type`**: What it's for, improvements for unmet file extensions.

5. **User said to extract `_guess_type`**: Create filetype.py with extensible EXTENSION_MAP, default to "binary" for unknown types, AND rename `backends` to `backend`.

6. **Second Refactoring (filetype.py + rename)**:
   - Created `mirage/backends/filetype.py` with `EXTENSION_MAP`, `DEFAULT_TYPE = "binary"`, and `guess_type()` function
   - Updated all `_guess_type` references to use new module
   - Renamed `backends/` to `backend/` across entire codebase
   - All 609 tests passed

7. **User asked about FileType enum**: Asked to use enums and what the types are for.

8. **FileType enum update**: Added `CSV` to `FileType` enum, made `guess_type` return `FileType` instead of strings, tightened `FileStat.type` from `FileType | str | None` to `FileType | None`. Added image/zip/pdf extensions to EXTENSION_MAP.

9. **User asked to refactor fuse_ops**: Group similar ops, follow commands folder architecture, maybe group by file types.

10. **Brainstorming fuse_ops**: Identified two kinds of ops:
    - Backend-strategy ops: default, local, memory, s3
    - Format ops: csv, parquet
    - Proposed: backend/, filetype/, remote/ subfolders

11. **User said rename `fuse_ops` to `fuse`** and incorporate top-level `fuse/` directory.

12. **Fuse refactoring**:
    - Merged separate `fuse/` package (mirage-fuse) into `mirage/fuse/`
    - Made fusepy an optional dependency
    - Organized into backend/, filetype/, remote/
    - Removed old fuse/ package directory
    - Updated pyproject.toml
    - 641 tests passing

13. **User asked to group fuse backends similarly**: local/, object_storage/ within fuse/backend/

14. **Fuse backend subgrouping**: Moved local.py, memory.py into local/, s3.py into object_storage/. 641 tests passing.

15. **User asked to refactor tests**:
    - No classes, just def test_xxx (already done)
    - Each source file should have exactly one test file mirroring its path
    - Entered plan mode

16. **Exploration**: Found all tests already use plain functions. Mapped all test files to source files.

17. **Plan agent**: Produced detailed plan for test reorganization including moves, splits, merges, and renames.

18. **Verification**: Checked that tests/backend/test_memory.py exists and tests/types/ directory exists.

Now I need to capture the current state accurately. We are in plan mode, working on writing the final plan to the plan file.

Key files in current state:

**mirage/backend/filetype.py** (created):
```python
from mirage.types import FileType

EXTENSION_MAP: dict[str, FileType] = {
    "json": FileType.JSON,
    "jsonl": FileType.JSON,
    "csv": FileType.CSV,
    "tsv": FileType.CSV,
    "txt": FileType.TEXT,
    ...
    "pdf": FileType.PDF,
}

DEFAULT_TYPE = FileType.BINARY

def guess_type(path: str) -> FileType:
    ext = path.rsplit(".", 1)[-1].lower() if "." in path else ""
    return EXTENSION_MAP.get(ext, DEFAULT_TYPE)
```

**mirage/types.py** (modified):
- Added `CSV = "csv"` to FileType enum
- Changed `FileStat.type` from `FileType | str | None` to `FileType | None`

**pyproject.toml** (modified):
- Removed mirage-fuse dependency
- Added `fuse = ["fusepy>=3.0.1"]` optional dep
- Removed uv workspace/sources for fuse
- Added fusepy to `all` optional dep

Current directory structures:
```
mirage/backend/
├── __init__.py, base.py, filetype.py, loader.py
├── local/ (local.py, memory.py)
├── network/ (http.py, proxy.py)
└── object_storage/ (r2.py, s3.py)

mirage/fuse/
├── __init__.py, base.py, fs.py, loader.py, registry.py, README.md
├── backend/ (default.py, local/local.py, local/memory.py, object_storage/s3.py)
├── filetype/ (csv.py, parquet.py)
└── remote/ (remote.py)
```

Errors encountered:
1. test_loader.py had string literals with old module paths (not just imports) - fixed by updating the string literals
2. test_app.py patch target `mirage.cli.commands.workspace.fuse_mount_fn` didn't work because it was a lazy import inside a function - fixed by patching `mirage.fuse.fs.mount` instead

The current work is writing the plan file for test reorganization. We're in plan mode phase 4 (writing final plan).

Summary:
1. Primary Request and Intent:
   The user requested a series of refactorings to the `mirage` project:
   - **Backend refactoring**: Group similar backends into subfolders (object_storage, local, network) within `mirage/backends/`
   - **Extract `_guess_type`**: Move the hardcoded type-guessing function from `memory.py` into a new `filetype.py` module with an extensible `EXTENSION_MAP` dict and `"binary"` as default for unknown extensions
   - **Rename `backends` → `backend`**: Singular naming across the entire codebase
   - **Use FileType enum**: Make `guess_type` return `FileType` enum values instead of raw strings, tighten `FileStat.type` to `FileType | None`
   - **Refactor fuse_ops**: Group fuse ops by backend type and file type (backend/, filetype/, remote/), rename `fuse_ops` to `fuse`, and merge the separate top-level `fuse/` package into `mirage/fuse/`
   - **Refactor tests** (current/pending): Reorganize test files so each source file has exactly one test file mirroring its path: `mirage/a/b/c.py` → `tests/a/b/test_c.py`. No test classes, only `def test_xxx` functions.

2. Key Technical Concepts:
   - Python package reorganization with `git mv` for proper tracking
   - Sub-package `__init__.py` files for re-exporting symbols
   - Lazy imports for optional dependencies (boto3, fusepy, pyarrow)
   - `FileType(str, Enum)` for type-safe file type classification
   - Extensible `EXTENSION_MAP` dict pattern for file type guessing
   - Optional dependency groups in `pyproject.toml` (`[project.optional-dependencies]`)
   - FUSE filesystem ops architecture: backend-strategy ops vs format-rendering ops
   - Merging a separate Python package (mirage-fuse) into the main package as an optional dependency

3. Files and Code Sections:

   - **mirage/backend/filetype.py** (CREATED) — Central file type detection module extracted from `_guess_type` in memory.py
     ```python
     from mirage.types import FileType

     EXTENSION_MAP: dict[str, FileType] = {
         "json": FileType.JSON,
         "jsonl": FileType.JSON,
         "csv": FileType.CSV,
         "tsv": FileType.CSV,
         "txt": FileType.TEXT,
         "md": FileType.TEXT,
         "py": FileType.TEXT,
         "js": FileType.TEXT,
         "ts": FileType.TEXT,
         "yaml": FileType.TEXT,
         "yml": FileType.TEXT,
         "toml": FileType.TEXT,
         "png": FileType.IMAGE_PNG,
         "jpg": FileType.IMAGE_PNG,
         "jpeg": FileType.IMAGE_JPEG,
         "gif": FileType.IMAGE_GIF,
         "zip": FileType.ZIP,
         "gz": FileType.GZIP,
         "pdf": FileType.PDF,
     }

     DEFAULT_TYPE = FileType.BINARY

     def guess_type(path: str) -> FileType:
         ext = path.rsplit(".", 1)[-1].lower() if "." in path else ""
         return EXTENSION_MAP.get(ext, DEFAULT_TYPE)
     ```

   - **mirage/types.py** (MODIFIED) — Added CSV to FileType enum, tightened FileStat.type
     ```python
     class FileType(str, Enum):
         DIRECTORY = "directory"
         TEXT = "text"
         BINARY = "binary"
         JSON = "json"
         CSV = "csv"
         IMAGE_PNG = "image/png"
         IMAGE_JPEG = "image/jpeg"
         IMAGE_GIF = "image/gif"
         ZIP = "application/zip"
         GZIP = "application/gzip"
         PDF = "application/pdf"

     class FileStat(BaseModel):
         model_config = ConfigDict(frozen=True)
         name: str
         size: int | None = None
         modified: str | None = None
         type: FileType | None = None  # was FileType | str | None
         extra: dict = Field(default_factory=dict)
     ```

   - **mirage/backend/__init__.py** (MODIFIED) — Re-exports from new subpackages
     ```python
     from mirage.backend.base import BaseBackend
     from mirage.backend.local import LocalBackend, MemoryBackend
     from mirage.backend.network import ProxyBackend

     __all__ = ["BaseBackend", "LocalBackend", "MemoryBackend", "ProxyBackend"]
     ```

   - **mirage/backend/local/__init__.py** (CREATED) — Re-exports for local subpackage
     ```python
     from mirage.backend.local.local import LocalBackend
     from mirage.backend.local.memory import MemoryBackend

     __all__ = ["LocalBackend", "MemoryBackend"]
     ```

   - **mirage/backend/network/__init__.py** (CREATED)
     ```python
     from mirage.backend.network.proxy import ProxyBackend
     __all__ = ["ProxyBackend"]
     ```

   - **mirage/backend/object_storage/__init__.py** (CREATED) — Minimal, lazy loading
     ```python
     __all__: list[str] = []
     ```

   - **mirage/fuse/__init__.py** (CREATED) — FUSE_OPS singleton registry
     ```python
     from mirage.fuse.backend.default import DefaultFuseOp
     from mirage.fuse.backend.local.local import LocalFuseOp
     from mirage.fuse.backend.local.memory import MemoryFuseOp
     from mirage.fuse.backend.object_storage.s3 import S3FuseOp
     from mirage.fuse.base import FuseOp
     from mirage.fuse.filetype.csv import CsvFuseOp
     from mirage.fuse.filetype.parquet import ParquetFuseOp

     FUSE_OPS: dict[str, FuseOp] = {
         "default": DefaultFuseOp(),
         "s3": S3FuseOp(),
         "memory": MemoryFuseOp(),
         "local": LocalFuseOp(),
         "parquet": ParquetFuseOp(),
         "csv": CsvFuseOp(),
     }
     ```

   - **mirage/fuse/fs.py** (MOVED from fuse/mirage_fuse/fs.py) — MirageFS FUSE filesystem implementation, imports updated from `mirage.fuse_ops` to `mirage.fuse` and from `mirage_fuse` to `mirage.fuse`

   - **pyproject.toml** (MODIFIED) — Removed mirage-fuse workspace dependency, added fusepy as optional
     ```toml
     [project.optional-dependencies]
     s3      = ["boto3>=1.34"]
     r2      = ["boto3>=1.34"]
     http    = []
     fuse    = ["fusepy>=3.0.1"]
     parquet = ["pandas>=3.0.1", "pyarrow>=15.0"]
     all     = ["boto3>=1.34", "fusepy>=3.0.1", "pyarrow>=15.0"]
     ```
     The `[tool.uv.workspace]` and `[tool.uv.sources]` sections for mirage-fuse were removed.

   - **mirage/__init__.py** (MODIFIED)
     ```python
     from mirage.backend.local import LocalBackend, MemoryBackend
     from mirage.types import (ConsistencyPolicy, FileStat, Inode, MountMode,
                               SyncPolicy, SyncResult)
     from mirage.workspace import Workspace
     ```

4. Errors and Fixes:
   - **test_loader.py string literals**: After renaming `backends` → `backend`, `tests/backend/test_loader.py` had string literals like `"mirage.backends.memory:MemoryBackend"` passed to `load_backend_class()`. These are dotpath strings, not Python imports, so the mass import replacement missed them. Fixed by manually updating lines 7 and 36.
   - **test_app.py patch target**: After merging fuse/ into mirage/fuse/, the subagent changed the mock patch from `patch("mirage_fuse.mount")` to `patch("mirage.cli.commands.workspace.fuse_mount_fn")`. But `fuse_mount_fn` is a lazy import inside a function body, so it doesn't exist as a module attribute. Fixed by changing to `patch("mirage.fuse.fs.mount")`.
   - **test_loader.py string literals (second time)**: After renaming `fuse_ops` → `fuse`, the test_loader.py in fuse tests had string literal `"mirage.fuse.backend.memory:MemoryFuseOp"` which needed updating to `"mirage.fuse.backend.local.memory:MemoryFuseOp"` after the backend subgrouping.

5. Problem Solving:
   - All refactorings completed successfully with 641 tests passing
   - Used parallel subagents (sonnet model) for mass import replacements across 50+ files
   - Handled cross-references between backend modules (e.g., `_guess_type` imported from memory.py by s3.py, http.py, local.py)
   - Managed the complexity of merging a separate Python package (mirage-fuse with its own pyproject.toml) into the main package
   - Currently in plan mode designing the test directory reorganization

6. All User Messages:
   - "Ok now let's refactor the mirage/backends... let's group similar backend together to same subfolder... for example s3 and r2 are object storage (maybe a better name) etc. and loader etc we should put to another folder etc. let's brainstorm and do the refactoring and renaming and new subfolders"
   - "do" (confirming the proposed grouping)
   - "Why we use _guess_type? What's that for? any improvement for that? because in the future we may need to handle unmet file extension"
   - "do this and if unmet file type, should we use text by default or binary? also rename backends to backend?"
   - "What's the default filetype for system like linux?"
   - "ok let's follow them" (confirming binary default)
   - "Can we use enums for them? and what's that used for? I mean those types give me an example"
   - "FileType | None also you didn't answer me why this file type matters"
   - "Ok let's brainstorm and refactor and clean fuse_ops... let's follow the commands folder for the architecture... and maybe we should group those op with respect to different file types?"
   - "format of file type?" (clarifying subfolder naming)
   - "also is fuse ops a good folder name or just fuse?"
   - "let's rename to fuse and let's also incorporate the top-level fuse to this subfolder"
   - "Yea we change the dependency also remember to include the root fuse README.md here also"
   - "can we similarly group the backends in fuse to object_storage etc?"
   - "Ok let's refactor the tests by 1. no class just have def test_xxx 2. each source file should have exactly one test file for example backend/abc/def.py should have tests/backend/abc/test_def.py"
   - "Let's brainstorm and plan" (entered plan mode)

7. Pending Tasks:
   - **Test directory reorganization** (in plan mode): Finalize plan and implement the restructuring of the entire `tests/` directory to mirror the `mirage/` source structure 1:1. This involves:
     - ~15 pure file moves (git mv)
     - ~4 renames
     - ~7 file splits (test files that cover multiple source files)
     - ~2 file merges (multiple test files covering one source file)
     - Creating ~16 new `__init__.py` files in new directories

8. Current Work:
   We are in **plan mode** (phase 3-4), working on the test directory reorganization. The Plan agent produced a comprehensive plan. I just verified that `tests/backend/test_memory.py` exists and `tests/types/` directory exists. The plan file at `/Users/agent/.claude/plans/shimmering-leaping-wozniak.md` has NOT been written yet. I need to:
   1. Write the final plan to the plan file
   2. Call ExitPlanMode to get user approval

   The current directory structures after all previous refactorings:
   ```
   mirage/backend/
   ├── __init__.py, base.py, filetype.py, loader.py
   ├── local/ (local.py, memory.py)
   ├── network/ (http.py, proxy.py)
   └── object_storage/ (r2.py, s3.py)

   mirage/fuse/
   ├── __init__.py, base.py, fs.py, loader.py, registry.py, README.md
   ├── backend/ (default.py, local/{local.py,memory.py}, object_storage/s3.py)
   ├── filetype/ (csv.py, parquet.py)
   └── remote/ (remote.py)
   ```

   641 tests currently passing, 1 skipped.

9. Optional Next Step:
   Write the final plan to `/Users/agent/.claude/plans/shimmering-leaping-wozniak.md` and call ExitPlanMode. The user's most recent explicit request: "Ok let's refactor the tests by 1. no class just have def test_xxx 2. each source file should have exactly one test file for example backend/abc/def.py should have tests/backend/abc/test_def.py" followed by "Let's brainstorm and plan". We were in plan mode exploring the test structure and a Plan agent produced a detailed reorganization plan. The next step is to finalize this plan into the plan file and exit plan mode for user approval.

If you need specific details from before compaction (like exact code snippets, error messages, or content you generated), read the full transcript at: /Users/agent/.claude/projects/-Users-agent-strukto-mirage/664396f7-55a4-4604-8289-aa7142480db4.jsonl
Please continue the conversation from where we left off without asking the user any further questions. Continue with the last task that you were asked to work on.


Let me continue where we left off. I need to:
1. Explore the current test directory structure to understand what needs to be reorganized
2. Write the final plan to the plan file
3. Call ExitPlanMode

Let me first check the current test structure and source structure to make sure I have accurate information.


Let me verify the current test and source structures before writing the final plan.


Agent

Explore current test structure

Prompt:
Thoroughly explore the current test directory structure and source directory structure for this project. I need:

1. Full recursive listing of all files under tests/ (including __init__.py files)
2. Full recursive listing of all .py files under mirage/ (the source)
3. For each test file, identify which source file(s) it tests by looking at its imports

Use glob patterns like "tests/**/*.py" and "mirage/**/*.py" to get complete listings. Then read a few key test files to understand which source files they cover, especially:
- tests/backend/ files
- tests/fuse/ files
- tests/test_workspace.py or similar
- tests/test_types.py or similar
- Any test files that seem to cover multiple source files

Be very thorough - I need the complete picture.


I now have a complete picture of the entire test and source structure. Here is the comprehensive report.

---

## Complete Project Structure

### Source Files: mirage/ (100 files)

```
mirage/__init__.py
mirage/types.py

mirage/backend/__init__.py
mirage/backend/base.py
mirage/backend/filetype.py
mirage/backend/loader.py
mirage/backend/local/__init__.py
mirage/backend/local/local.py
mirage/backend/local/memory.py
mirage/backend/network/__init__.py
mirage/backend/network/http.py
mirage/backend/network/proxy.py
mirage/backend/object_storage/__init__.py
mirage/backend/object_storage/r2.py
mirage/backend/object_storage/s3.py

mirage/cli/__init__.py
mirage/cli/app.py
mirage/cli/client.py
mirage/cli/config.py
mirage/cli/helpers.py
mirage/cli/commands/__init__.py
mirage/cli/commands/command.py
mirage/cli/commands/observe.py
mirage/cli/commands/server.py
mirage/cli/commands/workspace.py

mirage/commands/__init__.py
mirage/commands/builtin/__init__.py
mirage/commands/builtin/handlers.py
mirage/commands/builtin/nav.py
mirage/commands/builtin/net.py
mirage/commands/builtin/read.py
mirage/commands/builtin/search.py
mirage/commands/builtin/write.py
mirage/commands/custom/__init__.py
mirage/commands/custom/arg_resolver.py
mirage/commands/custom/executor.py
mirage/commands/custom/registry.py
mirage/commands/remote/__init__.py
mirage/commands/remote/auth.py
mirage/commands/remote/client.py
mirage/commands/remote/fastapi/__init__.py
mirage/commands/remote/fastapi/app.py
mirage/commands/remote/grpc/__init__.py
mirage/commands/remote/grpc/proto/__init__.py
mirage/commands/remote/grpc/proto/command_pb2.py
mirage/commands/remote/grpc/proto/command_pb2_grpc.py
mirage/commands/remote/grpc/server.py

mirage/fuse/__init__.py
mirage/fuse/base.py
mirage/fuse/fs.py
mirage/fuse/loader.py
mirage/fuse/registry.py
mirage/fuse/backend/__init__.py
mirage/fuse/backend/default.py
mirage/fuse/backend/local/__init__.py
mirage/fuse/backend/local/local.py
mirage/fuse/backend/local/memory.py
mirage/fuse/backend/object_storage/__init__.py
mirage/fuse/backend/object_storage/s3.py
mirage/fuse/filetype/__init__.py
mirage/fuse/filetype/csv.py
mirage/fuse/filetype/parquet.py
mirage/fuse/remote/__init__.py
mirage/fuse/remote/remote.py

mirage/observe/__init__.py
mirage/observe/entries.py
mirage/observe/inspector.py
mirage/observe/session_log.py
mirage/observe/fastapi/__init__.py
mirage/observe/fastapi/router.py

mirage/runtime/__init__.py
mirage/runtime/base.py
mirage/runtime/local.py
mirage/runtime/memory.py
mirage/runtime/remote.py
mirage/runtime/accessors/__init__.py
mirage/runtime/accessors/base.py
mirage/runtime/accessors/local.py
mirage/runtime/accessors/remote.py

mirage/server/__init__.py
mirage/server/constants.py
mirage/server/routes.py
mirage/server/fastapi/__init__.py
mirage/server/fastapi/app.py
mirage/server/fastapi/config.py
mirage/server/fastapi/daemon.py
mirage/server/fastapi/routers/__init__.py
mirage/server/fastapi/routers/command_router.py
mirage/server/fastapi/routers/io_router.py
mirage/server/fastapi/routers/workspace_router.py
mirage/server/service/__init__.py
mirage/server/service/base.py
mirage/server/service/cache.py
mirage/server/service/local.py

mirage/workspace/__init__.py
mirage/workspace/mount.py
mirage/workspace/sync.py
mirage/workspace/cache/__init__.py
mirage/workspace/cache/entry.py
mirage/workspace/cache/policy.py
mirage/workspace/cache/store.py
```

---

### Test Files: tests/ (63 files)

```
tests/__init__.py
tests/conftest.py

tests/backend/__init__.py
tests/backend/test_configs.py
tests/backend/test_loader.py
tests/backend/test_local.py
tests/backend/test_memory.py
tests/backend/test_proxy.py

tests/cli/__init__.py
tests/cli/test_app.py
tests/cli/test_client.py
tests/cli/test_config.py

tests/commands/__init__.py
tests/commands/test_client.py
tests/commands/test_handlers.py
tests/commands/test_init.py
tests/commands/test_nav.py
tests/commands/test_read.py
tests/commands/test_search.py
tests/commands/test_write.py

tests/fuse/__init__.py
tests/fuse/test_default.py
tests/fuse/test_format_ops.py
tests/fuse/test_fs.py
tests/fuse/test_loader.py
tests/fuse/test_registry.py
tests/fuse/test_remote.py

tests/integration/__init__.py
tests/integration/test_remote_transfer.py

tests/observe/__init__.py
tests/observe/test_api.py
tests/observe/test_entries.py
tests/observe/test_inspector.py
tests/observe/test_session_log.py

tests/runtime/__init__.py
tests/runtime/test_accessors.py
tests/runtime/test_local.py
tests/runtime/test_memory.py
tests/runtime/test_remote.py
tests/runtime/test_runtime.py

tests/server/__init__.py
tests/server/conftest.py
tests/server/test_app.py
tests/server/test_arg_resolver.py
tests/server/test_file_cache.py
tests/server/test_io_router.py
tests/server/test_registry.py
tests/server/test_service.py

tests/test_types.py

tests/types/__init__.py
tests/types/test_types.py

tests/workspace/__init__.py
tests/workspace/test_cache_entry.py
tests/workspace/test_init.py
tests/workspace/test_lock.py
tests/workspace/test_mount.py
tests/workspace/test_sync.py
tests/workspace/test_write_ops.py
tests/workspace/cache/__init__.py
tests/workspace/cache/test_policy.py
tests/workspace/cache/test_store.py
```

---

### Test-to-Source Mapping (by test file)

#### tests/backend/

| Test File | Source File(s) Tested | Key Imports |
|---|---|---|
| `test_memory.py` | `mirage/backend/local/memory.py` | Uses `memory_backend` fixture from `conftest.py`; imports `mirage.types.FileType` |
| `test_local.py` | `mirage/backend/local/local.py` | `from mirage.backend.local.local import LocalBackend` |
| `test_proxy.py` | `mirage/backend/network/proxy.py`, `mirage/server/fastapi/app.py`, `mirage/observe/session_log.py` | `from mirage.backend.network.proxy import ProxyBackend`, full server stack via TestClient |
| `test_configs.py` | `mirage/backend/object_storage/s3.py`, `mirage/backend/object_storage/r2.py`, `mirage/commands/custom/registry.py` | Tests `S3Config`, `R2Config`, `ExternalCommand` Pydantic models |
| `test_loader.py` | `mirage/backend/loader.py` | `from mirage.backend.loader import load_backend_class` |

#### tests/fuse/

| Test File | Source File(s) Tested | Key Imports |
|---|---|---|
| `test_fs.py` | `mirage/fuse/fs.py`, `mirage/workspace/__init__.py`, `mirage/backend/local/memory.py` | `from mirage.fuse.fs import _MIRAGE_DIR, _MIRAGE_WHOAMI, MirageFS`; also exercises `mount_background` |
| `test_default.py` | `mirage/fuse/backend/default.py`, `mirage/fuse/backend/local/memory.py` | `from mirage.fuse.backend.default import DefaultFuseOp`, `from mirage.fuse.backend.local.memory import MemoryFuseOp` |
| `test_format_ops.py` | `mirage/fuse/filetype/csv.py`, `mirage/fuse/filetype/parquet.py` | `from mirage.fuse.filetype.csv import CsvFuseOp`, `from mirage.fuse.filetype.parquet import ParquetFuseOp` |
| `test_registry.py` | `mirage/fuse/registry.py`, `mirage/fuse/__init__.py` (FUSE_OPS dict), `mirage/workspace/__init__.py` | `from mirage.fuse import FUSE_OPS, FuseOp`, `from mirage.fuse.registry import FuseOpRegistry` |
| `test_loader.py` | `mirage/fuse/loader.py` | `from mirage.fuse.loader import build_registry_from_config, load_fuse_op_class` |
| `test_remote.py` | `mirage/fuse/remote/remote.py` | `from mirage.fuse.remote.remote import RemoteFuseOp` |

#### tests/workspace/

| Test File | Source File(s) Tested | Key Imports |
|---|---|---|
| `test_write_ops.py` | `mirage/workspace/__init__.py` (Workspace class) | `from mirage.workspace import Workspace`; tests write, rm, mv, cp, sync, reset |
| `test_init.py` | `mirage/workspace/__init__.py` (consistency/cache logic) | Tests `ConsistencyPolicy.NONE/ALWAYS/TTL/LAZY` behaviors |
| `test_lock.py` | `mirage/workspace/__init__.py` (lock_paths, unlock_paths) | Tests `ws.lock_paths()`, `ws._lock_path()`, `ws.unlock_paths()` |
| `test_mount.py` | `mirage/workspace/mount.py`, `mirage/workspace/__init__.py` | `from mirage.workspace.mount import MountRegistry`; tests duplicate prefix, per-mount modes |
| `test_sync.py` | `mirage/workspace/sync.py` | `from mirage.workspace.sync import DirtyTracker`; tests all `on_read/on_write/on_delete/on_sync` methods |
| `test_cache_entry.py` | `mirage/workspace/cache/entry.py` | `from mirage.workspace.cache.entry import CacheEntry` |
| `cache/test_store.py` | `mirage/workspace/cache/store.py` | `from mirage.workspace.cache.store import LRUCache` |
| `cache/test_policy.py` | `mirage/workspace/cache/policy.py`, `mirage/workspace/cache/entry.py` | `from mirage.workspace.cache.policy import metadata_matches` |

#### tests/observe/

| Test File | Source File(s) Tested | Key Imports |
|---|---|---|
| `test_session_log.py` | `mirage/observe/session_log.py`, `mirage/observe/entries.py` | `from mirage.observe.entries import CommandEntry, SessionEntry`, `from mirage.observe.session_log import SessionLog` |
| `test_entries.py` | `mirage/observe/entries.py` | `from mirage.observe.entries import CommandEntry, ParsedCommand, SessionEntry, WorkspaceInfo` |
| `test_api.py` | `mirage/observe/fastapi/router.py` (via the FastAPI app), `mirage/server/fastapi/app.py`, `mirage/observe/session_log.py` | Full server integration via TestClient; tests `/observe/*` endpoints |
| `test_inspector.py` | `mirage/observe/inspector.py`, `mirage/commands/__init__.py` (COMMANDS), `mirage/commands/custom/registry.py` | `from mirage.observe import inspector` |

#### tests/server/

| Test File | Source File(s) Tested | Key Imports |
|---|---|---|
| `test_app.py` | `mirage/server/fastapi/app.py`, `mirage/server/fastapi/routers/workspace_router.py`, full server stack | Full TestClient integration; all workspace lifecycle, exec, fuse-ops endpoints |
| `test_file_cache.py` | `mirage/server/service/cache.py` | `from mirage.server.service.cache import FileCache` |
| `test_io_router.py` | `mirage/server/fastapi/routers/io_router.py` (via app) | Tests all `/io/*` endpoints: create, write, read, stat, mkdir, readdir, unlink, rename, truncate, fuse_cat |
| `test_service.py` | `mirage/server/service/local.py` | `from mirage.server.service.local import LocalService`; full service API including fuse ops |
| `test_arg_resolver.py` | `mirage/commands/custom/arg_resolver.py` | `from mirage.commands.custom.arg_resolver import classify_args` |
| `test_registry.py` (server) | `mirage/commands/custom/registry.py`, `mirage/commands/remote/client.py`, `mirage/server/constants.py` | `from mirage.commands.custom.registry import CommandRegistry, ExternalCommand`; also tests `_parse_size` from `server/constants.py` |
| `conftest.py` | `mirage/server/fastapi/app.py`, `mirage/observe/session_log.py` | Provides shared `client` fixture and session-clearing autouse fixture |

#### tests/runtime/

| Test File | Source File(s) Tested | Key Imports |
|---|---|---|
| `test_runtime.py` | `mirage/runtime/base.py` | `from mirage.runtime.base import CommandNotAllowedError, Runtime` |
| `test_memory.py` | `mirage/runtime/memory.py` | `from mirage.runtime.memory import MemoryRuntime` |
| `test_local.py` | `mirage/runtime/local.py` | `from mirage.runtime.local import LocalRuntime` |
| `test_remote.py` | `mirage/runtime/remote.py`, `mirage/runtime/base.py`, `mirage/server/fastapi/app.py` | `from mirage.runtime.remote import RemoteRuntime`; full server integration via TestClient |
| `test_accessors.py` | `mirage/runtime/accessors/local.py`, `mirage/runtime/memory.py`, `mirage/commands/custom/registry.py`, `mirage/fuse/registry.py` | `from mirage.runtime.accessors.local import LocalCommandsAccessor, LocalFuseOpsAccessor, LocalWorkspaceOpsAccessor` |

#### tests/commands/

| Test File | Source File(s) Tested | Key Imports |
|---|---|---|
| `test_init.py` | `mirage/commands/__init__.py` (COMMANDS, COMMAND_COST dicts) | `from mirage.commands import COMMAND_COST, COMMANDS` |
| `test_handlers.py` | `mirage/commands/builtin/handlers.py` | Imports all handler functions `_ls, _cat, _head, _tail, _grep, _sort, _uniq, _cut, _tr, _mkdir, _touch, _cp, _mv, _rm, _sed, _echo, _tee, _stat, _pwd, _find, _du, _tree, _wc, _md5, _diff, _file, _resolve` plus the `COMMANDS` dict |
| `test_nav.py` | `mirage/commands/builtin/nav.py` | `from mirage.commands.builtin.nav import cd, du, find, ls, pwd, stat, tree` |
| `test_read.py` | `mirage/commands/builtin/read.py`, `mirage/backend/local/local.py` | `from mirage.commands.builtin.read import cat, diff, file, head, md5, tail, wc` |
| `test_search.py` | `mirage/commands/builtin/search.py` | `from mirage.commands.builtin.search import cut, grep, jq, sort_lines, tr, uniq` |
| `test_write.py` | `mirage/commands/builtin/write.py` | `from mirage.commands.builtin.write import cp, echo, mkdir, mv, rm, sed, tee, touch` |
| `test_client.py` | `mirage/commands/remote/client.py` | `from mirage.commands.remote.client import CommandClient, CommandClientError, GRPCCommandClient, HTTPCommandClient` |

#### tests/cli/

| Test File | Source File(s) Tested | Key Imports |
|---|---|---|
| `test_client.py` | `mirage/cli/client.py` | `from mirage.cli.client import ClientError, MirageClient` |
| `test_config.py` | `mirage/cli/config.py`, `mirage/backend/local/local.py`, `mirage/backend/local/memory.py` | `from mirage.cli.config import load_workspace_yaml, load_yaml_config, parse_mount_uri, parse_mount_uri_full, parse_mount_uri_with_mode, parse_mounts_from_cli, split_mount_spec` |
| `test_app.py` | `mirage/cli/app.py`, `mirage/cli/commands/workspace.py`, `mirage/fuse/fs.py` | Invokes `mirage.cli.app` as a subprocess; also directly tests `mount()` CLI command with mocking |

#### tests/types/

| Test File | Source File(s) Tested | Key Imports |
|---|---|---|
| `test_types.py` | `mirage/types.py` | `from mirage.types import BackendType, CommandResult, FilePayload, RemoteCommandOptions` |

#### tests/test_types.py (root level)

| Test File | Source File(s) Tested | Key Imports |
|---|---|---|
| `test_types.py` | `mirage/types.py` | `from mirage.types import CommandStats, FileStat, Inode, SyncResult, TransferStats` |

Note: There are two separate files testing `mirage/types.py` — `tests/test_types.py` covers `FileStat`, `Inode`, `SyncResult`, `TransferStats`, `CommandStats`, while `tests/types/test_types.py` covers `CommandResult`, `FilePayload`, `RemoteCommandOptions`, `BackendType`.

#### tests/integration/

| Test File | Source File(s) Tested | Key Imports |
|---|---|---|
| `test_remote_transfer.py` | `mirage/commands/custom/registry.py`, `mirage/commands/remote/client.py`, `mirage/workspace/__init__.py`, `mirage/backend/local/memory.py` | Full round-trip test of file transfer through `CommandRegistry` with a mocked `CommandClient` |

---

### Shared Fixtures (tests/conftest.py)

The root `conftest.py` provides three fixtures shared across all test modules:
- `memory_backend` — a bare `MemoryBackend()` instance (used in `tests/backend/test_memory.py`)
- `write_ws` — `Workspace({"/tmp/": MemoryBackend()}, mode=WRITE, sync_policy=IMMEDIATE)` — used heavily in `tests/commands/`
- `staged_ws` — same but with `sync_policy=STAGED`

The `tests/server/conftest.py` provides:
- `client` — a `TestClient(app)` wrapping the FastAPI app
- `clear_sessions` (autouse) — clears `_sessions` and `_session_logs` before/after each test

---

### Source Files with NO Direct Test Coverage

The following source files are exercised only indirectly (through higher-level tests) and have no dedicated test file:
- `mirage/backend/base.py` — abstract base class
- `mirage/backend/filetype.py` — exercised through `test_read.py` (file type detection)
- `mirage/backend/network/http.py` — no direct test
- `mirage/backend/object_storage/s3.py` / `r2.py` — only config models tested; actual S3/R2 I/O untested
- `mirage/commands/builtin/net.py` — curl/wget handlers; only in COMMANDS dict test
- `mirage/commands/custom/executor.py` — exercised indirectly via registry
- `mirage/commands/remote/auth.py` — no direct test
- `mirage/commands/remote/fastapi/app.py` — no dedicated test
- `mirage/commands/remote/grpc/server.py` — no dedicated test
- `mirage/fuse/backend/local/local.py`, `mirage/fuse/backend/object_storage/s3.py` — no dedicated tests
- `mirage/observe/fastapi/router.py` — tested indirectly via `tests/observe/test_api.py`
- `mirage/runtime/accessors/base.py`, `mirage/runtime/accessors/remote.py` — `remote.py` exercised via `test_remote.py`
- `mirage/server/fastapi/config.py`, `mirage/server/fastapi/daemon.py` — no dedicated tests
- `mirage/server/fastapi/routers/command_router.py`, `workspace_router.py` — tested via `test_app.py`
- `mirage/server/routes.py`, `mirage/server/service/base.py` — no dedicated tests
- `mirage/cli/helpers.py`, `mirage/cli/commands/command.py`, `mirage/cli/commands/observe.py`, `mirage/cli/commands/server.py` — no dedicated tests

agentId: a8316ac8d69187e5b (for resuming to continue this agent's work if needed)
<usage>total_tokens: 113990
tool_uses: 52
duration_ms: 137721</usage>

Prompt:
Thoroughly explore the current test directory structure and source directory structure for this project. I need:

1. Full recursive listing of all files under tests/ (including __init__.py files)
2. Full recursive listing of all .py files under mirage/ (the source)
3. For each test file, identify which source file(s) it tests by looking at its imports

Use glob patterns like "tests/**/*.py" and "mirage/**/*.py" to get complete listings. Then read a few key test files to understand which source files they cover, especially:
- tests/backend/ files
- tests/fuse/ files
- tests/test_workspace.py or similar
- tests/test_types.py or similar
- Any test files that seem to cover multiple source files

Be very thorough - I need the complete picture.