diff --git a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.i18n.yaml
index 82629e613f..945501552c 100644
--- a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.i18n.yaml
+++ b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.i18n.yaml
@@ -33,8 +33,8 @@
en: a244f6a88fdabf3d
zh: e4feedb7297f4865
/agent-note-typert-gateway-targeted-method-calls/three-artifact-kinds-and-two-typescript-programs:
- en: c5bcd6eb76bf3d42
- zh: 9df2fbb364643186
+ en: 4479d41466723100
+ zh: 3a7f398e0d0070a5
/agent-note-typert-gateway-targeted-method-calls/the-remote-package-entry:
en: 75e977fe0fb71d59
zh: 00760c79da173cb7
diff --git a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md
index ba4e8eb4c9..b11aa25668 100644
--- a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md
+++ b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md
@@ -207,17 +207,9 @@ Client Program
`remote-client` is the Host Program's second emitter, not a third Program or the Client's local face. It contains no Host Cordis merge, Service class, Context class, or implementation code, and it does not enter the Host-local reflection registry.
-The Host lib build performs strict Host analysis and emits both the Host-local and Remote consumer artifacts. The Client lib then consumes the Remote DTS. The complete order is:
+Source-only Host analysis writes the Host-local and Remote consumer artifacts before package compilation. [The generation-order decision](../process/2026-10-18-source-first-metadata-builds.md) owns placement and its tradeoffs; [Development](../../../../docs/development.md#typescript-project-layout) owns the command sequence.
-```text
-Host lib build
-→ 生成 typert.host.{js,d.ts}
-→ 生成各业务包 lib/typert.remote-client.{js,d.ts,d.ts.map}
-→ 完成 Client lib 和 typert.client 产物
-→ Vite 构建 Web
-```
-
-The top-level `build` runs the step graph in `scripts/build.ts`, which completes the Host bundle and the Remote artifacts before starting Client TypeScript compilation. `build:lib` remains the complete serial lib build for `build:bench`, and its Host half `build:lib:host` serves the vendor release workflows. `typecheck` writes the Remote artifacts through `scripts/generate-typert.ts`, beside Host compilation, before Client TypeScript compilation. A clean build must not depend on stale `.d.ts` files from an earlier build.
+The top-level `build`, declaration-only `typecheck`, and serial `build:lib` share metadata preparation before package compilation. `build:lib` remains the complete serial lib build for `build:bench`; its Host half `build:lib:host` serves the vendor release workflows and retains aggregate checking. A clean build must not depend on stale `.d.ts` files from an earlier build.
Compiler-backed repository gates that resolve the consumer surface have the same prerequisite even when their primary inputs are source files. The public `typecheck`, `lint`, and `doc-typecheck` commands run the Host contract pass first. The gate scheduler may use their `*:contracts-ready` variants only after an explicit Typert-contract or complete-build dependency, so parallel lanes neither read missing declarations nor run concurrent generators against the same outputs.
diff --git a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md
index a6eb7dae73..a287402746 100644
--- a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md
+++ b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md
@@ -207,17 +207,9 @@ Client Program
`remote-client` 是 Host Program 的第二个 emitter,不是第三个 Program,也不是 Client 本地 face。它不包含 Host Cordis merge、Service class、Context class 或实现代码,不进入 Host 本地 reflection registry。
-Host lib 构建负责完成严格 Host 分析并产出 Host 本地 artifact 与 Remote 消费端 artifact;Client lib 随后消费 Remote DTS。完整顺序为:
+仅源码的 Host 分析在包编译之前写出 Host 本地 artifact 与 Remote 消费端 artifact。[生成顺序决策](../process/2026-10-18-source-first-metadata-builds.zh.md)负责生成位置及其取舍;[开发指南](../../../../docs/development.zh.md#typescript-project-layout)负责命令序列。
-```text
-Host lib build
-→ 生成 typert.host.{js,d.ts}
-→ 生成各业务包 lib/typert.remote-client.{js,d.ts,d.ts.map}
-→ 完成 Client lib 和 typert.client 产物
-→ Vite 构建 Web
-```
-
-顶层 `build` 运行 `scripts/build.ts` 中的步骤图,先完成 Host bundle 与 Remote artifact,再启动 Client TypeScript 编译。`build:lib` 仍是 `build:bench` 使用的完整串行 lib 构建,其 Host 部分 `build:lib:host` 供 vendor 发布 workflow 使用。`typecheck` 与 Host 编译并行,经 `scripts/generate-typert.ts` 写出 Remote artifact,再启动 Client TypeScript 编译。一次干净构建不能依赖上次残留的 `.d.ts`。
+顶层 `build`、仅生成声明的 `typecheck` 与串行 `build:lib` 都在包编译之前准备元数据。`build:lib` 仍是 `build:bench` 使用的完整串行 lib 构建;其 Host 部分 `build:lib:host` 供 vendor 发布 workflow 使用,并保留 aggregate 检查。一次干净构建不能依赖上次残留的 `.d.ts`。
即使主要输入是源文件,需要通过编译器解析消费方 surface 的仓库门禁也有相同的前置条件。公共 `typecheck`、`lint` 和 `doc-typecheck` 命令会先执行 Host 约定 pass。门禁调度器仅可在显式的 Typert 约定依赖或完整构建依赖完成后使用对应的 `*:contracts-ready` 变体,使并行 lane 既不会读取缺失的声明,也不会针对同一输出并发运行多个生成器。
diff --git a/.agents/notes/implemented/process/2026-10-11-build-artifacts-check-split.i18n.yaml b/.agents/notes/implemented/process/2026-10-11-build-artifacts-check-split.i18n.yaml
index 6b37f2ef96..ab4b4c81af 100644
--- a/.agents/notes/implemented/process/2026-10-11-build-artifacts-check-split.i18n.yaml
+++ b/.agents/notes/implemented/process/2026-10-11-build-artifacts-check-split.i18n.yaml
@@ -12,8 +12,8 @@
en: a15c7c13f167e5ac
zh: 63bcc2ef60c2f344
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/decision/commands:
- en: 3058f44cbcdbb1ea
- zh: 59cb9a5db6fd1825
+ en: cc797cf688f8de64
+ zh: 5d997b686a435cc5
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/decision/steps-and-dependencies:
en: c6c77bb1c7acf5e3
zh: d53c2358bef42502
@@ -36,5 +36,5 @@
en: 54905b912d17c74e
zh: 86a298004c8024e3
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/consequences:
- en: 2e5cf828ac5e2211
- zh: e3b12f671eb8378d
+ en: 48a8cbb9e8fee654
+ zh: ece1aeaee843b48a
diff --git a/.agents/notes/implemented/process/2026-10-11-build-artifacts-check-split.md b/.agents/notes/implemented/process/2026-10-11-build-artifacts-check-split.md
index 661cf0f912..fcd4fed452 100644
--- a/.agents/notes/implemented/process/2026-10-11-build-artifacts-check-split.md
+++ b/.agents/notes/implemented/process/2026-10-11-build-artifacts-check-split.md
@@ -18,8 +18,8 @@ Each command does only its own job.
| Command | Responsibility | Steps | Scheduling | Client build record |
|---|---|---|---|---|
-| `pnpm run build:artifacts` | Produces every artifact of `build`; package projects are fully type-checked as usual, the configured aggregate root programs are not checked | native + metadata → package/library steps → Web | Dependency order | Deleted at start, written after every step passes |
-| `pnpm run build:check` | Type-checks only the configured aggregate root programs | host-check, client-check | Independent checks concurrent, no prerequisite steps | Neither read nor written |
+| `pnpm run build:artifacts` | Produces every artifact of `build`; package projects are fully type-checked as usual, the Host and Client aggregate programs are not checked | [Artifact order](../../../../docs/development.md#typescript-project-layout) | Dependency order | Deleted at start, written after every step passes |
+| `pnpm run build:check` | Type-checks only the Host and Client aggregate programs | host-check, client-check | Host and Client checks concurrent, no prerequisite steps | Neither read nor written |
| `pnpm run build` | Every step of `build:artifacts` and `build:check` | The dependency graph below | Concurrent through the run-gates scheduler | Deleted at start, written after every step passes |
`build:check` neither detects, rebuilds, nor validates `build:artifacts` outputs; when prerequisite outputs are missing or stale, the compiler's own diagnostics (for example TS6305 and TS2307) report it, and no command adds prerequisite checks, guards, or fallbacks. `build:artifacts` produces the same artifacts and record as `build` and replaces `--artifacts-only`; that flag and the `libraries` mode of `compile-referenced-projects.ts` are removed, and the Python executable builder uses `--build-artifacts`. `build:official` remains `tsx scripts/build.ts --profile official` and runs the complete `build`.
@@ -136,6 +136,6 @@ Development iterations can skip the two aggregate root programs with `build:arti
Removing or changing `build` in the two required lanes, consumers and windows-build, requires first transferring both aggregate checks to another blocking owner. The typecheck scope and the lint prerequisites generate Typert contracts without bundling ([local gate speed-ups](2026-10-12-local-gate-speedups.md)); artifact scopes use [source-first metadata ordering](2026-10-18-source-first-metadata-builds.md).
-client-src's dependency on host-bundle guarantees the "Host and Remote artifacts complete before Client compilation" ordering that [Typert Remote method calls](../architecture/2026-08-02-typert-remote-method-calls.md) describes.
+host-src depends on host-typert, so generated Remote declarations precede package compilation. [Typert Remote method calls](../architecture/2026-08-02-typert-remote-method-calls.md) describes the emitted contributions.
Verification: `scripts/build.spec.ts` covers the type-stripping check, rejection of the removed flag and invalid scopes, the conflict between `--profile` and `--scope check`, scope selection, step dependencies, and the environment; `compile-referenced-projects.spec.ts` covers project mode checking only the aggregate program, the diagnostic for missing declarations, and `tsc -b --dry` accepting the tsbuildinfo that `tsc -p` writes; failure propagation, output buffering, and process-tree termination come from the run-gates scheduler and its tests; `run-gates.spec.ts`, `ci-workflow.spec.ts`, `preview-workflow.spec.ts`, `build-exe-for-python-sdk.spec.ts`, `dev-web.spec.ts`, and `tests/ci-release-selfhosted.spec.ts` pin each caller's command.
diff --git a/.agents/notes/implemented/process/2026-10-11-build-artifacts-check-split.zh.md b/.agents/notes/implemented/process/2026-10-11-build-artifacts-check-split.zh.md
index 7f9136a1e5..cc52a1a925 100644
--- a/.agents/notes/implemented/process/2026-10-11-build-artifacts-check-split.zh.md
+++ b/.agents/notes/implemented/process/2026-10-11-build-artifacts-check-split.zh.md
@@ -18,8 +18,8 @@ Status: implemented
| 命令 | 职责 | 步骤 | 调度 | client build record |
|---|---|---|---|---|
-| `pnpm run build:artifacts` | 产出 `build` 的全部产物;包 project 照常完整类型检查,不检查已配置的聚合根程序 | native + metadata → package/library steps → Web | 依赖顺序 | 开始时删除,全部通过后写入 |
-| `pnpm run build:check` | 只类型检查已配置的聚合根程序 | host-check、client-check | 独立检查并发,无前置步骤 | 不读写 |
+| `pnpm run build:artifacts` | 产出 `build` 的全部产物;包 project 照常完整类型检查,不检查Host 与 Client 聚合程序 | [产物顺序](../../../../docs/development.zh.md#typescript-project-layout) | 依赖顺序 | 开始时删除,全部通过后写入 |
+| `pnpm run build:check` | 只类型检查Host 与 Client 聚合程序 | host-check、client-check | Host 与 Client 检查并发,无前置步骤 | 不读写 |
| `pnpm run build` | `build:artifacts` 与 `build:check` 的全部步骤 | 下文依赖图 | run-gates 调度器并发执行 | 开始时删除,全部通过后写入 |
`build:check` 不检测、不重建、也不校验 `build:artifacts` 的产物;前置产物缺失或过期时,由编译器自身的诊断(例如 TS6305、TS2307)报告,任何命令都不增加前置检查、守卫或回退逻辑。`build:artifacts` 产出的产物与 record 和 `build` 相同,并取代 `--artifacts-only`;该参数与 `compile-referenced-projects.ts` 的 `libraries` 模式已删除,Python executable builder 使用 `--build-artifacts`。`build:official` 仍是 `tsx scripts/build.ts --profile official`,执行完整的 `build`。
@@ -136,6 +136,6 @@ CI 证据:PR #6005 上,两个运行并发 `pnpm run build` 的必需 lane
删除或改变 consumers 与 windows-build 两个必需 lane 的 `build`,必须先把两个聚合检查转交另一处阻塞型 owner。typecheck scope 与 lint 类前置步骤不打包即生成 Typert 约定([本地门禁提速](2026-10-12-local-gate-speedups.zh.md));产物 scope 采用[先生成元数据的顺序](2026-10-18-source-first-metadata-builds.zh.md)。
-[Typert Remote 方法调用](../architecture/2026-08-02-typert-remote-method-calls.zh.md)描述的“Client 编译前先完成 Host 与 Remote 产物”由 client-src 对 host-bundle 的依赖保证。
+host-src 依赖 host-typert,因此生成的 Remote 声明先于包编译。[Typert Remote 方法调用](../architecture/2026-08-02-typert-remote-method-calls.zh.md)说明生成的贡献。
验证:`scripts/build.spec.ts` 覆盖类型剥离检查、已删除参数与非法范围的拒绝、`--profile` 与 `--scope check` 的冲突、范围选择、步骤依赖与环境;`compile-referenced-projects.spec.ts` 覆盖 project mode 只检查聚合程序、缺失声明的诊断,以及 `tsc -b --dry` 接受 `tsc -p` 写出的 tsbuildinfo;失败传播、输出缓冲与进程树终止沿用 run-gates 的调度实现及其测试;`run-gates.spec.ts`、`ci-workflow.spec.ts`、`preview-workflow.spec.ts`、`build-exe-for-python-sdk.spec.ts`、`dev-web.spec.ts` 与 `tests/ci-release-selfhosted.spec.ts` 固定各调用方的命令。
diff --git a/.agents/notes/implemented/process/2026-10-18-source-first-metadata-builds.i18n.yaml b/.agents/notes/implemented/process/2026-10-18-source-first-metadata-builds.i18n.yaml
index b7696f134e..38b68fadf4 100644
--- a/.agents/notes/implemented/process/2026-10-18-source-first-metadata-builds.i18n.yaml
+++ b/.agents/notes/implemented/process/2026-10-18-source-first-metadata-builds.i18n.yaml
@@ -9,14 +9,14 @@
en: 3c40c9bd375290a6
zh: dc8d892bdd08c6b4
/agent-note-generate-metadata-before-package-compilation/decision:
- en: 7121ef78b28675af
- zh: ef7538377c6d367e
+ en: c9f896731a173a13
+ zh: 9978e46528caa456
/agent-note-generate-metadata-before-package-compilation/measurements:
- en: 80048ef5e9bc06ea
- zh: f74ec5f0fc617080
+ en: 42f488af4497f671
+ zh: 00eeb4cc51799da3
/agent-note-generate-metadata-before-package-compilation/alternatives-considered:
en: 81fde4514a8b6fc0
zh: 4739f5c7978e7db2
/agent-note-generate-metadata-before-package-compilation/consequences:
- en: 5f7d7bbeae4c86a7
- zh: 32fe8710a1c3015e
+ en: b12bcb7cc9467706
+ zh: 8896ac7cd011e3c0
diff --git a/.agents/notes/implemented/process/2026-10-18-source-first-metadata-builds.md b/.agents/notes/implemented/process/2026-10-18-source-first-metadata-builds.md
index 59e39a6ec9..058da1e9a0 100644
--- a/.agents/notes/implemented/process/2026-10-18-source-first-metadata-builds.md
+++ b/.agents/notes/implemented/process/2026-10-18-source-first-metadata-builds.md
@@ -12,9 +12,9 @@ A Host package can consume generated Remote declarations without becoming a Clie
Artifact and declaration-only builds run source-only Typert generation before package compilation. The existing generator rejects workspace build outputs and unresolved workspace imports, so a metadata producer cannot depend on its own generated declarations through another source package. TypeScript retains compiler-face ownership and rejects project-reference cycles. Generation failure stops dependent compilation and bundling.
-The root bundler uses `decoratorPlugin()` to lower standard TypeScript decorators without generating metadata again. The public `typertPlugin()` retains package/workspace generation and its Worker Thread behavior. Public Node Client providers, when present, must bundle before their consumers; source-first metadata does not remove that runtime dependency. [Development](../../../../docs/development.md#typescript-project-layout) owns the current command order.
+The root bundler uses `decoratorPlugin()` to lower standard TypeScript decorators without generating metadata again. The public `typertPlugin()` retains package/workspace generation and its Worker Thread behavior. [Development](../../../../docs/development.md#typescript-project-layout) owns the current command order.
-This ordering replaces only the generation placement in [local gate speed-ups](2026-10-12-local-gate-speedups.md), [lossless build speed-ups](2026-10-11-lossless-build-speedups.md), and [the build/check split](2026-10-11-build-artifacts-check-split.md). Their other optimizations, failure guarantees, public plugin behavior, and historical measurements remain useful and stay active.
+This ordering replaces only the generation placement in [local gate speed-ups](2026-10-12-local-gate-speedups.md), [lossless build speed-ups](2026-10-11-lossless-build-speedups.md), [the build/check split](2026-10-11-build-artifacts-check-split.md), and [Remote method generation](../architecture/2026-08-02-typert-remote-method-calls.md). Their other optimizations, failure guarantees, public plugin behavior, and historical measurements remain useful and stay active.
## Measurements
@@ -23,7 +23,7 @@ The local experiment uses macOS ARM64, Node 24.19.0 and TypeScript 6.0.3. `DSH_G
| Inputs | Clean baseline / candidate (s) | No-change baseline / candidate (s) | Clean peak RSS baseline / candidate (MiB) |
|---|---|---|---|
| Without Node Session consumers | 56.67, 53.55 / 56.29, 56.37 | 13.64, 13.74 / 15.32, 15.26 | 3855, 3858 / 3255, 3268 |
-| With Node Session consumers and delayed-stage removal | 61.18, 61.41 / 56.27, 56.44 | 20.08, 20.09 / 15.98, 16.09 | 4013, 4012 / 3278, 3291 |
+| Separate prototype: Node Session consumers and delayed-stage removal | 61.18, 61.41 / 56.27, 56.44 | 20.08, 20.09 / 15.98, 16.09 | 4013, 4012 / 3278, 3291 |
RSS is summed over descendant processes approximately every 100ms; shared pages count more than once, so this is not unique physical memory. Two samples per case are local evidence, not a CI budget. All 150 generated Typert/Remote files are byte-identical in both comparisons. Complete inventories contain 12,246 and 12,352 files respectively; only the changed decorator-plugin module's four compiler outputs differ.
@@ -39,6 +39,8 @@ RSS is summed over descendant processes approximately every 100ms; shared pages
## Consequences
+The consumer row is evidence from a separate integration prototype. It does not describe the packages or validation delivered by this prerequisite.
+
The standalone prerequisite costs 1.60s (+11.7%) in median no-change artifact time because generation no longer overlaps bundling. Its clean difference is +1.22s, smaller than the baseline spread. This is ordering support, not an independent speedup. With delayed stages removed, the measured consumer build improves by 4.94s clean and 4.05s without changes. Peak summed RSS decreases in both cases.
-Source-generation tests reject metadata dependency cycles with absent and preexisting outputs. Compiler fixtures reject project-reference cycles before bundling. Decorator tests exercise both plugin entry points; complete builds and built Node-provider smokes retain runtime exports and identities. Wine's separate command sequence prepares metadata explicitly; its source guard rejects missing preparation, while native Windows execution remains CI-owned.
+Source-generation tests reject metadata dependency cycles with absent and preexisting outputs. A fresh Host-consumer fixture compiles against metadata generated in the same build. Decorator tests exercise both plugin entry points, and complete builds verify application artifacts. Wine's separate command sequence prepares metadata explicitly; its source guard rejects missing preparation, while native Windows execution remains CI-owned.
diff --git a/.agents/notes/implemented/process/2026-10-18-source-first-metadata-builds.zh.md b/.agents/notes/implemented/process/2026-10-18-source-first-metadata-builds.zh.md
index c94234ec0a..c2f43549c3 100644
--- a/.agents/notes/implemented/process/2026-10-18-source-first-metadata-builds.zh.md
+++ b/.agents/notes/implemented/process/2026-10-18-source-first-metadata-builds.zh.md
@@ -12,9 +12,9 @@ Host 包可以消费生成的 Remote 声明,而不必成为 Client 包。如
产物构建与仅生成声明的构建都在包编译之前运行仅源码的 Typert 生成。现有生成器拒绝工作区构建产物与无法解析的工作区导入,因此元数据生产者不能经由另一个源码包依赖自身生成的声明。TypeScript 保留 compiler face 的归属,并拒绝 project reference 环。生成失败后不启动依赖它的编译与打包。
-根打包器使用 `decoratorPlugin()` 转换标准 TypeScript 装饰器,不重复生成元数据。公开的 `typertPlugin()` 保留 package/workspace 生成与 Worker Thread 行为。存在公开 Node Client provider 时,仍须在消费方之前打包;仅源码生成不消除该运行时依赖。[开发指南](../../../../docs/development.zh.md#typescript-project-layout)负责当前命令顺序。
+根打包器使用 `decoratorPlugin()` 转换标准 TypeScript 装饰器,不重复生成元数据。公开的 `typertPlugin()` 保留 package/workspace 生成与 Worker Thread 行为。[开发指南](../../../../docs/development.zh.md#typescript-project-layout)负责当前命令顺序。
-该顺序只替代[本地门禁提速](2026-10-12-local-gate-speedups.zh.md)、[无损构建提速](2026-10-11-lossless-build-speedups.zh.md)与[构建/检查拆分](2026-10-11-build-artifacts-check-split.zh.md)中的生成位置。它们的其他优化、失败保证、公开插件行为与历史测量仍有价值,因此保留为活跃记录。
+该顺序只替代[本地门禁提速](2026-10-12-local-gate-speedups.zh.md)、[无损构建提速](2026-10-11-lossless-build-speedups.zh.md)、[构建/检查拆分](2026-10-11-build-artifacts-check-split.zh.md)与 [Remote 方法生成](../architecture/2026-08-02-typert-remote-method-calls.zh.md)中的生成位置。它们的其他优化、失败保证、公开插件行为与历史测量仍有价值,因此保留为活跃记录。
## Measurements
@@ -23,7 +23,7 @@ Host 包可以消费生成的 Remote 声明,而不必成为 Client 包。如
| 输入 | 干净构建基线/候选 (s) | 无改动构建基线/候选 (s) | 干净构建 RSS 峰值基线/候选 (MiB) |
|---|---|---|---|
| 不含 Node Session 消费方 | 56.67, 53.55 / 56.29, 56.37 | 13.64, 13.74 / 15.32, 15.26 | 3855, 3858 / 3255, 3268 |
-| 包含 Node Session 消费方并删除后置阶段 | 61.18, 61.41 / 56.27, 56.44 | 20.08, 20.09 / 15.98, 16.09 | 4013, 4012 / 3278, 3291 |
+| 独立原型:Node Session 消费方与后置阶段删除 | 61.18, 61.41 / 56.27, 56.44 | 20.08, 20.09 / 15.98, 16.09 | 4013, 4012 / 3278, 3291 |
RSS 约每 100ms 汇总所有子孙进程;共享页面会重复计数,因此不代表唯一物理内存。每种情况的两个样本是本地证据,不是 CI 预算。两组比较中的全部 150 个 Typert/Remote 生成文件逐字节相同。完整清单分别包含 12,246 与 12,352 个文件;只有装饰器插件模块的四个编译产物不同。
@@ -39,6 +39,8 @@ RSS 约每 100ms 汇总所有子孙进程;共享页面会重复计数,因此
## Consequences
+消费方一行是独立集成原型的测量证据,不表示本前置改动交付的包或验证。
+
独立前置改动使无改动产物构建的中位数增加 1.60s(+11.7%),因为生成不再与打包重叠。干净构建增加 1.22s,小于基线的样本波动。该改动提供顺序支持,并非独立提速。删除后置阶段后,测得的消费方构建在干净状态下快 4.94s,无改动时快 4.05s。两组的进程树 RSS 峰值都下降。
-源码生成测试在产物缺失和已存在时均拒绝元数据依赖环。编译器 fixture 在打包前拒绝 project reference 环。装饰器测试覆盖两个插件入口;完整构建与构建产物上的 Node provider 冒烟保留运行时导出和身份。Wine 的独立命令序列显式准备元数据;源码守卫拒绝缺失的准备步骤,原生 Windows 执行仍由 CI 负责。
+源码生成测试在产物缺失和已存在时均拒绝元数据依赖环。干净的 Host 消费方 fixture 使用同一次构建生成的元数据完成编译。装饰器测试覆盖两个插件入口,完整构建验证应用产物。Wine 的独立命令序列显式准备元数据;源码守卫拒绝缺失的准备步骤,原生 Windows 执行仍由 CI 负责。
diff --git a/apps/web/tests/README.i18n.yaml b/apps/web/tests/README.i18n.yaml
index 9dc3bf2779..9c182e45e0 100644
--- a/apps/web/tests/README.i18n.yaml
+++ b/apps/web/tests/README.i18n.yaml
@@ -12,5 +12,5 @@
en: 8c406d423c8e2137
zh: a66a144fd146f56c
/apps-web-browser-e2e/do-not-import-deepseek-ai-dsh-client-here:
- en: 4ec4978cd394dc0d
- zh: 431e8dfd98e99917
+ en: 7cc75a6413647142
+ zh: a8c541f9b93e9f7f
diff --git a/apps/web/tests/README.md b/apps/web/tests/README.md
index 55b9ed5d4c..bfcba1ad90 100644
--- a/apps/web/tests/README.md
+++ b/apps/web/tests/README.md
@@ -34,7 +34,7 @@ They type-check in the root `tsconfig.host.json`, not in the Client aggregate, b
## Do not import `@deepseek-ai/dsh-client-*` here
-Importing a Client package — a value or a type — pulls its whole TypeScript project, and every project it references, into the **Host build graph**. That has bitten this lane once already: four Client consumer packages reference `api/remotes`' Client face, which cannot compile until Host tsdown has generated `@deepseek-ai/dsh-goal/remote`, so the Host build phase ended up waiting on an artifact it produces itself.
+Importing a Client package — a value or a type — pulls its TypeScript project and referenced projects into the Host build graph. Generated Remote declarations are available before package compilation, but this does not make the Host and Client Cordis `Context` declarations compatible. Keep these tests on the Host face and use only the explicit built-client harness exception below.
When a scenario needs a Client-owned constant or pure function, mirror it here instead, next to the commented-out import that names the source module. A drift then surfaces as a missed selector or a stale mirrored value — a loud failure, never a silent pass. `scaffold.ts` follows this rule for the welcome-notice namespace, acknowledgement field, version, and asserted Chinese copy.
diff --git a/apps/web/tests/README.zh.md b/apps/web/tests/README.zh.md
index 1350b245bf..24d8bdfd5d 100644
--- a/apps/web/tests/README.zh.md
+++ b/apps/web/tests/README.zh.md
@@ -34,7 +34,7 @@ pnpm --filter @deepseek-ai/dsh-web-frontend exec playwright install --with-deps
## 不要在此 import `@deepseek-ai/dsh-client-*`
-import 一个 Client 包——无论值还是类型——都会把它整个 TypeScript 工程、以及它引用的每个工程拉进 **Host 构建图**。这已经坑过本 lane 一次:四个 Client 消费方包引用了 `api/remotes` 的 Client face,而该 face 必须等 Host tsdown 生成 `@deepseek-ai/dsh-goal/remote` 之后才能编译,于是 Host 构建阶段变成在等一个由它自己产出的产物。
+import 一个 Client 包——无论值还是类型——都会把它的 TypeScript 工程及其引用的工程拉进 Host 构建图。生成的 Remote 声明在包编译之前就绪,但这并不使 Host 与 Client 的 Cordis `Context` 声明兼容。保留这些测试的 Host face 归属,只使用下文明确的 built-client harness 例外。
当某个场景需要 Client 持有的常量或纯函数时,改为在此处镜像一份,并紧挨着一条注释掉的 import 点明源模块。这样漂移会表现为选择器未命中或镜像值陈旧——是响亮的失败,绝不会是静默通过。`scaffold.ts` 按此规则镜像 welcome-notice 的 namespace、确认字段、版本和被断言的中文文案。
diff --git a/docs/api-gateway.i18n.yaml b/docs/api-gateway.i18n.yaml
index 65b0c3e196..6ea6996144 100644
--- a/docs/api-gateway.i18n.yaml
+++ b/docs/api-gateway.i18n.yaml
@@ -12,8 +12,8 @@
en: fe382a9bcc768cc2
zh: 78e3d50d59780c3d
/api-gateway/strict-generation-pipeline:
- en: 9c4a98eb0539ec7f
- zh: ad2a145abd805fdd
+ en: 5e1c9c0e0929d0e4
+ zh: dc2d9ae2374d6970
/api-gateway/runtime-invocation:
en: 37b4858fa48a61b6
zh: 9a296a2171f1ab7d
@@ -21,8 +21,8 @@
en: 9570ffe7a348e5ac
zh: 2b70b61b1ff1fbb0
/api-gateway/development-mode:
- en: 23ae422bd65300da
- zh: 67dd9c02f4ffec53
+ en: 56f37b64ddd8ab68
+ zh: e1cbf07fb425bb85
/api-gateway/boundaries:
en: e6befc5f8c90f97b
zh: 4ace4a493fd2b41f
diff --git a/docs/api-gateway.md b/docs/api-gateway.md
index 484b866e95..103822c4aa 100644
--- a/docs/api-gateway.md
+++ b/docs/api-gateway.md
@@ -99,11 +99,11 @@ The API Gateway package owns the Host dispatcher and Client Remote endpoint as p
## Strict generation pipeline
-The root build compiles and bundles the Host face, then the Client face, then builds the Web shell ([step order](development.md#typescript-project-layout)). The Host phase first compiles the Host package references, then runs `tsdown --env.DSH_BUILD_FACE host`; the normal Host Project Reference graph compiles the Typert generator, which runs during this tsdown pass with the Host aggregate as its only `ts.Program` seed. The Client phase then compiles the Client package references and runs `tsdown --env.DSH_BUILD_FACE client`, consuming the newly generated Remote Client declarations and runtime contributions without starting Typert again.
+The root build first runs source-only Host analysis through `scripts/generate-typert.ts`, then compiles and bundles the Host and Client faces and builds Web ([step order](development.md#typescript-project-layout)). The generator uses the Host aggregate as its only `ts.Program` seed and writes Remote declarations before package compilation. The Host tsdown pass lowers decorators without generating metadata again; the Client pass consumes those declarations and runtime contributions.
Both tsdown passes match `vendor/*`, `packages/*/*`, and `apps/cli`, the Host pass also `apps/desktop-host` ([build order](development.md#typescript-project-layout)), and both bundle only JavaScript emitted to `lib/types` by the corresponding tsc phase. The root config does not scan Client artifacts, classify package names, or pass a maintained filter to tsdown; package-local configs return entries for the current phase based on `DSH_BUILD_FACE`. An ordinary Client plugin produces both its Node loader entry and browser bundle during the Client phase.
-`api/remotes`, `api/gateway`, `api/session-controller`, and `api/workspace-controller` (plus `client/connection`) split TypeScript faces. `api/remotes`' Client project depends on `/remote` declarations generated for business packages during Host tsdown; root aggregates and direct consumers must reference each split package's `tsconfig.host.json` or `tsconfig.client.json` respectively. `api-remotes`' `clientBundle(..., { hostPhase: true })` produces its Host entry during Host tsdown and leaves only the browser entry for Client tsdown. The Agent/Session lookup policy lives in `@deepseek-ai/dsh-api-session-controller`, not in `api-remotes`.
+`api/remotes`, `api/gateway`, `api/session-controller`, and `api/workspace-controller` (plus `client/connection`) split TypeScript faces. `api/remotes`' Client project depends on `/remote` declarations generated for business packages before package compilation; root aggregates and direct consumers must reference each split package's `tsconfig.host.json` or `tsconfig.client.json` respectively. `api-remotes`' `clientBundle(..., { hostPhase: true })` produces its Host entry during Host tsdown and leaves only the browser entry for Client tsdown. The Agent/Session lookup policy lives in `@deepseek-ai/dsh-api-session-controller`, not in `api-remotes`.
Each contributing business package writes generated files to its own `lib/` directory, not to its source directory:
@@ -157,7 +157,7 @@ Changing only a Remote method's implementation body without changing its contrac
pnpm run build:lib
```
-The running Client watcher consumes these generated files when it rebundles. If `pnpm run build:lib:host` has already refreshed the Host contract, `pnpm run build:lib:client` can complete the Client side; a clean worktree cannot skip the Host phase. Recompiling only the frontend source cannot infer new types from Host decorators. `pnpm run typecheck` generates the Host contract with the bundle-free Typert pass before Client tsc, and the CI build lanes and release builds generate it in the Host bundle phase before Client tsc.
+The running Client watcher consumes these generated files when it rebundles. If `pnpm run build:lib:host` has already refreshed the Host contract, `pnpm run build:lib:client` can complete the Client side; a clean worktree cannot skip the Host phase. Recompiling only the frontend source cannot infer new types from Host decorators. `pnpm run typecheck` generates the Host contract with the bundle-free Typert pass before Client tsc, and the CI build lanes and release builds use the same source-only generation before package compilation.
## Boundaries
diff --git a/docs/api-gateway.zh.md b/docs/api-gateway.zh.md
index c44d6f403f..8e2df4acbe 100644
--- a/docs/api-gateway.zh.md
+++ b/docs/api-gateway.zh.md
@@ -101,11 +101,11 @@ API Gateway 包同时拥有 Host dispatcher 与 Client Remote endpoint 两个对
## 严格生成流水线
-根构建依次编译并打包 Host face、Client face,再构建 Web 壳层([步骤顺序](development.zh.md#typescript-project-layout))。Host 阶段先编译 Host 包 reference,再运行 `tsdown --env.DSH_BUILD_FACE host`;Typert generator 由正常 Host Project Reference 图编译,并在这次 tsdown 中以 Host aggregate 为唯一 `ts.Program` 种子运行。Client 阶段随后编译 Client 包 reference 并运行 `tsdown --env.DSH_BUILD_FACE client`,使用刚生成的 Remote Client 声明和运行时贡献,但不再次启动 Typert。
+根构建首先通过 `scripts/generate-typert.ts` 运行仅源码的 Host 分析,再编译并打包 Host 与 Client face,最后构建 Web([步骤顺序](development.zh.md#typescript-project-layout))。生成器以 Host aggregate 为唯一 `ts.Program` 种子,在包编译前写出 Remote 声明。Host tsdown 阶段转换装饰器,但不重复生成元数据;Client 阶段消费这些声明与运行时贡献。
两次 tsdown 都匹配 `vendor/*`、`packages/*/*` 与 `apps/cli`,Host 阶段另外匹配 `apps/desktop-host`([构建顺序](development.zh.md#typescript-project-layout)),且都只打包 `lib/types` 中由对应 tsc 阶段发射的 JavaScript。根配置不扫描 Client 产物、不按包名分类,也不向 tsdown 传维护式 filter;各包的本地配置根据 `DSH_BUILD_FACE` 返回当前阶段的入口。普通 Client 插件在 Client 阶段一起生成 Node loader 入口与 browser bundle。
-`api/remotes`、`api/gateway`、`api/session-controller` 与 `api/workspace-controller`(外加 `client/connection`)都拆分 TypeScript face。`api/remotes` 的 Client project 依赖业务包在 Host tsdown 中生成的 `/remote` 声明;根 aggregate 与直接消费方必须分别引用各拆分包自己的 `tsconfig.host.json` 或 `tsconfig.client.json`。`api-remotes` 的 `clientBundle(..., { hostPhase: true })` 让 Host 入口在 Host tsdown 中生成,让 Client tsdown 只生成 browser 入口。Agent/Session lookup 策略位于 `@deepseek-ai/dsh-api-session-controller`,而非 `api-remotes`。
+`api/remotes`、`api/gateway`、`api/session-controller` 与 `api/workspace-controller`(外加 `client/connection`)都拆分 TypeScript face。`api/remotes` 的 Client project 依赖业务包在包编译前生成的 `/remote` 声明;根 aggregate 与直接消费方必须分别引用各拆分包自己的 `tsconfig.host.json` 或 `tsconfig.client.json`。`api-remotes` 的 `clientBundle(..., { hostPhase: true })` 让 Host 入口在 Host tsdown 中生成,让 Client tsdown 只生成 browser 入口。Agent/Session lookup 策略位于 `@deepseek-ai/dsh-api-session-controller`,而非 `api-remotes`。
每个贡献业务包把生成文件写入自己的 `lib/`,而不是源码目录:
@@ -159,7 +159,7 @@ pnpm run dev:web
pnpm run build:lib
```
-运行中的 Client watcher 会在重新打包时消费这些生成文件。若已单独运行 `pnpm run build:lib:host` 刷新 Host 约定,也可再运行 `pnpm run build:lib:client` 完成 Client 侧;干净工作树不能跳过 Host 阶段。仅重新编译前端源码不能从 Host decorator 推导新类型。`pnpm run typecheck` 先以不打包的 Typert 生成刷新 Host 约定,再运行 Client tsc;CI 的构建 lane 与发布构建则在 Client tsc 之前的 Host bundle 阶段生成它。
+运行中的 Client watcher 会在重新打包时消费这些生成文件。若已单独运行 `pnpm run build:lib:host` 刷新 Host 约定,也可再运行 `pnpm run build:lib:client` 完成 Client 侧;干净工作树不能跳过 Host 阶段。仅重新编译前端源码不能从 Host decorator 推导新类型。`pnpm run typecheck` 先以不打包的 Typert 生成刷新 Host 约定,再运行 Client tsc;CI 的构建 lane 与发布构建同样在包编译之前使用仅源码生成。
diff --git a/package.json b/package.json
index f6a50922c2..39c7f94f95 100644
--- a/package.json
+++ b/package.json
@@ -24,7 +24,7 @@
"build:bench": "npm run build:native-system && npm run build:lib && tsdown --config-loader native --config benchmarks/tsdown.config.ts",
"build:official": "tsx scripts/build.ts --profile official",
"build:lib": "pnpm run build:lib:host && pnpm run build:lib:client",
- "build:lib:host": "node --max-old-space-size=4096 ./node_modules/typescript/bin/tsc -b tsconfig.host.json && tsdown --config-loader native --env.DSH_BUILD_FACE host && pnpm --filter @deepseek-ai/dsh-desktop run bundle",
+ "build:lib:host": "tsx scripts/compile-referenced-projects.ts host-libraries && node --max-old-space-size=4096 ./node_modules/typescript/bin/tsc -p tsconfig.host.json && pnpm --filter @deepseek-ai/dsh-desktop run bundle",
"build:lib:client": "tsc -b tsconfig.client.json && tsdown --config-loader native --env.DSH_BUILD_FACE client",
"build:web": "pnpm --filter @deepseek-ai/dsh-web-frontend run build",
"build:desktop": "pnpm --filter @deepseek-ai/dsh-desktop run build",
diff --git a/packages/api/remotes/README.i18n.yaml b/packages/api/remotes/README.i18n.yaml
index 3339260812..f3b9eab6fc 100644
--- a/packages/api/remotes/README.i18n.yaml
+++ b/packages/api/remotes/README.i18n.yaml
@@ -21,8 +21,8 @@
en: 31bb9a2f50094b84
zh: 226c055739d5def7
/deepseek-ai-dsh-api-remotes/build-boundary:
- en: 7a9934a3ba5b185f
- zh: 40457fd5707f57f0
+ en: a73c2b7e1069a750
+ zh: 5b3f87a9ea29cb14
/deepseek-ai-dsh-api-remotes/model-experience:
en: 6a60ae036c71e76f
zh: bf5e86a5ac5b4be4
diff --git a/packages/api/remotes/README.md b/packages/api/remotes/README.md
index bde4e4c93a..8d49cb49a5 100644
--- a/packages/api/remotes/README.md
+++ b/packages/api/remotes/README.md
@@ -53,7 +53,7 @@ The payload-free `schedule/changed` event invalidates Schedule task queries; Cli
## Build boundary
-Most repository packages belong to one TypeScript face: Host packages are registered in the root `tsconfig.host.json`, and Client packages in the root `tsconfig.client.json`. This package splits because its Host entry must participate in the Host Typert graph, while `src/client/index.ts` cannot compile until Host tsdown has generated the business packages' `/remote` declarations.
+Most repository packages belong to one TypeScript face: Host packages are registered in the root `tsconfig.host.json`, and Client packages in the root `tsconfig.client.json`. This package splits because its Host entry must participate in the Host Typert graph, while `src/client/index.ts` cannot compile until the source-only generation step has written the business packages' `/remote` declarations.
This package's root `tsconfig.json` is only a solution that references `tsconfig.host.json` and `tsconfig.client.json`. The Host aggregate and direct Host consumers reference the former, while the Client aggregate and direct Client consumers reference the latter; the package-root solution must not enter either aggregate's dependency graph. The two projects own disjoint source files and `.tsbuildinfo` files but share the `lib/types` output directory, with one deliberate exception: `src/remote-events.ts` and `src/types.ts` are listed in BOTH faces' `files`, because the forwarded-event allowlist is the single control point over what a consumer can receive, and the Host forwarding loop and the Client `ctx.remote.$on` key face must read one declaration rather than two that could drift.
diff --git a/packages/api/remotes/README.zh.md b/packages/api/remotes/README.zh.md
index 680e41e045..4f9772403d 100644
--- a/packages/api/remotes/README.zh.md
+++ b/packages/api/remotes/README.zh.md
@@ -53,7 +53,7 @@ Host entry 为每条 Client 流独立注册一组 allowlist listener 和一个
## 构建边界
-仓库中的多数包只属于一个 TypeScript face:Host 包登记在根 `tsconfig.host.json`,Client 包登记在根 `tsconfig.client.json`。本包需要拆分,因为 Host 入口要参与 Host Typert 图,而 `src/client/index.ts` 必须等 Host tsdown 生成业务包的 `/remote` 声明后才能编译。
+仓库中的多数包只属于一个 TypeScript face:Host 包登记在根 `tsconfig.host.json`,Client 包登记在根 `tsconfig.client.json`。本包需要拆分,因为 Host 入口要参与 Host Typert 图,而 `src/client/index.ts` 必须等仅源码生成步骤写出业务包的 `/remote` 声明后才能编译。
本包根 `tsconfig.json` 只是引用 `tsconfig.host.json` 与 `tsconfig.client.json` 的 solution。Host aggregate 和 Host 直接消费方引用前者,Client aggregate 和 Client 直接消费方引用后者;禁止把包根 solution 放进任一 aggregate 的依赖图。两个 project 拥有互不重叠的源码和 `.tsbuildinfo`,但共享 `lib/types` 输出目录——只有一处刻意的例外:`src/remote-events.ts` 与 `src/types.ts` **同时**列进两个 face 的 `files`,因为转发事件名单是「消费端能收到什么」的唯一控制点,Host 转发循环与 Client 的 `ctx.remote.$on` 键面必须读同一份声明,而不是两份可能彼此漂移的声明。
diff --git a/packages/typert/generator/README.i18n.yaml b/packages/typert/generator/README.i18n.yaml
index 06e8924ea9..f0ae35ad12 100644
--- a/packages/typert/generator/README.i18n.yaml
+++ b/packages/typert/generator/README.i18n.yaml
@@ -24,14 +24,14 @@
en: 6bb456bb03fc6617
zh: 029407c373b8e6a6
/deepseek-ai-dsh-typert-generator/use-this-package/analyzing-a-workspace-statically:
- en: 8227097859169cdf
- zh: 249790ed62e8558a
+ en: 0a4f5896df9f2fab
+ zh: e7d0d6cddd7a59e7
/deepseek-ai-dsh-typert-generator/use-this-package/running-generation-inside-a-tsdown-build:
- en: 6b4df2ee7a16f46c
- zh: dda8992e29630761
+ en: 15ecce4f32b7a8c0
+ zh: c7d6b449d7b20338
/deepseek-ai-dsh-typert-generator/use-this-package/generating-without-a-bundler:
- en: e1afb26aea87fe46
- zh: ed3cdfde78d87825
+ en: cf6893be24bdc495
+ zh: dafc23e2d2e8553e
/deepseek-ai-dsh-typert-generator/understand-the-implementation:
en: b90762df7a978580
zh: 123ab225a2b5df2a
diff --git a/packages/typert/generator/README.md b/packages/typert/generator/README.md
index ee1077cfa4..1a33a22a2b 100644
--- a/packages/typert/generator/README.md
+++ b/packages/typert/generator/README.md
@@ -60,21 +60,21 @@ Every generated parameter spec contains both `codec` and `strictCodec`, derived
### Analyzing a workspace statically
-Static consumers call `WorkspaceAnalyzer` directly against the workspace's `tsconfig.host.json` and `tsconfig.client.json` aggregates, select a face and package subset, and read the resulting `FaceModel` and type graph without emitting or loading runtime artifacts. `analyzeInBatches()` processes a large package selection through bounded compiler programs with the same model shape, and `discoverPackages()` finds contributing packages without building a type-checker program; its optional `package.json` predicate skips a package before its sources or project config are read. Typert parses only the tsconfigs of packages it analyzes; validating every referenced tsconfig belongs to the preceding `tsc -b`.
+Static consumers call `WorkspaceAnalyzer` directly against the workspace's `tsconfig.host.json` and `tsconfig.client.json` aggregates, select a face and package subset, and read the resulting `FaceModel` and type graph without emitting or loading runtime artifacts. `analyzeInBatches()` processes a large package selection through bounded compiler programs with the same model shape, and `discoverPackages()` finds contributing packages without building a type-checker program; its optional `package.json` predicate skips a package before its sources or project config are read. Typert parses only the tsconfigs of packages it analyzes; validating every referenced tsconfig belongs to package compilation with `tsc -b`.
JSON, YAML, SVG, PNG, JPEG, and WebP resource exports contribute no TypeScript declarations and are excluded from the API model. Missing JavaScript or TypeScript export sources remain errors.
### Running generation inside a tsdown build
-`decoratorPlugin()` lowers standard TypeScript decorators without reading or writing metadata. The repository build uses it after source-only generation; consumers that generate metadata during bundling use `typertPlugin()`.
+The `./tsdown` subpath exports `decoratorPlugin()` and `typertPlugin()`. The `./tsdown` subpath exports `decoratorPlugin()` and `typertPlugin()`. `decoratorPlugin()` lowers standard TypeScript decorators without reading or writing metadata. The repository build uses it after source-only generation; consumers that generate metadata during bundling use `typertPlugin()`.
-The package's `./tsdown` subpath provides `typertPlugin()` for builds that generate metadata during bundling: it lowers standard decorators in TypeScript dependencies before bundling and emits the model-driven face artifacts at the package output root. A module-id filter on its `transform` hook keeps non-TypeScript modules out of JavaScript. The workspace root is the nearest ancestor of the bundle that contains both `tsconfig.host.json` and `packages/`, so a package's own face leaf config never qualifies. In `package` mode it generates synchronously in the bundled package's `writeBundle` and emits only that package.
+`typertPlugin()` serves builds that generate metadata during bundling: it lowers standard decorators in TypeScript dependencies before bundling and emits the model-driven face artifacts at the package output root. A module-id filter on its `transform` hook keeps non-TypeScript modules out of JavaScript. The workspace root is the nearest ancestor of the bundle that contains both `tsconfig.host.json` and `packages/`, so a package's own face leaf config never qualifies. In `package` mode it generates synchronously in the bundled package's `writeBundle` and emits only that package.
In `workspace` mode it emits every explicit contributor once without blocking bundling: the first package build's `buildStart` starts generation in a Worker Thread, and the first `writeBundle` awaits the result and writes every artifact while later bundles return immediately. An analysis error, a worker crash or early exit, or a malformed worker response fails that bundle, and therefore the tsdown pass, with a `TypertWorkerError` naming the workspace and cause; the worker is terminated before the failure is reported.
### Generating without a bundler
-`writeWorkspaceArtifacts()` and `writePackageArtifacts()` are the plugin's file writers: each artifact becomes `lib/typert..{js,d.ts}`, plus `lib/typert.remote-client.{js,d.ts,d.ts.map}` when it carries Remote methods, and a Host artifact without Remote methods removes stale Remote client files. Writing the result of `WorkspaceTypertGenerator.generateContributors()` through them leaves the same bytes as the `workspace`-mode plugin, and `findWorkspaceRoot()` applies the plugin's workspace-root rule. The repository's bundle-free pass, [`scripts/generate-typert.ts`](../../../scripts/generate-typert.ts), generates the Host face this way for type checks that need no bundle.
+`writeWorkspaceArtifacts()` and `writePackageArtifacts()` are the plugin's file writers: each artifact becomes `lib/typert..{js,d.ts}`, plus `lib/typert.remote-client.{js,d.ts,d.ts.map}` when it carries Remote methods, and a Host artifact without Remote methods removes stale Remote client files. Writing the result of `WorkspaceTypertGenerator.generateContributors()` through them leaves the same bytes as the `workspace`-mode plugin, and `findWorkspaceRoot()` applies the plugin's workspace-root rule. The repository's bundle-free pass, [`scripts/generate-typert.ts`](../../../scripts/generate-typert.ts), generates the Host face this way before package compilation for artifact and declaration-only builds.
With `rejectBuildOutputs: true`, analysis fails with a `TypertAnalysisError` when a face program loads a file from a workspace package's `lib/` directory, or cannot resolve an import of a workspace package, as on a tree whose packages are not yet compiled. The error names each such import specifier and the source file a `paths` alias should map it to, so a passing generation depends only on workspace source and can run before or beside package compilation. Leave it off for a face whose sources import generated outputs, such as a Client face importing `/remote`.
diff --git a/packages/typert/generator/README.zh.md b/packages/typert/generator/README.zh.md
index 51816bb944..536eb139af 100644
--- a/packages/typert/generator/README.zh.md
+++ b/packages/typert/generator/README.zh.md
@@ -60,21 +60,21 @@ factory 返回类实例,并保留 spec,直到 `descriptors` 或 `methods` ge
### 静态分析工作区
-静态消费方直接针对工作区的 `tsconfig.host.json` 与 `tsconfig.client.json` 聚合配置调用 `WorkspaceAnalyzer`,选择 face 与包子集,并在不生成或加载运行时产物的前提下读取生成的 `FaceModel` 与类型图。`analyzeInBatches()` 通过有界的编译器程序处理大批量包选择,模型形态保持一致;`discoverPackages()` 无需构建类型检查程序即可找出参与贡献的包,其可选的 `package.json` 谓词会在读取某个包的源码或项目配置之前就跳过该包。Typert 只解析它所分析的包的 tsconfig;校验每个被引用的 tsconfig 由之前运行的 `tsc -b` 负责。
+静态消费方直接针对工作区的 `tsconfig.host.json` 与 `tsconfig.client.json` 聚合配置调用 `WorkspaceAnalyzer`,选择 face 与包子集,并在不生成或加载运行时产物的前提下读取生成的 `FaceModel` 与类型图。`analyzeInBatches()` 通过有界的编译器程序处理大批量包选择,模型形态保持一致;`discoverPackages()` 无需构建类型检查程序即可找出参与贡献的包,其可选的 `package.json` 谓词会在读取某个包的源码或项目配置之前就跳过该包。Typert 只解析它所分析的包的 tsconfig;校验每个被引用的 tsconfig 由包编译中的 `tsc -b` 负责。
JSON、YAML、SVG、PNG、JPEG 和 WebP 资源导出不提供 TypeScript 声明,因此不进入 API 模型。JavaScript 或 TypeScript 导出源文件缺失时仍报错。
### 在 tsdown 构建中运行生成
-`decoratorPlugin()` 转换标准 TypeScript 装饰器,不读取或写入元数据。仓库构建在仅源码生成完成后使用它;需要在打包时生成元数据的消费方使用 `typertPlugin()`。
+`./tsdown` 子路径导出 `decoratorPlugin()` 与 `typertPlugin()`。`./tsdown` 子路径导出 `decoratorPlugin()` 与 `typertPlugin()`。`decoratorPlugin()` 转换标准 TypeScript 装饰器,不读取或写入元数据。仓库构建在仅源码生成完成后使用它;需要在打包时生成元数据的消费方使用 `typertPlugin()`。
-包的 `./tsdown` 子路径为需要在打包时生成元数据的构建提供 `typertPlugin()`:它在打包前转换 TypeScript 依赖中的标准装饰器,并在包输出根目录生成模型驱动的 face 产物。其 `transform` 钩子带有模块 id 过滤器,非 TypeScript 模块不会进入 JavaScript。工作区根目录是打包输出最近的、同时包含 `tsconfig.host.json` 与 `packages/` 的祖先目录,因此包自身的 face 叶子配置永远不会被当作根目录。`package` 模式在当前包的 `writeBundle` 中同步生成,并只生成该包。
+`typertPlugin()` 用于需要在打包时生成元数据的构建:它在打包前转换 TypeScript 依赖中的标准装饰器,并在包输出根目录生成模型驱动的 face 产物。其 `transform` 钩子带有模块 id 过滤器,非 TypeScript 模块不会进入 JavaScript。工作区根目录是打包输出最近的、同时包含 `tsconfig.host.json` 与 `packages/` 的祖先目录,因此包自身的 face 叶子配置永远不会被当作根目录。`package` 模式在当前包的 `writeBundle` 中同步生成,并只生成该包。
`workspace` 模式对每个显式贡献方各生成一次,且不阻塞打包:第一个包构建的 `buildStart` 在 Worker Thread 中启动生成,第一个 `writeBundle` 等待结果并写入全部产物,之后的打包立即返回。分析错误、worker 崩溃或提前退出、worker 响应格式错误,都会以指明工作区与原因的 `TypertWorkerError` 使该打包失败,从而使整个 tsdown 阶段失败;报告失败之前 worker 已被终止。
### 不经打包器生成
-`writeWorkspaceArtifacts()` 与 `writePackageArtifacts()` 是插件的写盘函数:每个产物写成 `lib/typert..{js,d.ts}`,带 Remote 方法时另写 `lib/typert.remote-client.{js,d.ts,d.ts.map}`,没有 Remote 方法的 Host 产物会删除残留的 Remote client 文件。把 `WorkspaceTypertGenerator.generateContributors()` 的结果交给它们写盘,得到的字节与 `workspace` 模式插件相同;`findWorkspaceRoot()` 采用插件的工作区根目录规则。仓库中不打包的生成 [`scripts/generate-typert.ts`](../../../scripts/generate-typert.ts) 就以这种方式为不需要 bundle 的类型检查生成 Host face。
+`writeWorkspaceArtifacts()` 与 `writePackageArtifacts()` 是插件的写盘函数:每个产物写成 `lib/typert..{js,d.ts}`,带 Remote 方法时另写 `lib/typert.remote-client.{js,d.ts,d.ts.map}`,没有 Remote 方法的 Host 产物会删除残留的 Remote client 文件。把 `WorkspaceTypertGenerator.generateContributors()` 的结果交给它们写盘,得到的字节与 `workspace` 模式插件相同;`findWorkspaceRoot()` 采用插件的工作区根目录规则。仓库中不打包的生成 [`scripts/generate-typert.ts`](../../../scripts/generate-typert.ts) 就以这种方式为产物构建与仅生成声明的构建在包编译之前生成 Host face。
设置 `rejectBuildOutputs: true` 后,若 face program 载入了工作区包 `lib/` 目录下的文件,或无法解析对工作区包的 import(例如包尚未编译的工作区),分析会以 `TypertAnalysisError` 失败。错误信息列出每个这样的 import 说明符,以及应由 `paths` 别名映射到的源文件;因此通过的生成只依赖工作区源码,可以在包编译之前或与之并行运行。若某个 face 的源码 import 生成产物(例如 Client face import `/remote`),则不要开启它。
diff --git a/packages/typert/generator/src/tsdown-plugin.ts b/packages/typert/generator/src/tsdown-plugin.ts
index 1a67dc5014..325b7e41b5 100644
--- a/packages/typert/generator/src/tsdown-plugin.ts
+++ b/packages/typert/generator/src/tsdown-plugin.ts
@@ -58,7 +58,7 @@ type GenerationOutcome =
| { readonly ok: false; readonly error: TypertWorkerError }
/**
- * Lower standard TypeScript decorators when metadata was generated before bundling.
+ * Lower standard TypeScript decorators without reading or writing metadata.
* @returns a rolldown-compatible transform without metadata generation or artifact writes.
*/
export function decoratorPlugin(): Pick {
diff --git a/packages/typert/generator/tests/tsdown-plugin.spec.ts b/packages/typert/generator/tests/tsdown-plugin.spec.ts
index 4d45aa4ff1..e62ea641a1 100644
--- a/packages/typert/generator/tests/tsdown-plugin.spec.ts
+++ b/packages/typert/generator/tests/tsdown-plugin.spec.ts
@@ -76,7 +76,7 @@ afterEach(() => {
})
describe('typertPlugin', () => {
- it.each([typertPlugin, decoratorPlugin])('lowers standard decorators in TypeScript source dependencies through %s', (factory) => {
+ it.each([['typertPlugin', typertPlugin], ['decoratorPlugin', decoratorPlugin]] as const)('lowers standard decorators in TypeScript source dependencies through %s', (_name, factory) => {
const { transform } = factory()
expect(transform.handler('export const value = 1\n', '/workspace/src/plain.ts')).toBeUndefined()
expect(transform.handler('@sealed\nexport class Example {}\n', '/workspace/src/example.ts?raw')?.code)
diff --git a/scripts/build.ts b/scripts/build.ts
index e316dc52f8..6dcd73590b 100644
--- a/scripts/build.ts
+++ b/scripts/build.ts
@@ -5,8 +5,8 @@
* `--scope check` type-checks only the Host and Client aggregate programs against the package
* declarations already on disk; the default runs both step sets as one dependency graph, so the
* aggregate checks overlap the remaining artifact steps. `--scope typecheck` (`pnpm run typecheck`)
- * type-checks every package project and both aggregate programs; beside Host compilation it writes
- * the Typert declarations that Client compilation imports, without bundling.
+ * generates Typert declarations before package compilation, then type-checks every package project
+ * and both aggregate programs without bundling.
*/
import { rmSync } from 'node:fs'
@@ -70,8 +70,9 @@ export function parseBuildArguments(args: readonly string[]): BuildArguments {
* Construct the build steps of one scope as a gate dependency graph.
* Artifact steps generate Typert from source before Host compilation, then bundle Host,
* Desktop, and Client artifacts before Web. Native output also precedes Host compilation.
- * The typecheck scope generates Typert before compiling Host and Client packages; it writes no bundle. In the complete and typecheck
- * scopes each aggregate check waits only for its own face's package compilation; alone, the
+ * The typecheck scope generates Typert before compiling Host and Client packages; it writes no
+ * bundle. In complete and typecheck scopes each aggregate check waits only for its own face's
+ * package compilation; alone, the
* checks have no predecessors and read whatever declarations already exist. The typecheck scope
* checks aggregates in build mode, which skips a program whose tsbuildinfo is current; the other
* scopes use project mode, which compiles no reference.
diff --git a/scripts/ci-workflow.spec.ts b/scripts/ci-workflow.spec.ts
index 6eb46de89b..1040b5f18b 100644
--- a/scripts/ci-workflow.spec.ts
+++ b/scripts/ci-workflow.spec.ts
@@ -86,7 +86,10 @@ describe('CI workflow', () => {
.toContainEqual(expect.objectContaining({ name: 'Build', run: command }))
}
const scripts = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8')) as { scripts: Record }
- expect(scripts.scripts['build:lib:host']).toContain('tsc -b tsconfig.host.json')
+ const hostSteps = scripts.scripts['build:lib:host']!.split(' && ')
+ expect(hostSteps[0]).toBe('tsx scripts/compile-referenced-projects.ts host-libraries')
+ expect(hostSteps).toContain('node --max-old-space-size=4096 ./node_modules/typescript/bin/tsc -p tsconfig.host.json')
+ expect(hostSteps.at(-1)).toBe('pnpm --filter @deepseek-ai/dsh-desktop run bundle')
expect(scripts.scripts['build:lib:client']).toContain('tsc -b tsconfig.client.json')
expect(scripts.scripts.typecheck).toBe('tsx scripts/build.ts --scope typecheck')
})
diff --git a/scripts/compile-referenced-projects.ts b/scripts/compile-referenced-projects.ts
index 3cd1e290cc..fe0751b2d3 100644
--- a/scripts/compile-referenced-projects.ts
+++ b/scripts/compile-referenced-projects.ts
@@ -146,7 +146,7 @@ function compilerStep(
// Typert reads workspace packages only from source (generate-typert.ts
// rejects build outputs), so it needs no compiled package. It runs under tsx's
// ESM hook rather than through `pnpm exec`, whose startup would lengthen the
-// critical path before Client compilation.
+// critical path before package compilation.
function typertStep(): LibraryStep {
return {
id: 'host-typert',
@@ -208,7 +208,7 @@ function runStep(root: string, step: LibraryStep): void {
if (result.status !== 0) throw stepFailure(step, result.status ?? result.signal)
}
-function runStepConcurrently(root: string, step: LibraryStep): Promise {
+function runStepAsync(root: string, step: LibraryStep): Promise {
return new Promise((settle, fail) => {
const child = spawn(step.command, step.args, { cwd: root, stdio: 'inherit' })
child.once('error', fail)
@@ -241,7 +241,7 @@ export function buildLibraryArtifacts(root: string, hostOnly: boolean, mode: Com
* @throws the first failed step's error; dependent compilation does not start after generation fails.
*/
export async function buildHostContracts(root: string): Promise {
- for (const step of contractSteps(root, 'host')) await runStepConcurrently(root, step)
+ for (const step of contractSteps(root, 'host')) await runStepAsync(root, step)
}
if (import.meta.main) {
diff --git a/scripts/generate-typert.ts b/scripts/generate-typert.ts
index d0f399bd91..53a954cab2 100644
--- a/scripts/generate-typert.ts
+++ b/scripts/generate-typert.ts
@@ -53,7 +53,7 @@ const STAGE_ACTIONS: Readonly> = {
}
// Host package compilation owns TypeScript diagnostics, as it does for the
-// tsdown plugin; rejecting build outputs lets this pass run beside it.
+// tsdown plugin; rejecting build outputs lets this pass run before package compilation.
const GENERATION_OPTIONS = { checkDiagnostics: false, rejectBuildOutputs: true } as const
/**