Merge branch 'worktree/pr344-retarget-stack' into worktree/pr345-retarget-stack

# Conflicts:
#	.agents/notes/implemented/process/2026-06-17-ts-build-config.md
This commit is contained in:
Tianyi Cui
2026-07-23 14:51:21 +08:00
229 changed files with 7544 additions and 4375 deletions
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-06-11-tsdown-over-dumble.md: 5d7593d689e2404bede0fa51b41c5d8eec397a03
2026-06-11-tsdown-over-dumble.zh.md: 080ee4c3fb9894049716fd5aec3ddd7b3d62d547
2026-06-11-tsdown-over-dumble.md: e8cdaeb1e3331ffb04de024acff5b0e6ca3e6366
2026-06-11-tsdown-over-dumble.zh.md: 97cbfe8cda6c2f2a5738c8221843735454b34183
@@ -17,7 +17,7 @@ Replace dumble with **tsdown** (rolldown-based, ~2.5M downloads/week, VoidZero-b
- Root `tsdown.config.ts` with `workspace: ['vendor/*', 'packages/*/*']` (explicit globs keep bundling to vendored Cordis and the TypeScript package tree; `workspace: true` would also discover example manifests and non-bundled workspace members).
- Shared shape: entry `lib/types/index.js`, `outDir: 'lib'`, ESM, `platform: node`, `target: es2024`, `fixedExtension: false` (keeps `.js` for `"type": "module"` packages), `dts: false` (tsc -b owns declarations), `clean: false` (lib/ also holds TSC's `lib/types` intermediate tree). The entry was originally `src/index.ts`; the [TSC-first build Agent Note](2026-06-17-ts-build-config.md) later moved tsdown to bundling TSC-emitted JS so TypeScript transform behavior comes from one compiler.
- Two per-package overrides in vendor/ (ours, like the regenerated tsconfigs; logged in vendor/README.md): schemastery (dual `.mjs`/`.cjs` via `outExtensions`), logger-console (two single-entry passes so the shared base class is inlined into each entry instead of a hash-named chunk, matching upstream's published shape).
- `scripts/build.ts` deleted; `pnpm run build` = `tsc -b tsconfig.build.json && tsdown`.
- `scripts/build.ts` deleted; `pnpm run build` = `tsc -b && tsdown` (the root solution owns the emit graph).
## Alternatives considered
@@ -17,7 +17,7 @@ Status: implemented
- 根目录 `tsdown.config.ts`,配置 `workspace: ['vendor/*', 'packages/*/*']`(显式 glob 将打包范围限定在 vendor 的 Cordis 与 TypeScript 包目录树内;`workspace: true` 还会发现示例 manifest 和不需要打包的 workspace 成员)。
- 共享形状:入口为 `lib/types/index.js``outDir: 'lib'`ESM`platform: node``target: es2024``fixedExtension: false`(为 `"type": "module"` 包保留 `.js`),`dts: false`(声明归 tsc -b 所有),`clean: false`lib/ 还保存 TSC 的 `lib/types` 中间树)。入口最初是 `src/index.ts`[TSC 优先构建 Agent Noteagent 决策记录)](2026-06-17-ts-build-config.md)随后将 tsdown 改为打包 TSC 输出的 JS,使 TypeScript 转换行为统一由一个编译器提供。
- vendor/ 中有两个按包覆盖的配置(属于我们自己的修改,与重新生成的 tsconfig 类似;记录在 vendor/README.md 中):schemastery(通过 `outExtensions` 输出双格式 `.mjs`/`.cjs`)、logger-console(两次单入口 pass,使共享基类被内联到每个入口而非生成哈希命名的 chunk,与上游发布形态一致)。
- `scripts/build.ts` 删除;`pnpm run build` = `tsc -b tsconfig.build.json && tsdown`
- `scripts/build.ts` 删除;`pnpm run build` = `tsc -b && tsdown`(根 solution 拥有 emit 图)
## 曾考虑的替代方案
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-06-17-ts-build-config.md: b82e5cae6dbfaae68d3a834162085e3f7b2c0c72
2026-06-17-ts-build-config.zh.md: e6cec899e521ee2c8325c7562329455141da8af5
2026-06-17-ts-build-config.md: 1275a635242ea887c941db9dc0554fd33acd4102
2026-06-17-ts-build-config.zh.md: 3b20d30011f84ae1a40182c23fa0743c608bb2ab
@@ -1,9 +1,11 @@
# Agent Note: TSC-first build and one tsconfig
# Agent Note: TSC-first build and one compiler ownership
Status: implemented
English | [中文](2026-06-17-ts-build-config.zh.md)
> Root project topology (which tsconfig owns which graph) has since moved to a solution root over two aggregate programs; see the [solution-root note](2026-07-22-tsconfig-solution-root-two-aggregates.md). The tsc-first pipeline decided here is unchanged.
## Problem
The current TypeScript build and typecheck setup had these issues:
@@ -30,29 +32,29 @@ In-package relative imports use explicit `.ts` specifiers.
`pnpm run build` is a two-stage build:
- Stage 1: `tsc -b tsconfig.build.json` emits per-module `.js`, declarations `.d.ts`, JS sourcemaps `.js.map`, and declaration sourcemaps `.d.ts.map` into each package's `lib/types`. This is the authoritative TypeScript compilation result. For publish we keep `.d.ts` / `.d.ts.map` and ignore `.js` / `.js.map`.
- The build project uses the project-reference graph that `tsc -b` compiles. For example, root `tsconfig.build.json` references package and vendor tsconfigs. It validates and emits package/vendor build results.
- Stage 1: `tsc -b` over the root solution emits per-module `.js`, declarations `.d.ts`, JS sourcemaps `.js.map`, and declaration sourcemaps `.d.ts.map` into each package's `lib/types`. This is the authoritative TypeScript compilation result. For publish we keep `.d.ts` / `.d.ts.map` and ignore `.js` / `.js.map`.
- The graph is the project-reference graph reachable from the root solution `tsconfig.json` through the two aggregates ([topology](2026-07-22-tsconfig-solution-root-two-aggregates.md)). It validates and emits package/vendor build results.
- Stage 2: a bundler reads the emitted JS under `lib/types` and writes the bundled runtime entry as `lib/index.js` or `lib/index.mjs` (follow current behavior). This stage is bundling only. It must not read TypeScript source or emit declarations.
`tsdown` is no longer the owner of TypeScript compilation or declaration output.
`pnpm run typecheck` runs build mode over the root `tsconfig.json`.
- The root `tsconfig.json` is the single development/typecheck project. It typechecks examples, tests, and scripts with `noEmit`, and validates package/vendor source through references.
- Referenced package/vendor projects keep the same emit behavior as build, so typecheck can refresh their `lib/types` outputs instead of using a separate no-emit graph. Project-specific strictness changes live in the owning `packages/*/*/tsconfig.json` or `vendor/*/tsconfig.json`.
- The root no-emit project disables `rewriteRelativeImportExtensions`; it emits nothing and includes tests that import helpers across project-reference boundaries. Package/vendor emit projects keep the rewrite enabled.
`pnpm run typecheck` runs the same `tsc -b` graph.
- The aggregates (`tsconfig.host.json`, `tsconfig.client.json`) typecheck examples, tests, and scripts with `noEmit`, and validate package/vendor source through references.
- Referenced package/vendor projects keep the same emit behavior as build, so typecheck refreshes their `lib/types` outputs instead of using a separate no-emit graph. Project-specific strictness changes live in the owning `packages/*/*/tsconfig.json` or `vendor/*/tsconfig.json`.
- The no-emit aggregates disable `rewriteRelativeImportExtensions`; they emit nothing and include tests that import helpers across project-reference boundaries. Package/vendor emit projects keep the rewrite enabled.
The command orchestration shape is:
```sh
pnpm run build:
tsc -b tsconfig.build.json
tsc -b
tsdown
pnpm run verify-node-next-types:
tsx scripts/verify-node-next-types.ts
pnpm run typecheck:
tsc -b tsconfig.json
tsc -b
```
`pnpm run demo:*` still runs `src` directly through tsx and root paths, without a compile step.
@@ -67,7 +69,7 @@ tsc -b tsconfig.json
Build responsibilities are clearer:
- Each module under `packages/<group>/<pkg>` and `vendor/*` has one local tsconfig for build, typecheck, and tools that run source directly, such as `tsx` and `vitest`.
- The `build` command uses `tsconfig.build.json`. `tsc -b` owns the publishable per-module `.js` and `.d.ts` output, and the bundler owns only `lib/index.*`.
- The `build` command drives the root solution graph. `tsc -b` owns the publishable per-module `.js` and `.d.ts` output, and the bundler owns only `lib/index.*`.
- `lib/types/*.d.ts` and `.d.ts.map` are the publish declaration output.
- `lib/types/*.d.ts` uses explicit `.ts` relative specifiers, which TypeScript's NodeNext/Node16 resolver maps to sibling `.d.ts` files.
- `lib/types/*.js` is only a bundler input and must not be used as a runtime entry or public import target.
@@ -1,9 +1,11 @@
# Agent Note: TSC 优先构建与单一 tsconfig
# Agent Note: TSC 优先构建与编译器单一归属
Status: implemented
[English](2026-06-17-ts-build-config.md) | 中文
> 根项目拓扑(即哪个 tsconfig 拥有哪张图)后来改为由一个 solution 根文件统辖两个聚合 program;见[solution 根文件 Agent Note](2026-07-22-tsconfig-solution-root-two-aggregates.md)。本文确定的 TSC 优先流水线保持不变。
## 问题
此前的 TypeScript 构建与类型检查配置存在以下问题:
@@ -30,29 +32,29 @@ Status: implemented
`pnpm run build` 是两阶段构建:
- 阶段 1`tsc -b tsconfig.build.json` 将逐模块的 `.js`、声明文件 `.d.ts`、JS sourcemap `.js.map` 和声明 sourcemap `.d.ts.map` 输出到各 package 的 `lib/types`。这是权威的 TypeScript 编译结果。发布时保留 `.d.ts` / `.d.ts.map`,忽略 `.js` / `.js.map`
- 构建项目使用 `tsc -b` 编译的 project-reference 图。例如,根 `tsconfig.build.json` 引用 package 和 vendor 的 tsconfig校验并输出 package/vendor 的构建结果。
- 阶段 1在根 solution 上执行 `tsc -b`将逐模块的 `.js`、声明文件 `.d.ts`、JS sourcemap `.js.map` 和声明 sourcemap `.d.ts.map` 输出到各 package 的 `lib/types`。这是权威的 TypeScript 编译结果。发布时保留 `.d.ts` / `.d.ts.map`,忽略 `.js` / `.js.map`
- 该图是从根 solution `tsconfig.json` 经两个聚合可达的 project-reference 图([拓扑](2026-07-22-tsconfig-solution-root-two-aggregates.md)),用于校验并输出 package/vendor 的构建结果。
- 阶段 2:打包器读取 `lib/types` 下输出的 JS,将打包后的运行时入口写为 `lib/index.js``lib/index.mjs`(沿用当前行为)。此阶段仅做打包,禁止读取 TypeScript 源码或输出声明文件。
`tsdown` 不再负责 TypeScript 编译或声明文件输出。
`pnpm run typecheck` 以 build 模式运行根 `tsconfig.json`
- `tsconfig.json` 是唯一的开发/类型检查项目。它`noEmit` 方式检查示例、测试和脚本,并通过 references 校验 package/vendor 源码。
- 被引用的 package/vendor 项目保持与 build 相同的输出行为,因此 typecheck 可以刷新它们的 `lib/types` 输出,而无需使用独立的 no-emit 图。项目特定的严格度变更放在各自的 `packages/*/*/tsconfig.json``vendor/*/tsconfig.json` 中。
- no-emit 项目禁用 `rewriteRelativeImportExtensions`;它不输出任何文件,且包含跨 project-reference 边界导入 helper 的测试。package/vendor 的 emit 项目保持重写开启。
`pnpm run typecheck` 运行同一张 `tsc -b`
- 两个聚合(`tsconfig.host.json``tsconfig.client.json``noEmit` 方式检查示例、测试和脚本,并通过 references 校验 package/vendor 源码。
- 被引用的 package/vendor 项目保持与 build 相同的输出行为,因此 typecheck 刷新它们的 `lib/types` 输出,而无需使用独立的 no-emit 图。项目特定的严格度变更放在各自的 `packages/*/*/tsconfig.json``vendor/*/tsconfig.json` 中。
- 两个 no-emit 聚合禁用 `rewriteRelativeImportExtensions`;它不输出任何文件,且包含跨 project-reference 边界导入 helper 的测试。package/vendor 的 emit 项目保持重写开启。
命令编排结构如下:
```sh
pnpm run build:
tsc -b tsconfig.build.json
tsc -b
tsdown
pnpm run verify-node-next-types:
tsx scripts/verify-node-next-types.ts
pnpm run typecheck:
tsc -b tsconfig.json
tsc -b
```
`pnpm run demo:*` 仍通过 tsx 和根路径直接运行 `src`,无需编译步骤。
@@ -67,7 +69,7 @@ tsc -b tsconfig.json
构建职责更加清晰:
- `packages/<group>/<pkg>``vendor/*` 下的每个模块有一份本地 tsconfig,同时服务于构建、类型检查和直接运行源码的工具(如 `tsx``vitest`)。
- `build` 命令使用 `tsconfig.build.json``tsc -b` 负责可发布的逐模块 `.js``.d.ts` 输出,打包器仅负责 `lib/index.*`
- `build` 命令驱动根 solution 图`tsc -b` 负责可发布的逐模块 `.js``.d.ts` 输出,打包器仅负责 `lib/index.*`
- `lib/types/*.d.ts``.d.ts.map` 是发布用的声明输出。
- `lib/types/*.d.ts` 使用显式 `.ts` 相对说明符,TypeScript 的 NodeNext/Node16 解析器会将其映射到同级的 `.d.ts` 文件。
- `lib/types/*.js` 仅作为打包器输入,禁止用作运行时入口或公开导入目标。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-19-web-styling-system.md: c80ef0d56a0e57b38fbb52bd07cbc0f69ec85912
2026-07-19-web-styling-system.zh.md: 59013a4a950196f3a065ac18415f9b5ed42f3ec3
2026-07-19-web-styling-system.md: b4d647924ab6ab172cd7a7e2531a10a2a7e62981
2026-07-19-web-styling-system.zh.md: 01064d4d52b3ed2b179a4795f5113b94480945bd
@@ -2,7 +2,7 @@
Status: implemented
> Token-system update (2026-07-22): the framework rulings here (CSS Modules + clsx, no component library, no tailwind, tokens-only colors) remain in force, but the two-layer `--bg-*`/`--text-*` token table and its `web-ui/src/style/global.css` home were replaced by the `--dsw-*` static+alias sheets in `packages/client/ui-theme/src/styles/` (dark = `body[data-ds-dark-theme]` override). Current authority: `missions/tasks/20260721-1520-web-plugin-rfc/architecture.md` §15.
> Token-system update (2026-07-22): the framework rulings here (CSS Modules + clsx, no component library, no tailwind, tokens-only colors) remain in force, but the two-layer `--bg-*`/`--text-*` token table and its `web-ui/src/style/global.css` home were replaced by the `--dsw-*` static+alias sheets in `packages/client/ui-theme/src/styles/` (dark = `body[data-ds-dark-theme]` override) — the sheets themselves are the token authority.
English | [中文](2026-07-19-web-styling-system.zh.md)
@@ -2,7 +2,7 @@
Status: implemented
> token 体系更新(2026-07-22):本文框架裁决(CSS Modules + clsx、无组件库、无 tailwind、组件只用 token)仍然生效,但两层 `--bg-*`/`--text-*` token 表及其宿主 `web-ui/src/style/global.css` 已被 `packages/client/ui-theme/src/styles/` 的 `--dsw-*` static+alias 双层表取代(暗色=`body[data-ds-dark-theme]` 覆写)。现行权威:`missions/tasks/20260721-1520-web-plugin-rfc/architecture.md` §15
> token 体系更新(2026-07-22):本文框架裁决(CSS Modules + clsx、无组件库、无 tailwind、组件只用 token)仍然生效,但两层 `--bg-*`/`--text-*` token 表及其宿主 `web-ui/src/style/global.css` 已被 `packages/client/ui-theme/src/styles/` 的 `--dsw-*` static+alias 双层表取代(暗色=`body[data-ds-dark-theme]` 覆写)——样式表本身即 token 权威
[English](2026-07-19-web-styling-system.md) | 中文
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-20-gui-testing-system.md: db1b47566f5aa089ffcb10d130ecde1851b93112
2026-07-20-gui-testing-system.zh.md: 691c6baf50c1025a09461effd28ac0f1650fb933
2026-07-20-gui-testing-system.md: fdd5c7f9d33f9a90ea4afe145265be5fe93e0fc2
2026-07-20-gui-testing-system.zh.md: 0ae08133742711b87e9155ddc6f3104b757c1b55
@@ -2,7 +2,7 @@
Status: implemented
> Path update (2026-07-22, plugin-system refactor): the three-tier philosophy and golden-path method here remain current; homes moved — object-layer specs now live in `packages/client/runtime/tests/` (was web-runtime), wire specs in `packages/client/connection/tests/`, and the `web-ui` coverage exclusion is gone with the package (component specs are per-plugin jsdom suites under each `packages/client/*/tests/`). Current test-system authority: `missions/tasks/20260721-1520-web-plugin-rfc/architecture.md` §18.
> Path update (2026-07-22, plugin-system refactor): the three-tier philosophy and golden-path method here remain current; homes moved — object-layer specs now live in `packages/client/runtime/tests/` (was web-runtime), wire specs in `packages/client/connection/tests/`, and the `web-ui` coverage exclusion is gone with the package (component specs are per-plugin jsdom suites under each `packages/client/*/tests/`). Component-spec shape follows the [slot system standard](../architecture/2026-07-22-slot-type-chain-implementation.md): feed props directly — the store share comes from `createXXXStore().create()` (the real engine, the sanctioned zero-machinery path), framework hooks are plain stubs; no render machinery, no provider mounting. Slot ownership/registry semantics are tier-2 territory (`runtime` + `ui-slots` suites), not component specs.
English | [中文](2026-07-20-gui-testing-system.zh.md)
@@ -2,7 +2,7 @@
Status: implemented
> 路径更新(2026-07-22,插件体系重构):本文三层理念与金路径方法仍为现行;家搬了——对象层 spec 现居 `packages/client/runtime/tests/`(原 web-runtime)、wire spec 现居 `packages/client/connection/tests/``web-ui` 覆盖豁免随包消亡(组件 spec 为各 `packages/client/*/tests/` 的 jsdom 套件)。测试体系现行权威:`missions/tasks/20260721-1520-web-plugin-rfc/architecture.md` §18
> 路径更新(2026-07-22,插件体系重构):本文三层理念与金路径方法仍为现行;家搬了——对象层 spec 现居 `packages/client/runtime/tests/`(原 web-runtime)、wire spec 现居 `packages/client/connection/tests/``web-ui` 覆盖豁免随包消亡(组件 spec 为各 `packages/client/*/tests/` 的 jsdom 套件)。组件 spec 形态遵循 [slot 体系标准](../architecture/2026-07-22-slot-type-chain-implementation.md)props 直喂——store 份额来自 `createXXXStore().create()`(真引擎,获认可的零机械路径),框架 hook 用普通桩;无渲染机械、不挂 provider。坑位归属/注册表语义归 2 层地界(`runtime` + `ui-slots` 套件),不归组件 spec
[English](2026-07-20-gui-testing-system.md) | 中文
@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-22-cordis-tutorial-docs.md: 45abb8f524218ce0b2678606ae62c7bf2dbca00b
2026-07-22-cordis-tutorial-docs.zh.md: cd1a62e2a6e7f2e28bc32109dd67746840f8b8b7
@@ -0,0 +1,32 @@
# Agent Note: Tutorial-style Cordis docs under docs/cordis-tutorial
Status: implemented
English | [中文](2026-07-22-cordis-tutorial-docs.zh.md)
## Problem
The repo documents Cordis at two levels: the condensed [cordis-primer](../../../../docs/cordis-primer.md) states the concepts, and the `docs/user/develop/` pages teach harness plugin authoring against harness services. Neither serves a developer meeting Cordis itself for the first time: the primer assumes the reader already writes plugins, and the develop pages jump straight to `defineTool` without showing how contexts, fibers, services, and dispatch actually behave. There was no path where a reader runs bare Cordis, watches a fiber go PENDING, or sees a waterfall veto happen.
## Decision
`docs/cordis-tutorial/` holds a seven-chapter hands-on tutorial (first plugin → lifecycle/effects → services → events → config → composition/HMR → harness tool). Its properties, in decreasing order of load-bearing-ness:
- **Every transcript is real.** Each chapter's files run in the gitignored `tmp/cordis-tutorial/` scratch directory via `node --import tsx ../../vendor/cordis/bin.js`, and the shown output is what those commands print. The chapter that uses harness packages (`@deepseek-ai/dsh-tools` and `@deepseek-ai/dsh-llm`) runs keylessly.
- **dsh-flavored, not pure Cordis**: later chapters use real harness services and events (`ctx.tools`, `tools/result`) so the tutorial lands the reader inside this repo's actual composition model, per the requesting user's choice.
- **English-only, published to both website locales** through `mirroredPages()` in [website/docs.ts](../../../../website/docs.ts) under a `Cordis 教程` / `Cordis tutorial` section of the develop sidebar — the same pattern as the reference pages, so a Chinese pair can ratchet in later without route changes.
- Code fences compile under `doc-typecheck` except the two fences that import scratch-relative files (`./stats.ts`) or intentionally throw, which carry `ignore-check`.
## Alternatives considered
**Under `docs/user/develop/` as paired product docs.** That tier requires en+zh+i18n records in the same PR, roughly doubling the change and coupling every future tutorial edit to a translation. Rejected for the first landing; the mirrored projection keeps the same public visibility.
**Pure-Cordis tutorial with no harness packages.** Cleaner as framework documentation, but the audience is agent developers extending this harness; ending at `ctx.tools.execute` and `tools/result` teaches the composition they will actually work in. The user chose this explicitly.
**Extending the primer instead of a new directory.** The primer is a 600-word budgeted concept reference; a multi-chapter walkthrough inside it would break its tier's job (and its budget) rather than complement it.
## Consequences
- A runnable introduction to Cordis exercises the loader, fiber states, effects, service injection, all five dispatch-mode contracts, Schemastery validation, and HMR. It demonstrates PENDING dependencies and validation failure; it explains the loader's logged unresolved-entry failure because that boot-time log may not reach a console exporter.
- The tutorial's transcripts pin behavior informally but are not snapshot-gated; if loader or HMR behavior changes, the transcripts drift until a human replays the chapters. The compile gate covers only the code fences.
- The chapters name concrete harness APIs (`ctx.tools.execute`, `CallId`, `tools/result`); renames must update the tutorial like any other doc reference (`verify-md-links` catches file moves, not API prose).
@@ -0,0 +1,32 @@
# Agent Note: `docs/cordis-tutorial` 下的 Cordis 实操教程文档
Status: implemented
[English](2026-07-22-cordis-tutorial-docs.md) | 中文
## 问题
本仓库从两个层面介绍 Cordis:精简的 [cordis-primer](../../../../docs/cordis-primer.md) 阐述概念,`docs/user/develop/` 下的页面则讲解如何基于 harness 服务编写 harness 插件。但二者都不适合初次接触 Cordis 的开发者:primer 假定读者已经会编写插件,开发页面则直接从 `defineTool` 讲起,没有展示上下文、fiber、服务和 dispatch 的实际行为。此前没有一条学习路径让读者运行原生 Cordis、观察 fiber 进入 PENDING 状态,或看到 waterfall(瀑布式事件)否决实际发生。
## 决策
`docs/cordis-tutorial/` 包含一套七章实操教程(第一个插件 → 生命周期与 effect → 服务 → 事件 → 配置 → 组合与 HMR(热模块替换)→ harness 工具)。以下是教程的特性,按重要性从高到低排列:
- **每段 transcript(文本记录)都真实可复现。** 每章文件都通过 `node --import tsx ../../vendor/cordis/bin.js` 在 git 忽略的 `tmp/cordis-tutorial/` 临时目录中运行,展示的输出就是这些命令实际打印的内容。使用 harness 包(package)(`@deepseek-ai/dsh-tools``@deepseek-ai/dsh-llm`)的章节无需密钥即可运行。
- **采用 dsh 风格,而非纯 Cordis**:后续章节使用真实的 harness 服务和事件(`ctx.tools``tools/result`),使读者最终进入本仓库实际采用的组合模型,这遵循了提出请求的用户所作的选择。
- **仅提供英文版,但发布到网站的两个语言区域**:通过 [website/docs.ts](../../../../website/docs.ts) 中的 `mirroredPages()`,发布到开发侧边栏的 `Cordis 教程` / `Cordis tutorial` 分区。该方式与参考页面采用的模式相同,因此日后可以逐步纳入中文配对,而无需更改路由。
- 除两个围栏代码块外,其余代码块均通过 `doc-typecheck` 编译;这两个例外分别导入临时目录中的相对路径文件(`./stats.ts`)或有意抛出异常,因此标有 `ignore-check`
## 考虑过的替代方案
**作为双语产品文档放在 `docs/user/develop/` 下。** 该层级要求在同一个 PR(Pull Request)中同时提供英文、中文和 i18n 记录,这会使变更量大致翻倍,并要求未来每次修改教程时都同步翻译。首次落地不采用此方案;镜像投影仍可保持同等的公开可见性。
**不使用任何 harness 包的纯 Cordis 教程。** 作为框架文档会更简洁,但目标读者是扩展此 harness 的 agent(智能体)开发者;以 `ctx.tools.execute``tools/result` 收尾,能讲清他们实际使用的组合方式。用户明确选择了此方案。
**扩充 primer,而非新建目录。** primer 是一份预算上限为 600 词的精简概念参考;在其中加入多章演练会破坏该文档层级的职责及其篇幅预算,而非形成补充。
## 结果
- 现在有了一份可运行的 Cordis 入门教程,涵盖 loader、fiber 状态、effect、服务注入、全部五种 dispatch 模式的契约、Schemastery 校验和 HMR。教程实际展示了依赖处于 PENDING 状态和配置校验失败;对于 loader 记录的配置项解析失败,教程只作说明,因为启动阶段的日志可能无法到达控制台导出器。
- 教程中的 transcript 以非正式方式固定了行为,但没有快照门禁;如果 loader 或 HMR 的行为发生变化,transcript 会逐渐偏离实际结果,直到有人重新运行各章。编译门禁只覆盖围栏代码块。
- 各章写明了具体的 harness API`ctx.tools.execute``CallId``tools/result`);这些 API 重命名时,必须像更新其他文档引用一样同步修改教程(`verify-md-links` 能发现文件移动,但无法发现 API 文字引用变化)。
@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-22-tsconfig-solution-root-two-aggregates.md: 19c229693b98ff3825caf935fa647ab85aff0f56
2026-07-22-tsconfig-solution-root-two-aggregates.zh.md: becc43de1ef2f6a53b0f6c2285eb64d9b42604f1
@@ -0,0 +1,45 @@
# Agent Note: Solution root over two aggregate programs
Status: implemented
English | [中文](2026-07-22-tsconfig-solution-root-two-aggregates.zh.md)
## Problem
The GUI split introduced a second aggregate program (`tsconfig.client.json`, [layering RFC](../architecture/2026-07-19-gui-layering-and-rpc-protocol.md)) while the root `tsconfig.json` kept doubling as the host aggregate, and `tsconfig.build.json` remained a third, hand-maintained full emit graph. That triple bookkeeping produced four concrete asymmetries:
- The typecheck and build references lists drifted apart (`packages/goal/command-goal` was in the typecheck graph but missing from the build graph).
- The lefthook pre-push hook ran `tsc -b tsconfig.json` only, so client-side type breakage passed the local checkpoint and surfaced in CI.
- tsserver discovers only configs named `tsconfig.json`, so client test files sat on no discoverable config chain and fell back to inferred projects (no paths, wrong lib/jsx).
- The vitest configs pointed at three different resolution sources (`tsconfig.vitest.json`, the root config, and one hand-written alias).
## Decision
One solution root, two check units, one shared base pair, no separate build or vitest config:
| File | Role | Forms a program? |
|---|---|---|
| `tsconfig.json` | Solution root: `extends` base, `files: []`, two references; the whole-repo `tsc -b tsconfig.json` graph, the tsserver entry, and the nearest config for get-tsconfig consumers (tsx running `examples/`, `scripts/`, doc fences) whose bare workspace imports resolve through the inherited `paths` | No |
| `tsconfig.base.json` | Shared compilerOptions and the source `paths` map; doubles as the resolution facade for vite-tsconfig-paths (no `include`, so it applies to every importer) | No |
| `tsconfig.base.client.json` | Browser compiler shape (`jsx: react-jsx`, DOM libs, `types: []`) shared by the client aggregate and every `packages/client/*` package | No |
| `tsconfig.host.json` | The former root aggregate, moved verbatim: host packages, examples, tests, scripts, website; excludes `packages/client` | Yes |
| `tsconfig.client.json` | Client packages and their tests; extends `tsconfig.base.client.json` | Yes |
The load-bearing principle: **cordis `Context` declaration-merge collisions exist only inside a `ts.Program`, never in module resolution.** A solution file forms no program, so referencing both aggregates from one root cannot collide the merges; vite-tsconfig-paths reads only `paths` and `include` and discards types, so one facade may span both sides. The only way to explode is to flatten both sides into a single program — hence two derived disciplines: `tsconfig.base.json` never gains `include`/`files` (it would leak into every extending package and narrow the facade), and every repo-wide `ts.Program` consumer (`scripts/ts-project.ts`, doc-typecheck standalone mode) seeds `tsconfig.host.json` or `tsconfig.client.json` explicitly, never the root solution. Program-backed generators and semantic gates intentionally stay host-only; the client side gets program-backed gates only when a real need arrives.
Commands collapse to one graph and keep the config name explicit: `typecheck` = `tsc -b tsconfig.json`, `build` = `tsc -b tsconfig.json && tsdown`, lefthook pre-push stays `tsc -b tsconfig.json --pretty false` unchanged (the same line now covers both sides through the solution). `tsconfig.build.json` and `tsconfig.vitest.json` are deleted; all vitest configs point vite-tsconfig-paths at `tsconfig.base.json`.
The solution root `extends` the base deliberately: `examples/` and `scripts/` have no nearer tsconfig, so tsx (get-tsconfig) resolves their workspace imports through the root file. `extends` restores the `paths` map there while `files: []` keeps the file program-less. Their *type checking* is unaffected by this: examples, scripts, and website files are included by the host aggregate.
## Alternatives considered
- **Rename `tsconfig.build.json` to `tsconfig.host.json`** — rejected: the build graph was the full emit graph including all client packages, not a host graph; the name that fits the former root aggregate is `tsconfig.host.json`, and the build graph itself is subsumed by the solution.
- **Point vitest at the root solution** — rejected: a solution has neither `paths` nor `include`, so resolution would become a function of how far the plugin walks references, and the client aggregate's include (tests only, no src) would leave transitive src→src imports unmapped, falling through to `exports` and loading a second copy of module singletons.
- **Keep `tsconfig.vitest.json` as a dedicated facade** — retained only as the fallback if vite-tsconfig-paths mishandles an include-less config; the base file already carries the paths map, and an include-less config applies everywhere, which is strictly wider than the facade's hand-kept include list.
## Consequences
- `docs/development.md#typescript-project-layout` is the authoritative description; root `AGENTS.md` carries the two disciplines as conventions.
- The [ts-build-config note](2026-06-17-ts-build-config.md) keeps ownership of the tsc-first build pipeline (tsc emits, tsdown bundles, `.ts` specifiers with `rewriteRelativeImportExtensions`); its former "one root typecheck project" shape is superseded by this note.
- Adding a package registers it in exactly one aggregate's references (host packages in `tsconfig.host.json`, client packages in `tsconfig.client.json`); the build graph needs no separate registration.
- The build gate depends on the typecheck gate: both now drive the same `tsc -b` graph, so running them concurrently would race the same `.tsbuildinfo` files.
@@ -0,0 +1,45 @@
# Agent Note: 以 solution 根文件统辖两个聚合 program
Status: implemented
[English](2026-07-22-tsconfig-solution-root-two-aggregates.md) | 中文
## 问题
GUI 拆分引入了第二个聚合 program(`tsconfig.client.json`,见[分层 RFC](../architecture/2026-07-19-gui-layering-and-rpc-protocol.md)),根 `tsconfig.json` 则继续兼任宿主侧聚合,`tsconfig.build.json` 还是第三份手工维护的全量 emit 图。三处账本并行,造成四个具体的不对称:
- 类型检查与构建的 references 列表逐渐脱节(`packages/goal/command-goal` 在类型检查图里,构建图里却没有)。
- lefthook 的 pre-push 钩子只运行 `tsc -b tsconfig.json`,客户端侧的类型破坏因此通过本地检查点,直到 CI 才暴露。
- tsserver 只发现名为 `tsconfig.json` 的配置,客户端测试文件不在任何可发现的配置链上,回落到推断项目(inferred project),既没有 pathslib/jsx 也不对。
- 各 vitest 配置指向三个不同的解析来源(`tsconfig.vitest.json`、根配置,外加一处手写别名)。
## 决策
一个 solution 根文件,两个检查单元,一对共享 base,不再单设 build 或 vitest 配置:
| 文件 | 角色 | 是否构成 program |
|---|---|---|
| `tsconfig.json` | solution 根文件:`extends` base、`files: []`、两条 references;同时是全仓 `tsc -b tsconfig.json` 图、tsserver 入口,以及 get-tsconfig 消费方(tsx 运行 `examples/``scripts/`、文档围栏代码块)就近命中的配置,其裸 workspace 导入经继承来的 `paths` 解析 | 否 |
| `tsconfig.base.json` | 共享 compilerOptions 与源码 `paths` 映射;兼任 vite-tsconfig-paths 的解析门面(不含 `include`,因此对每个导入方都生效) | 否 |
| `tsconfig.base.client.json` | 浏览器侧编译形态(`jsx: react-jsx`、DOM lib、`types: []`),由客户端聚合与每个 `packages/client/*` 包共享 | 否 |
| `tsconfig.host.json` | 原根聚合原样迁入:宿主各包、examples、测试、scripts、website;排除 `packages/client` | 是 |
| `tsconfig.client.json` | 客户端各包及其测试;通过 `extends` 继承 `tsconfig.base.client.json` | 是 |
整个方案立足的原则:**cordis `Context` 的声明合并冲突只存在于同一个 `ts.Program` 内部,从不发生在模块解析中。** solution 文件不构成 program,因此从一个根文件同时引用两个聚合不会让两侧的声明合并相撞;vite-tsconfig-paths 只读取 `paths``include`、丢弃全部类型信息,因此一个门面可以横跨两侧。唯一会爆炸的做法是把两侧压平进同一个 program,由此推出两条派生纪律:`tsconfig.base.json` 永远不得添加 `include`/`files`(否则会泄漏进每个继承它的包,并收窄门面范围);每个全仓级 `ts.Program` 消费方(`scripts/ts-project.ts`、doc-typecheck 独立模式)都显式以 `tsconfig.host.json``tsconfig.client.json` 为种子,绝不使用根 solution。基于 program 的生成器与语义门禁有意只留在宿主侧;客户端侧只有在真实需求出现时才引入基于 program 的门禁。
各命令收敛到一张图,且显式写出配置名:`typecheck` = `tsc -b tsconfig.json``build` = `tsc -b tsconfig.json && tsdown`lefthook pre-push 保持 `tsc -b tsconfig.json --pretty false` 不变(经由 solution,这同一行命令现已覆盖两侧)。`tsconfig.build.json``tsconfig.vitest.json` 删除;所有 vitest 配置都把 vite-tsconfig-paths 指向 `tsconfig.base.json`
solution 根文件刻意 `extends` base`examples/``scripts/` 没有更近的 tsconfigtsxget-tsconfig)通过根文件解析它们的 workspace 导入。`extends``paths` 映射带回根文件,`files: []` 则让它始终不构成 program。这不影响两者的*类型检查*examples、scripts 与 website 的文件由宿主聚合纳入。
## 考虑过的替代方案
- **把 `tsconfig.build.json` 改名为 `tsconfig.host.json`**——不予采纳:构建图是包含全部客户端包的全量 emit 图,不是宿主图;`tsconfig.host.json` 这个名字对应的是原根聚合,而构建图本身已被 solution 吸收。
- **让 vitest 指向根 solution**——不予采纳:solution 既没有 `paths` 也没有 `include`,解析结果将取决于插件沿 references 走多远;且客户端聚合的 include 只收测试、不收 src,传递的 src→src 导入会失去映射,回落到 `exports`,加载出模块单例的第二份副本。
- **保留 `tsconfig.vitest.json` 作为专用门面**——仅保留为后备方案:若 vite-tsconfig-paths 处理不了无 include 的配置再启用;base 文件已经携带 paths 映射,而无 include 的配置处处生效,严格宽于该门面手工维护的 include 列表。
## 后果
- `docs/development.md#typescript-project-layout` 是权威描述;根 `AGENTS.md` 以约定形式收录上述两条纪律。
- [ts-build-config Agent Note](2026-06-17-ts-build-config.md) 继续拥有 tsc 先行的构建流水线(tsc 负责输出,tsdown 负责打包,`.ts` 说明符配合 `rewriteRelativeImportExtensions`);其原先「单一根类型检查项目」的形态由本文取代。
- 新增一个包只登记进恰好一个聚合的 references(宿主包进 `tsconfig.host.json`,客户端包进 `tsconfig.client.json`);构建图无需另行登记。
- 构建门禁依赖类型检查门禁:两者现在驱动同一张 `tsc -b` 图,并发运行会在同一批 `.tsbuildinfo` 文件上竞态。