# `CMDC.Backend`
[🔗](https://github.com/tuplehq/cmdc/blob/v0.4.0/lib/cmdc/backend.ex#L1)

CMDC.Backend behaviour — 文件 / 状态 / 远程存储的统一访问层。

对标 deepagents `BackendProtocol` **1:1 对齐**。让工具层（read_file / write_file /
edit_file / ls / glob / grep / execute 等）与底层执行环境完全解耦，支持任意
backend 实现（ETS / 本地文件 / Docker / Modal / E2B / Postgres ...）。

## 设计

- **10 个核心 callback**：
  - 文件操作：`ls/1` `read/3` `write/2` `edit/4` `grep/3` `glob/2`
  - 二进制传输：`upload_files/1` `download_files/1`
  - 沙盒扩展（可选）：`execute/2` `id/0`（由 `CMDC.Sandbox` 子 behaviour 引入）
- **统一 Result struct**：见 `CMDC.Backend.Results`，所有 callback 返回 struct，
  不再用 `{:ok, val} | {:error, reason}` 双形态，便于 pattern match 与
  序列化（Postgres / JSON）。
- **标准错误码 atom**：`:file_not_found / :permission_denied / :is_directory /
  :invalid_path`（4 基础 + 允许 backend 扩展 string）。
- **可路由 (Composite)**：`CMDC.Backend.Composite` 支持按路径前缀路由到不同子
  backend，让一个会话同时挂载 sandbox + PG memory + ETS history。

## 与 `CMDC.Sandbox` 关系

历史 `CMDC.Sandbox` 已经定义了类似但更简单的 8 callback（read_file/write_file/...）。
Phase 12C.0 将 `CMDC.Sandbox` 重构为 `extends CMDC.Backend`，只新增 `execute/2`
与 `id/0`，消除两套接口重叠。

## 与 `CMDC.Memory` 关系

现有 `CMDC.Memory` 是「语义记忆存储」behaviour（store/search/similarity_search），
与本模块**用途完全不同**，命名上独立，不冲突。

## Quick Start

    backend = CMDC.Backend.State.new(:my_table)
    %CMDC.Backend.Results.WriteResult{path: "/hello.txt"} =
      CMDC.Backend.write(backend, "/hello.txt", "world")
    %CMDC.Backend.Results.ReadResult{file_data: %{content: "world"}} =
      CMDC.Backend.read(backend, "/hello.txt")

## 实现指南

实现 backend 时只需 `use CMDC.Backend`（引入默认 `read/3` 等 dispatcher）+
实现 callback。最少要实现 `ls / read / write` 三件套，其他可逐步补齐。

    defmodule MyBackend do
      use CMDC.Backend

      @impl true
      def read(backend, path, opts), do: ...

      # 其他 callback ...
    end

## Backend Factory

支持运行期注入：传递工厂 `{module, opts}` 或 `lambda runtime -> backend.t()`。

# `factory`

```elixir
@type factory() :: t() | (term() -&gt; t())
```

Backend 工厂 — 接受 runtime（context）返回 backend 实例。

# `opts`

```elixir
@type opts() :: keyword()
```

callback 通用选项。

# `path`

```elixir
@type path() :: String.t()
```

路径 — 必须以 `/` 开头的绝对路径。

# `t`

```elixir
@type t() :: struct()
```

Backend 实例 —— 通常是 struct，由 `Backend.State.new/1` 等构造器返回。

# `download_files`

```elixir
@callback download_files(t(), paths :: [path()]) :: [
  CMDC.Backend.Results.FileDownloadResponse.t()
]
```

批量下载二进制文件。

返回与输入等长的 `[%FileDownloadResponse{}]`，支持 partial success。

# `edit`

```elixir
@callback edit(t(), path(), old_string :: String.t(), new_string :: String.t(), opts()) ::
  CMDC.Backend.Results.EditResult.t()
```

精确字符串替换。

## 选项
- `:replace_all` — `true` 替换全部、`false` 要求唯一匹配（默认 `false`）

# `glob`

```elixir
@callback glob(t(), pattern :: String.t(), opts()) :: CMDC.Backend.Results.GlobResult.t()
```

glob 文件匹配。

## 选项
- `:path` — 基础目录（默认 `/`）

# `grep`

```elixir
@callback grep(t(), pattern :: String.t(), opts()) :: CMDC.Backend.Results.GrepResult.t()
```

搜索文件内容（literal substring，不是 regex）。

## 选项
- `:path` — 搜索目录（默认 cwd）
- `:glob` — 文件名过滤

# `ls`

```elixir
@callback ls(t(), path()) :: CMDC.Backend.Results.LsResult.t()
```

列出指定目录下的直接子项（非递归）。

返回 `%LsResult{entries: [%FileInfo{}]}`，目录条目以 `/` 结尾、
`:is_dir` 为 `true`。

# `read`

```elixir
@callback read(t(), path(), opts()) :: CMDC.Backend.Results.ReadResult.t()
```

读取文件内容，支持按行分页。

## 选项
- `:offset` — 起始行（0-indexed，默认 0）
- `:limit` — 最大行数（默认 2000）

# `upload_files`

```elixir
@callback upload_files(t(), files :: [{path(), binary()}]) :: [
  CMDC.Backend.Results.FileUploadResponse.t()
]
```

批量上传二进制文件。

返回与输入等长的 `[%FileUploadResponse{}]`，支持 partial success。

# `write`

```elixir
@callback write(t(), path(), content :: String.t()) ::
  CMDC.Backend.Results.WriteResult.t()
```

创建新文件。文件已存在时返回 `:invalid_path` 错误（用 `edit/4` 修改既有文件）。

# `download_files`

```elixir
@spec download_files(t(), [path()]) :: [CMDC.Backend.Results.FileDownloadResponse.t()]
```

委派到 backend.download_files/2。

# `edit`

```elixir
@spec edit(t(), path(), String.t(), String.t(), opts()) ::
  CMDC.Backend.Results.EditResult.t()
```

委派到 backend.edit/4，opts 可选。

# `glob`

```elixir
@spec glob(t(), String.t(), opts()) :: CMDC.Backend.Results.GlobResult.t()
```

委派到 backend.glob/3，opts 可选（含 :path）。

# `grep`

```elixir
@spec grep(t(), String.t(), opts()) :: CMDC.Backend.Results.GrepResult.t()
```

委派到 backend.grep/3，opts 可选。

# `ls`

```elixir
@spec ls(t(), path()) :: CMDC.Backend.Results.LsResult.t()
```

委派到 backend.ls/1。

# `read`

```elixir
@spec read(t(), path(), opts()) :: CMDC.Backend.Results.ReadResult.t()
```

委派到 backend.read/3，opts 可选。

# `resolve`

```elixir
@spec resolve(factory(), term()) :: t()
```

解析 backend factory — 接受 struct 或函数。

- struct → 直接返回
- 1-arity function → 用 `runtime` 调用并返回结果

# `upload_files`

```elixir
@spec upload_files(t(), [{path(), binary()}]) :: [
  CMDC.Backend.Results.FileUploadResponse.t()
]
```

委派到 backend.upload_files/2。

# `write`

```elixir
@spec write(t(), path(), String.t()) :: CMDC.Backend.Results.WriteResult.t()
```

委派到 backend.write/2。

---

*Consult [api-reference.md](api-reference.md) for complete listing*
