# Cell 4 工具链架构

状态：设计记录（2026-11-07 起草；4.0.0 已按本文实现，未实现的条目标注"未实现"）。不是用户文档，用法见 [CLI 参考](../../dev-packages/cli/README.md)。**第一原则是 Coding Agent 优先，见 [agent-first.md](./agent-first.md)，本文与之冲突时以它为准。** 4.0 是一次全新设计，不兼容 3.x 的 CLI、hook、runtime、配置表达式和产物目录，也不提供迁移报错或兼容层。扩展模型见 [modules.md](./modules.md)。

## 1. 保留什么，去掉什么

**保留（Cell 的精华）**
1. **npm 包即组件**：装上依赖就获得能力（运行时 DI 模块、默认配置、工具链扩展），不需要逐个注册。
2. **分层配置**：组件默认值 → 项目配置 → mode 覆盖；配置按 `frontend` / `backend` 分区。
3. **DI 模块自动装配**：构建时根据组件图生成装配代码，static 模块同步加载、普通模块按依赖顺序异步加载。
4. **Serverless First**：同一份代码，一条命令构建并部署到多家云（包括阿里云 FC、腾讯云 SCF 这类国际框架不覆盖的平台）。
5. **前后端一体**：一个项目同时产出前端和后端。

**去掉（问题来源）**

| 去掉 | 原因 | 换成 |
|---|---|---|
| 三层进程、`NODE_PATH` 注入、`CELL_RFS` 环境变量传参 | 难排错、启动慢 | 单进程，标准模块解析 |
| 自定义 EL（过滤器、`onTarget`、mode 递归引出 mode） | 自造语言，难读难测 | 逻辑写 TS；YAML 只保留 `${env.X}` 插值 |
| 10 类约定文件 hook × before/on/after × shell 命令 | 扩展点重叠、顺序难推断 | 工厂函数式模块 + 固定组合规则的事件（见 modules.md） |
| webpack、webpack-chain、webpack-dev-server、clear-module 热重载 | 慢，扩展和工具绑死 | Vite 8 + Environment API |
| 外部依赖二次 `npm install`（pack-external-module） | 慢、不确定 | 按 package.json 依赖关系拷贝需要的包（自写，不依赖 nft） |
| 构建期安装 runtime | 构建需要联网，版本不受锁文件管理 | 构建零安装；部署器内置在 CLI，部署时也不安装 |
| `cell serve / props / config / update / runtime` 等命令 | 语义重叠 | 见 §8 |
| 平台子模式用 mode 文件表达（`cell-http-mode.yml` 等） | 配置散落 | preset 选项 |

## 2. 参考的业界方案

| 来源 | 借鉴 | 不照搬的部分 |
|---|---|---|
| Nitro | preset 决定产物形态；产物目录 `.output/server` + `.output/public`；CI 环境自动识别平台 | 见 §9 决策 1 |
| Nuxt（module、kit） | TS 配置加载；运行时配置可被环境变量覆盖；模块选项按 key 读取 | 不做 layers；模块写法改用 pi 式工厂函数 |
| Astro（integration、adapter） | 构建扩展只交出 Vite 插件；adapter 与部署分离 | 不用"一个大 hooks 对象"写法 |
| Vite | Environment API 区分 client / server；ModuleRunner 做后端热重载；插件生态直接复用 | — |
| pi（coding agent 的扩展与 package 机制） | 工厂函数式模块、每个事件固定组合规则、宿主包 peer + 导入映射、重载语义、按包名去重的来源与过滤 | 全局扩展目录、项目信任、出错后继续运行（见 modules.md §8） |
| Bun / Node SEA / Deno compile | 单可执行文件 | — |

## 3. 包结构

```
@celljs/kit     模块作者用：CellAPI 类型、defineCellModule / definePreset / defineDeployer（无重依赖，作为宿主包由 CLI 提供）
@celljs/vite    Vite 插件 cell()：组件图 → 虚拟模块、client/server 两个环境、开发期后端热重载
@celljs/cli     bin: cell。配置加载、模块宿主、preset 注册表、产物打包（单文件/可执行文件）、部署、命令
（原计划的 @celljs/deploy-* 部署器包不再需要：部署器内置在 @celljs/cli，直接调用云 API，见 §7 P3）
```

- Cloudflare 部署器内置在 `@celljs/cli`（2026-11-08）：它只调用 Wrangler（Cloudflare 官方 CLI，负责打包、上传和凭据），自身约 150 行，没有云 SDK。Wrangler 不是 Cell 的依赖：项目装了就用项目的，否则 `npx --yes wrangler@4` 在第一次使用时安装（npm 缓存），这就是该平台的"按需安装"。

- 删除：`cli-common`、`cli-runtime`、`cli-service`、`dev-server`、`frameworks`、`cloud-plugin`、`code-loader-plugin`、`node-plugin`、`static-plugin`、`fc/fc3/scf/lambda-plugin`、`testing`（并入 `@celljs/kit/testing`）。
- 进度（2026-11-08）：上面的旧包、`plugins/*`、CLI 模板、`koa-app` 示例和各组件的 `src/hooks`（webpack / deploy hook）已在 `next` 上提前删除，webpack 及 loader 依赖随之移除；其余示例改用 `cell build` / `cell preview`，`mode` 改为命令行 `-m`。P5 只剩重写模板与文档、发布。
- 平台运行时适配器（`fc-adapter`、`scf-adapter`、`lambda-adapter`、`vercel-adapter`、`faas-adapter`）保留为运行时包，它们是进入产物的代码。
- **最少第三方依赖**（2026-11-07 确认）：工具链只依赖 `vite` 和 `js-yaml`（运行时包已在用）；表达式求值复用项目中 `@celljs/core` 的 jexl。命令行参数用 `node:util` 的 `parseArgs`，`cell.config.ts` 用 Vite 自带的配置加载能力，事件分发、外部依赖拷贝、日志输出都用 Node 内置模块自写。新增任何第三方依赖都需要在本文记录理由。前端 Node polyfill 不由工具链提供，项目安装了才启用。
- 预估：工具链从约 7450 行（不含云插件）+ 云插件约 4500 行，降到约 3000 行 + 每个部署器约 400–800 行。

## 4. 配置

两类配置，职责不重叠：

| 文件 | 内容 | 何时求值 |
|---|---|---|
| `cell.config.ts`（可选） | 工具链：`preset`、`targets`、`modules`、`build`、`vite`、`hooks` | 构建时 |
| `cell.yml` + `cell.<mode>.yml` | 应用配置（`@Value` 读取的内容），按 `frontend` / `backend` 分区；组件也带 `cell.yml` 提供默认值 | 合并在构建时，插值在运行时 |

```ts
// cell.config.ts
import { defineConfig } from '@celljs/cli';
export default defineConfig({
  preset: 'fc',                       // 也可 --preset / CELL_PRESET / CI 自动识别
  fc: { trigger: 'http' },            // preset 选项
  modules: ['./modules/audit'],       // 额外的本地模块；依赖里的组件自动发现
  build: { format: 'esm', single: false, compile: false, external: [], sourcemap: false },
  vite: {},                           // 合并进 Vite 配置
  hooks: { 'build:before': 'prisma generate' },   // 项目级事件订阅，只接受命令；函数写本地模块
});
```

```yaml
# cell.yml
backend:
  server:
    port: "${env.PORT ?: 9000}"   # 含 `?:` 的值必须加引号
  typeorm:
    ormConfig:
      - type: mysql
        host: "${env.DB_HOST ?: 'localhost'}"
```

规则：
- 合并顺序：组件 `cell.yml`（按依赖拓扑序）→ 项目 `cell.yml` → 各 mode 文件（按 `-m` 顺序）→ module 的 `updateConfig`。数组整体替换，对象深合并。
- 插值 `${...}` 由核心包已有的 jexl 引擎求值，上下文是 `{ ...配置, env }`，支持 `${env.NAME ?: 默认值}` 和引用其他配置项（`${cell.server.path}`）。后端**在运行时求值**：同一个产物可以部署到多个环境，密钥不会写进产物；前端在构建时求值。不再提供 `onTarget`、`cliContext`、`currentMode` 这类只在构建时存在的上下文：目标差异写在 `frontend` / `backend` 分区，开发/生产差异写在 `cell.development.yml` / `cell.production.yml`（`cell dev` / `cell build` 自动加上对应 mode）。
- 任意已有配置项可被环境变量覆盖：`CELL_BACKEND__SERVER__PORT=8080` → 后端 `cell.server.port`。前缀定目标（`CELL_BACKEND__` 服务端启动时应用，`CELL_FRONTEND__` 构建时应用），`__` 分隔层级，第一段先在顶层找、找不到再到 `cell` 下找（`CELL__…` 显式指 `cell`；同名时顶层优先，`cell info` 提示）；同一个键或其上下级只能由一个变量设置，冲突的变量都不生效并报告，与顺序无关；不区分大小写并忽略 `_` / `-`；值按原值类型转换（JSON 写法的有限数且整数不超过 2^53、`true` / `false`、对象和数组用 JSON、原值 `null` 时 JSON 或文本），整体是一个 `${...}` 的值直接保留、由求值结果定类型；在 `${...}` 求值之前生效；只能覆盖已存在的键，找不到键或类型不符时不生效并报告（服务端 stderr、`cell build` 警告、`cell info`）。实现是 `@celljs/core` 的 `EnvConfigUtil`，服务端和构建期用同一份代码。
- mode 只由命令行 `-m` 和 preset 添加，不再由配置引出 mode；stage 就是普通 mode（`test` / `pre` / `prod`）。
- `cell.config.ts` 由 Vite 的配置加载能力（`loadConfigFromFile`）加载，支持 `.ts/.mts/.cts/.js/.mjs/.cjs`，同时存在多个时报 `CELL_CONFIG_AMBIGUOUS`。它 import 的本地文件记为依赖，`cell dev` 据此决定是否重载。
- `cell.config.ts` 只有一种写法：默认导出 `defineConfig({...})` 对象。顶层除 `preset / modules / build / vite / hooks` 外的字段都是工具链模块的选项，值必须是对象；模块通过扩充 `@celljs/kit` 的 `CellModuleOptions` 声明选项类型，所以写错字段在编辑器里就是类型错误。
- 加载后逐项校验，问题统一报 `CELL_CONFIG_INVALID`，带字段路径、所在行列和修复建议；疑似拼写错误（`bulid`、`build:befor`）给出"你是不是要写"。加载或执行失败报 `CELL_CONFIG_LOAD`。
- 优先级：命令行参数 → `cell.config.ts` → 环境变量（`CELL_PRESET`）→ CI 识别（只在 `CI` 为真时由 preset 的 `detect` 判断）→ 默认值。`cell info` 显示 preset 来自哪一层。
- `modules` 归一为四种：本地路径（必须存在）、包名（加载非运行时依赖包的工具链模块）、`'!包名'`（整个组件连同只经由它到达的依赖都不参与）、`{ source, runtime: false }`（去掉 DI 模块，保留配置）。同一个包只能出现一次；过滤了不存在的组件报 `CELL_COMPONENT_NOT_FOUND`。
- `build.compile: 'node'` 时格式固定为 `cjs`，显式写 `esm` 报 `CELL_CONFIG_CONFLICT`。
- `targets` 默认是 `backend` 加上组件声明的目标（package.json 的 `cell.targets`）；`cell.yml` 不再出现 `targets`、`entry` 这类工具链字段。后端入口由 preset 按名字从组件的 `cell.entries` 中选取（见 modules.md §2）。
- `hooks` 只接受命令字符串，只能订阅通知和闸门事件，执行规则见 modules.md §3.2。

## 5. 构建

`@celljs/vite` 的 `cell()` 插件，可在 `cell build` 之外直接用于 `vite build`：

- **两个环境**：`client` 输出 `.cell/output/public`；`server` 输出 `.cell/output/server/index.{mjs,cjs}`（`ssr.noExternal: true`，依赖内联）。
- **虚拟模块**：`virtual:cell/container`（DI 装配，static 模块静态 import、普通模块 `import()`）、`virtual:cell/config`（合并后的配置，插值保持为运行时表达式）、`virtual:cell/entry`（preset 选定的入口）。
- **框架包**按 `exports` 的 `import` 条件解析到 `esm/`，产物里只有一份框架。
- **装饰器元数据**：用户源码由 Oxc 转译（`decorator.legacy` + `emitDecoratorMetadata`）。构建时检查"装饰器引用了只有类型的导入"并报错指出位置；`build.transpiler: 'tsc'` 作为兜底。
- **前端 Node polyfill**：alias + define，只在用到时注入。
- **可选依赖**（typeorm 驱动、cache-manager 各存储）：组件在 `package.json` 的 `cell.optionalDependencies` 声明，解析不到时标记 external，不再靠"过滤警告"。

## 6. 开发模式（cell dev）

- 一个端口：Vite dev server 服务前端（HMR）。
- 后端：`server` 环境是 `RunnableDevEnvironment`，用 `runner.import(entry)` 加载入口，拿到 Express app / handler 后挂成 Vite 中间件；文件变化时 ModuleRunner 让对应模块失效，重新 import 并替换中间件。ESM 和 CJS 源码都适用。
- 开发期 node_modules 不打包，启动快。
- `cell.yml`、`cell.config.ts`、组件 package.json 变化时调用 `server.restart()`。
- 事件函数（FC/SCF/Lambda 的 event、timer 触发）在开发期由 preset 提供的 `dev` 处理：把 HTTP 请求转换成事件调用 handler。

## 7. 产物与单文件

Preset 决定产物形态（借鉴 Nitro）：

```ts
definePreset({
  name: 'fc',
  extends: 'node',
  entry: '{trigger}',                             // 入口名，由选项决定：http | event | timer | api-gateway，从组件 entries 中选
  format: 'cjs',
  node: '20',
  options: { trigger: 'http' },
  output: (out) => ({ ...out, files: { bootstrap: '#!/bin/bash\nnode index.cjs' } }),
  dev: { event: true },
  deployer: 'fc',                                 // 部署器内置在 @celljs/cli
});
```

内置 preset：`node`（默认）、`cli`、`cloudflare`、`fc`、`scf`、`lambda`、`vercel`；`bun`、`deno`、`static`、`docker` 未实现。选择顺序：`--preset` → `cell.config.ts` → `CELL_PRESET` → CI 环境自动识别 → `node`。

单文件三级：

| 级别 | 命令 | 产物 |
|---|---|---|
| L1 | `cell build` | `server/index.mjs` 一个文件，依赖内联、不分 chunk；原生模块和 `build.external` 按依赖关系拷到 `server/node_modules` |
| L2 | `cell build --single` | 前端资源内嵌进 `server/index.mjs`（`virtual:cell/assets`），静态资源服务从内存读取，一个文件跑全栈 |
| L3 | `cell build --compile [bun\|node\|deno]` | `.cell/output/app` 单个可执行文件；默认 bun，支持 `--target` 交叉编译；Node SEA 自动切到 CJS；有无法内嵌的原生模块时报错列出 |

`.cell/output/manifest.json` 记录 preset、格式、入口、文件清单（preset 写出的文件在 `files`），供 `cell preview` 和部署器使用。

**`cloudflare` preset**（2026-11-08）：Cell 后端（Express）运行在 Workers 的 `node:http` 服务端上（`nodejs_compat` + `cloudflare:node` 的 `httpServerHandler`），入口仍是 `@celljs/express-adapter` 的 `http`，运行时包不需要新的适配器。
- 产物：`server/index.mjs`（`runtime: 'workerd'`：workerd 没有 `import.meta.url` / `dirname` / `filename`，构建时替换为 `/bundle/server/index.mjs` 下的固定路径）、`server/worker.mjs`（生成的 Worker 模块）、`wrangler.json`。前端 `public/` 作为 Workers 静态资源，由 Cloudflare 直接返回，只有不匹配静态文件的请求进入 Worker。只支持 ESM。
- 启动：Workers 只允许在模块加载阶段生成代码（Express 依赖的 depd 用 `new Function`），只允许在请求处理中做 I/O。`worker.mjs` 在启动时 `import('./index.mjs')` 并等到 DI 容器加载完，`Application.start()` 留到第一个请求（`@celljs/core` 的 `ApplicationHost.beforeStart`）。模块加载失败时 Worker 启动失败，`wrangler dev` / `wrangler deploy` 直接报出。
- 配置：Cloudflare 的设置留在项目的 `wrangler.jsonc` / `wrangler.json`（Cloudflare 文档的写法，agent 熟悉），Cell 在其上设置 `main`、有前端时的 `assets.directory`、`nodejs_compat`；没有时 `name` 来自包名。`compatibility_date` 默认且最早为 `2026-03-17`（`node:tty` 从这天起默认可用，Express 的 `debug` 用到它），固定值保证同样的输入得到同样的产物。不在 `cell.config.ts` 里加 `cloudflare` 字段（R1：一种写法）。`wrangler.toml` 需要 TOML 解析器，不支持。
- 预览与部署：`cell preview` 运行 `wrangler dev`（本机 workerd）；部署器 `plan` = `wrangler deploy --dry-run`（打包并检查，不上传），`apply` = `wrangler deploy`。
- 已验证（wrangler 4.158 / workerd）：backend-app 的增删改查与 404、sample-app 的 React 页面与 RPC（`POST /api`）、`wrangler.json` 的 `vars` 进入 `${env.X}`；`cell deploy --dry-run` 输出计划；无凭据时 `CELL_DEPLOY_FAILED` 给出设置 `CLOUDFLARE_API_TOKEN` 的提示。

## 8. 命令行

| 命令 | 作用 |
|---|---|
| `cell init [template]` | 从模板创建项目（模板由 module 提供） |
| `cell dev` | 开发 |
| `cell build` | 构建（`--preset --single --compile --format -m`） |
| `cell preview` | 按 manifest 本地运行产物 |
| `cell deploy` | 构建（可 `--skip-build`）并部署，`--dry-run` 只生成部署包和参数（已实现，部署器：`cloudflare`、`fc`、`scf`、`lambda`、`vercel`） |
| `cell login / logout <provider>` | 管理云账号凭据（未实现；凭据目前只读环境变量） |
| `cell info` | 组件图、生效配置、已注册的模块能力、部署状态（`--json`） |
| `cell check` | 类型、配置 schema、装饰器元数据、DI 绑定检查 |
| `cell config get <key> --why` | 最终值及其来源文件与层级（已实现） |
| `cell docs [topic]` / `cell explain <code>` | 版本匹配的组件文档 / 错误码说明（`cell docs` 已实现；`cell explain` 未实现） |
| `cell mcp` | stdio MCP 服务器（见 agent-first.md R8） |
| `cell agent sync` | 按已安装组件生成 `AGENTS.md` 与技能目录 |
| `cell add <module>` | 安装组件并写入依赖（未实现） |

**按需安装**（未实现，且已被 §12 P3 的决定取代：内置部署器不依赖云 SDK，不需要按需安装）：部署器包含各云 SDK，体积大，不放进项目依赖。首次 `cell deploy` 时，若项目里没有对应部署器，自动安装到 `~/.cell/cache/<cli 主版本>/`（带锁文件），之后复用；项目里显式安装的版本优先。构建永远不触发安装。

## 9. 已定决策

1. **服务端构建不基于 Nitro，自研 preset 层，模型照搬 Nitro**（2026-11-07 确认）。原因：Nitro 要求 Web `fetch` 入口，Cell 运行时基于 Express；Nitro 没有 FC / SCF preset；Nitro 自带的 h3、unstorage 与 Cell 的 DI 和配置重叠。Cell 运行时改为 Fetch-native 后再评估。
2. `cell.yml` 只写应用配置，不允许放进 `cell.config.ts`（agent-first R1）。
3. 4.0 原定在 `next` 分支开发、P5 后合入 `main`；2026-11-08 按用户要求提前合入，此后在 `main` 上开发，3.x 的最后状态见标签 `v3.9.0`。

## 10. 实施阶段

| 阶段 | 内容 | 验收 |
|---|---|---|
| P1 | `@celljs/kit`、`@celljs/vite`（项目模型、结构化错误、虚拟模块并落盘、两个环境、L1） | backend-app、sample-app 与 3.9 对比：DI 绑定、HTTP 增删改查、前端 DOM 一致 |
| P2 | `@celljs/cli`：配置、模块宿主、preset、`dev / build / preview / info / check / mcp`、`--json`、dev MCP 端点、agent 评测首批任务 | 全部示例构建运行；后端改文件热重载、前端 HMR |
| P3 | 组件迁成 module；fc / scf / lambda / vercel preset 与部署器 | 各平台 `cell deploy --dry-run` 的代码包可在本地模拟器运行；真实部署各跑一次 |
| P4 | L2、L3 | `--single` 拷一个文件到空目录能跑全栈；`--compile` 的可执行文件在无 node_modules 的目录运行 |
| P5 | 删除旧包和 webpack 依赖，重写模板与文档，发布 4.0.0 | 干净安装下 build / test / test:esm / lint 全过 |

## 11. 风险

| 风险 | 对策 |
|---|---|
| Oxc 装饰器元数据与 tsc 不一致 | 构建期检查报错 + `transpiler: 'tsc'` 兜底 |
| 依赖内联后动态 `require` / `__dirname` 失效 | ESM 产物注入 createRequire 垫片；`build.external` + 按依赖关系拷贝 |
| 云平台对 ESM 支持不一 | preset 指定默认格式 |
| 原生模块无法进单可执行文件 | 构建时检测并报错 |
| 部署器重写引入回归 | 每个平台本地模拟器 + 一次真实部署 |
| 项目与缓存里存在同一部署器的不同版本 | 项目优先，`cell info` 显示来源 |
| 框架包 ESM 构建里的 `require` 垫片让打包器看不到依赖 | 打包时去掉垫片（`CellPlugin.withoutRequireShim`），测试在仓库外运行产物 |
| 内联的库在运行时按自身路径读文件（prisma 的 schema 与引擎） | `output:done` hook 拷到 `server/`，P3 起由组件声明随产物拷贝的文件 |

## 12. 进度

**P1 已完成（2026-11-07，`next` 分支）**：`@celljs/kit`（`CellError`、项目模型类型）与 `@celljs/vite`（`ProjectLoader`、`ExpressionEvaluator`、`ContainerGenerator` / `ConfigGenerator` / `HtmlGenerator`、`OutputWriter`、`CellPlugin` / `cell()`、`CellBuilder`）。验证结果：
- 11 个示例：DI 模块列表与 3.9 一致；后端配置在运行时求值后与 3.9 构建期结果一致；前端只差旧版 `onTarget` 留下的空对象。
- 8 个可加载的示例：ESM、CJS 两种产物的 DI 绑定解析结果与 3.9 完全一致；backend-app 的 HTTP 增删改查与 404 在 3.9、ESM、CJS、Bun 上一致。
- sample-app 前端渲染的 body 与 3.9 一致；前后端构建约 0.8s（3.9 约 5s）。
- 新增 40 个单元/集成测试（fixture 项目真实构建并启动 HTTP 服务、验证运行时插值）。

实现中确定的细节：
- 后端 `${...}` 留到运行时由 `@celljs/core` 求值。后端配置带 `paths.public`（默认 `public`，相对输出目录，项目模型里也有，所以 `cell info` 和环境变量覆盖能看到它），服务端启动时在应用 `CELL_BACKEND__*` 之后把它解析成绝对路径。含 `?:` 的 YAML 值必须加引号。
- 前端 HTML：项目的 `index.html` 或内置模板，`${...}` 用前端配置求值，自动追加入口脚本。
- ESM 产物里内联的 CommonJS 依赖使用的 `__dirname` / `__filename` 指向产物文件（`import.meta.dirname`），与 CJS 产物一致。
- 构建失败统一为 `CELL_BUILD_FAILED`，带出错文件与位置。

**P2 已完成**：
- 配置系统已完成（2026-11-07）：`@celljs/cli` 原地重写为 ESM 包（旧 3.x 源码已删除，`templates/` 留到 P5 重写），提供 `defineConfig`、`ConfigFileLoader`、`ConfigValidator`、`ConfigResolver`（`toBuilderOptions` 接到 `CellBuilder`）；`@celljs/kit` 增加 `CELL_EVENTS` 与 `CellModuleOptions`；`ProjectLoader` 支持 `disabled` / `runtimeDisabled`，`CellBuilder` 支持 `vite` 合并。集成测试从 `cell.config.ts` 一路构建到运行中的服务。
- 审查后确定（2026-11-07）：去掉 `vite:config` 事件；`hooks` 只接受命令；工具链扩展统一叫 toolchain 模块（`cell.toolchain` 字段、过滤键 `toolchain`）；`targets` 移出 cell.yml，组件在 package.json 声明、项目在 cell.config.ts 覆盖；入口改为组件的具名 `entries` + preset 选名字；preset 在 `modules:done` 之后确定；宿主包导入映射推迟到 P3。
- 命令实现顺序：① CLI 骨架 + `node` preset + `cell build` + hooks；② `cell info`；③ `cell preview`；④ 模块宿主完整版；⑤ `cell dev`；⑥ `cell check`；⑦ `cell mcp` 与 dev MCP 端点；⑧ agent 评测。
- 剩余顺序已确认（2026-11-08）：② `cell info` → ②' 通用环境变量覆盖 → ④ 模块宿主 → ⑤ `cell dev` → ⑤' `createTestApp()` → ⑥ `cell check` → ⑦ `cell mcp` → ⑧ agent 评测。环境变量覆盖属于配置层，跟在 `info` 后面，`info` 能显示每个值的来源；`createTestApp()`（agent-first R11，替代已删除的 `@celljs/testing`）与 `dev` 共用进程内启动后端的逻辑，排在 `dev` 之后、`check` 之前。
- 顺序调整（2026-11-08 用户确认）：`cell dev` 提前到模块宿主之前，尽快补上 3.x `cell serve` 的缺口。现在的顺序：⑤ `cell dev` → ④ 模块宿主 → ⑤' `createTestApp()` → ⑥ `cell check` → ⑦ `cell mcp` → ⑧ agent 评测。代价：`dev` 先只支持 `cell.config.ts` 的 `vite` 字段和项目 `hooks`（`dev:*` 事件），模块提供的 Vite 插件、`dev:*` 事件处理函数和 dev 中的模块重载等模块宿主完成时再接入 `dev`。
- 发版（2026-11-08 确认）：4.0 不发预发布版，P5 完成后直接发 4.0.0。`main` 上已有 4.0 的 major changeset，P5 之前不要执行 `release:version` / `release:publish`。
- ①③ 已完成（2026-11-08）：`cell build` / `cell preview`、`--json`、退出码、`node` preset、事件总线、`hooks`（环境变量 `CELL_EVENT` / `CELL_PRESET` / `CELL_MODE` / `CELL_OUT_DIR`，PATH 带 `node_modules/.bin`）。旧工具链已删除，全部示例改用新 CLI。
- 迁移验证（2026-11-08）：干净 worktree 下 install / lint / build / compile / test / test:esm / test:exports / test:refs 全过；40 个公开包用 `bun pm pack` 打包后，在仓库外用 npm 安装，backend-app 与 sample-app（React）`cell build` + `cell preview` 正常；9 个示例的产物拷到仓库外空目录都能运行（prisma 只差真实数据库）。验证中修复：ESM 产物缺 `require` / `require.main`、框架包 `require` 垫片导致依赖没有内联、`CELL_BUILD_FAILED` 丢失 `Caused by` 中的真实原因。迁移说明见 [../migration-4.md](../migration-4.md)。
- ② `cell info` 已完成（2026-11-08）：`ProjectInspector` 由解析后的工具链配置和项目模型生成结果，只读 package.json / `cell*.yml` / `manifest.json`，不执行 hooks（`Toolchain` 的 `runHooks: false`），示例项目约 0.15s。应用配置只在 `--app-config` 时输出，避免默认结果过大（当时最大的示例约 7KB）。组件信息增加 `targets` / `entries` / `toolchain`。`warnings` 预先提示缺少入口、无效 mode（带拼写建议）、`single` / `compile` 不可用。模块能力（模块宿主完成后）和部署状态（P3）在对应阶段加入。
- ②' 通用环境变量覆盖已完成（2026-11-08）：规则见 §4。后端在生成的 `dynamic-config` 模块里调用 `EnvConfigUtil.applyProcessEnv(config)`，早于任何读取原始配置的代码（`ConfigUtil.getRaw()` 在模块加载时就会被读）；前端由 `ProjectLoader` 在求值前应用。`TargetModel.envOverrides` 记录变量到路径的对应（不含值），后端的那份表示同一环境在服务端启动时会改什么，`cell info` 据此展示并警告。express-adapter 不再读 `SERVER__PORT` / `SERVER__HOST`，改读 `cell.server.port` / `cell.server.host`（`@celljs/web` 的 `cell.yml` 新增 `host: 127.0.0.1`），`cell preview --port` 改设 `CELL_BACKEND__SERVER__PORT`。只允许覆盖已存在的键，是为了让拼错的变量名被发现，而不是静默无效。
- ⑤ `cell dev` 已完成（2026-11-08，提前到模块宿主之前）：`CellDevServer`（`@celljs/vite`）+ `DevCommand`。后端入口改为可交给宿主：`@celljs/core` 新增 `ApplicationHost`（`Symbol.for` 全局，跨模块实例），`application-entry` 和 express-adapter 的 `http` 入口在有宿主时交出请求处理函数和 `stop`，不监听端口；`createTestApp()` 将复用它。server 环境关闭 runner 自带的 HMR（它会在每次变化时重跑入口），由 `CellDevServer` 统一重载：停旧应用 → 清空 runner 缓存 → 重新 import。开发期 `@celljs/*` 和组件经过插件（共享一份 core、能替换 dynamic-container / dynamic-config），其余依赖由 Node 加载；前端从依赖预构建中排除这些包。源码里的 `require()` 由插件补模块内 `require`，项目内的 CommonJS 文件（prisma client）由 Node 加载后重新导出。生成的 DI 容器在模块加载失败时不再吞掉错误（原来打印后返回 `undefined`），由入口退出进程或报告给宿主。9 个示例在 `cell dev` 下启动正常（prisma 只差真实数据库），sample-app 用无头 Chrome 验证页面渲染（含 RPC 结果）。
- ④ 模块宿主已完成（2026-11-08）：`ModuleHost`（`@celljs/cli`）+ `CellAPI` / `defineCellModule`（`@celljs/kit`）。启动顺序改为：读 `cell.config.ts` → 收集组件 → 执行工厂（组件 → `modules`）→ 解析 preset（模块注册的 preset 参与 `--preset` 与 CI 识别）→ 校验模块选项（`CELL_CONFIG_UNKNOWN_OPTION`、Standard Schema）→ 注册 hooks → `modules:done` → `config:resolved`。本地 TypeScript 模块用 Vite `runnerImport` 加载。模块注册的 Vite 插件、虚拟模块、DI 模块、入口进入 `cell build` / `cell dev` / `cell info`；`addDevHandler` / `addWatchFile` 进入 `cell dev`；`addOutputFile` 和 `build:done` 的 `event.files` 在构建后写入输出目录；`cell dev` 中通知类处理函数出错只报告。`updateConfig` 只改工具链配置（`build.external`、`vite`）。
- ⑤' `createTestApp()` 已完成（2026-11-08）：`TestApp`（`@celljs/vite`）复用 `CellDevServer` 的不监听模式（Vite middlewareMode、无 HMR、无文件监听），`@celljs/cli` 的 `createTestApp()` 先建 Toolchain（mode `test`、模块、preset 入口）。请求不经 socket：Bun 的 `node:http` 不接受内存连接，也不能直接构造 `ServerResponse`，所以自写内存版 req/res（`memory-http.ts`），关键方法放在实例上，因为 Express 会替换 req/res 的原型。backend-app、jwt-app 的测试改用它（由 build + preview 的约 3s 降到约 0.6s）。
- ⑥ `cell check` 已完成（2026-11-08）：`ProjectChecker` 依次运行 `config`（复用 `cell info` 的结构化警告）、`types`（项目自己的 `tsc --noEmit`，诊断解析成带位置的 issue）、`decorators`（tsconfig 设置）、`di`（`CellDevServer` 的 `backend: false` 模式只加载容器、设置 `ContainerProvider`，用 `getAllAsync` 逐个解析绑定，把 inversify 错误映射成 `CELL_DI_*`）和模块注册的检查。示例项目全量检查约 1–3 秒。DI 错误目前只在 `cell check` 中改写；运行时（core）的 DI 报错改写留到后续。
- ⑦ `cell mcp` 与 dev MCP 端点已完成（2026-11-08）：MCP 协议层自己实现（JSON-RPC 2.0，只有 tools），不依赖 MCP SDK。`cell mcp` 的内置工具在进程内执行同名命令并返回它的 `--json` 结果，保证两种接口一致；为此新增 `cell config get <key> [--why]`（按真实合并顺序列出来源：所有文件的公共部分在前、目标段在后）和 `cell docs <query>`（检索组件 README/`docs/` 和模块 `addDocs()`）。`deploy_plan` 执行 `cell deploy --dry-run`，`deploy_apply` 不提供。dev 端点在 `cell dev` 的 `/_cell/mcp`（Streamable HTTP、JSON 响应，只接受本机来源），路由和配置从运行中容器按 symbol 描述（`RouteProvider`、`ExpressionHandler`）查找，因为 module runner 里的模块实例与 CLI 不是同一份；请求、启动错误和控制台输出由 `DevActivity` 保存，跨重启保留。
- ⑧ agent 评测首批任务已完成（2026-11-08）：`evals/`（说明见 evals/README.md），`bun run eval:check` 自检每个任务（起始项目不通过、参考答案通过），`bun run eval -- --agent claude|codebuddy|<命令>` 真实运行并把成功率、轮次、token、费用写到 `evals/results/`。工作目录放在仓库外的临时目录并链接仓库的 `node_modules`，agent 读不到仓库说明和验收测试。首批 5 个任务：REST 接口与校验、typeorm CRUD、只给报错修 DI 绑定、改为部署到 Cloudflare 并给出计划、本地模块上报产物清单；设计里的前端 RPC 任务待有可靠的前端验收方式后补。编写任务时发现并修复：`cell check` 把同一个缺失绑定按依赖它的每个绑定各报一次（一个缺失报 13 条），现在按原因合并成一条并注明受影响的数量；better-sqlite3 是原生模块，不能在 Bun（`bun test`）里加载，typeorm 任务改用 sql.js。
- P4 单文件与可执行文件已完成（2026-11-08）：`--single` 时 `CellPlugin` 先构建前端，服务端入口换成虚拟模块 `virtual:cell/single-entry`（先 import `virtual:cell/assets` 注册内嵌文件，再 import 后端入口；ES 模块按顺序执行 import，保证应用启动前文件已注册），构建后删除 `public/`；`@celljs/serve-static` 发现 `Symbol.for('@celljs/serve-static/assets')` 时从内存返回。`--compile` 隐含 `--single`，由 `ExecutableCompiler`（@celljs/cli）调用 `bun build --compile` / `deno compile` / Node SEA（postject 通过 npx 按需获取，不加依赖）。验证：sample-app 的单文件拷到空目录用 node 运行、bun 与 node 编译的可执行文件拷到空目录运行，页面、脚本、favicon、API 正常。顺带修复：Bun 的 `require.main` 是只读访问器，服务端 bundle 里的 `__require.main ??= …` 在 Bun 下抛错（L1 产物用 bun 运行也受影响），改为 `Object.defineProperty`。已知行为（3.x 起如此）：`serve-static` 的 `spa: true` 会把未命中文件的 GET 请求都回退到 `index.html`，GET 接口需要配置 `cell.serve-static.apiPath`。
- P5 `cell init` 与模板已完成（2026-11-08）：模板在 `@celljs/cli` 的 `templates/`（`backend`、`fullstack`），版本占位符替换为当前 CLI 版本；`AgentGuide` 把 `agent/guide.md`（随 CLI 版本发布的 Cell 指南）和已安装组件、模块的 `AGENTS.md` 写进项目 `AGENTS.md` 的标记区与 `.agents/skills/cell/SKILL.md`，`cell init` 与 `cell agent sync` 共用。模块提供模板（`addTemplate`）暂不做：`cell init` 在项目之外运行，没有模块可加载。agent 评测的起始项目改为 `cell init backend` 的产物，评测同时覆盖模板和 AGENTS.md。顺带修复：`createTestApp().get(X)` 对测试从源码导入的标识按类名 / symbol 描述匹配（应用在 module runner 里是另一份模块实例，原先 `Symbol('X')` 必然找不到）。
- P3 fc / scf / lambda / vercel 已完成（2026-11-08）：四个 preset 和部署器都内置在 `@celljs/cli`，不做"按需安装部署器包"：FC、SCF、Lambda 的部署器直接调用云 API，请求签名按各家公开算法自己实现（阿里云 ACS3、腾讯云 TC3、AWS SigV4，单测用官方示例向量校验），zip 由 `ZipWriter`（node:zlib）生成，合计约 1000 行，没有云 SDK，按需安装的理由（SDK 体积大）不再成立；Vercel 和 Cloudflare 一样调用官方 CLI（`npx vercel@65`）。运行方式不改 Cell 服务端：FC / SCF 用启动脚本在 9000 端口跑原样的服务端；Lambda 和 Vercel 通过 `ApplicationHost` 拿到 handler（Lambda 在初始化阶段监听回环端口，把事件转成 HTTP 请求）。preset 新增 `options`（cell.config.ts 中与 preset 同名的字段，Standard Schema 校验，所有内置 preset 的字段都被认领，切换 preset 不报错）、`outDir`（vercel 固定 `.vercel/output`）、`staticHosting`（cloudflare、vercel 不接受 --single / --compile）。顺带修复：构建前若输出目录是旧的 Cell 产物（有 manifest.json）就整体清空，否则换 preset 后上一个 preset 写的文件（如 `bootstrap`）会留在产物和代码包里；FC（Node 20.10）和 SCF（Node 18）没有 `import.meta.dirname`，服务端 bundle 加了补丁。验证：四个 preset 的代码包解压后在本机按平台方式运行（FC/SCF 启动脚本、Lambda 事件、Vercel 函数），FC/SCF 包另在 Node 20.10 / 18.15 上运行；Vercel 用 `vercel deploy --temporary --prebuilt` 真实部署一次（页面、API、单页回退、favicon 正常）。FC、SCF、Lambda 的真实部署需要用户的云账号凭据，尚未执行。
- 尚未实现、文档里不要当作现有能力：`addCommand` / `addFlag` / `addTemplate` / `addDeployer`（随 P3 / P5 的命令一起）、`cell build -e npm:…` 临时试用、宿主包导入映射（P3）、组件升级后的 CHANGELOG 提示、React Fast Refresh（需要 `@vitejs/plugin-react` 或自写 refresh 运行时，待定）。