From 645fcf57137a82000f115aaabb7500b8682d6391 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Thu, 30 Jul 2026 00:53:05 +0800 Subject: [PATCH] docs(agent-note): propose packages/ regrouping by measured clustering RFC settling the target group map: fold the session family into one session/ group, dissolve ui/ into interaction/ (+tui), boot/, and the scaffold/ developer-tooling family (absorbing jsonrpc as server), merge timeout/ into guard/, and rename cordis/ to self-modification/. Zero npm renames now; five FIXME-tracked intended renames. Squash of the reviewed RFC iterations (turtle1999 map corrections applied). --- .../2026-07-29-package-regrouping.i18n.yaml | 6 + .../2026-07-29-package-regrouping.md | 106 ++++++++++++++++++ .../2026-07-29-package-regrouping.zh.md | 106 ++++++++++++++++++ 3 files changed, 218 insertions(+) create mode 100644 .agents/notes/proposed/architecture/2026-07-29-package-regrouping.i18n.yaml create mode 100644 .agents/notes/proposed/architecture/2026-07-29-package-regrouping.md create mode 100644 .agents/notes/proposed/architecture/2026-07-29-package-regrouping.zh.md diff --git a/.agents/notes/proposed/architecture/2026-07-29-package-regrouping.i18n.yaml b/.agents/notes/proposed/architecture/2026-07-29-package-regrouping.i18n.yaml new file mode 100644 index 0000000000..cdd772386f --- /dev/null +++ b/.agents/notes/proposed/architecture/2026-07-29-package-regrouping.i18n.yaml @@ -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 .agents/notes/proposed/architecture/2026-07-29-package-regrouping.md +2026-07-29-package-regrouping.md: 9a56f9f5f1e0aef7d1f9efe60168ec1b0f41cce8 +2026-07-29-package-regrouping.zh.md: c49c5a1daffaff599f70f36b62e4dd3b5de3e5cf diff --git a/.agents/notes/proposed/architecture/2026-07-29-package-regrouping.md b/.agents/notes/proposed/architecture/2026-07-29-package-regrouping.md new file mode 100644 index 0000000000..9a56f9f5f1 --- /dev/null +++ b/.agents/notes/proposed/architecture/2026-07-29-package-regrouping.md @@ -0,0 +1,106 @@ +# Agent Note: Regroup packages/ by measured clustering + +Status: proposed + +English | [中文](2026-07-29-package-regrouping.zh.md) + +## Problem + +The two-level `packages//` hierarchy ([original decision](../../archived/architecture/2026-06-20-package-hierarchy.md)) has drifted since June: 167 packages now sit in 40 groups, and several group boundaries no longer match how the packages actually cluster. + +- `ui/` mixes four unrelated planes: the human terminal channel (`tui`), the SDK's JSON-RPC server half (`jsonrpc`, whose peer dependency on `dsh-sdk-protocol` binds it to the `sdk/` stack), the human-interaction seams (`user-interaction`, `user-approval`, `permission`, `tool-ask-user`, `commands`), and channel-neutral boot glue (`app-boot`). Its own README narrates the mixture instead of stating a role. +- The session family is fragmented across five groups — `session-persistence/`, `session-projection/`, `session-query/`, `session-title/`, and `telemetry/` — although the measured dependency edges tie them together (query → persistence, title → projection, projection → persistence; see [docs/module-graph.md](../../../../docs/module-graph.md)). +- Two group names collide with unrelated packages: `telemetry/` (session reporting) vs `sdk/telemetry` = `dsh-telemetry` (launcher-side SDK telemetry), and `timeout/` (a tool-call guard) vs `util/timeout` (the generic promise utility). +- `cordis/` names its group after the framework every package is built on, so the name discriminates nothing; its single package `tool-cordis` is the runtime self-modification toolset. +- `sdk/` folder names are inconsistent: `sdk/sdk-client` and `sdk/sdk-protocol` repeat the group name while `sdk/telemetry`, `sdk/helper`, and `sdk/scripts` do not. + +The north star for the regrouping: **closely clustered packages share a group.** A cluster is measured — peer-dependency edges and co-change — not thematic. An isolated seam family may stand alone as a small group; the failure mode to avoid is the grab-bag whose name describes no single role. + +## Proposal + +Recompose six groups; every other group keeps today's boundary and contents (the dependency analysis confirmed the capability families — `bash/`, `pty/`, `code-runtime/`, `sandbox/`, `subprocess/`, `fs/`, `lsp/`, `web/`, `skill/`, and the rest — were already drawn correctly). npm package names do not change in this reorganization; the folder tree carries the whole change. + +| Group | Members (folder names) | From | +|---|---|---| +| `session/` | session-persistence, session-persistence-jsonl, session-persistence-sqlite, session-checkpoint-policy, session-projection, session-projection-cache, session-title, session-title-llm, session-title-first-message-llm, session-title-all-messages-llm, session-telemetry, session-telemetry-otel | `session-persistence/` + `session-projection/` + `session-title/` + `telemetry/` | +| `interaction/` | user-interaction, user-approval, permission, tool-ask-user, commands, tui | `ui/` | +| `boot/` | app-boot | `ui/` | +| `scaffold/` | helper, scripts, create-sdk, protocol, client, server, telemetry | `sdk/` + `ui/jsonrpc` | +| `guard/` | repeat-tool-guard, timeout-policy | `guard/` + `timeout/` | +| `self-modification/` | tool-cordis | `cordis/` | + +- **`session/`** is the durable session data plane: the persistence seam with its backends and checkpoint policy, the projection fold that serves whole values from that log, log-backed titles, and OTel reporting. The title fold is itself load-bearing for the read side (`session-query` peer-depends on `dsh-session-title`), so titles belong with the data plane, not in a derived-services annex. The plain name is deliberate (review: prefer names a human would say); the nearby `core/session` package remains the live in-memory service, while this group is the durable family around it. `session-query/` stays a standalone group — the read/tool surface has its own model tools and SQLite FTS backend and is consumed independently of persistence internals. Absorbing `telemetry/` ends the group-name collision with `dsh-telemetry`. +- **`interaction/`** is the human-collaboration plane plus the terminal channel that answers it: the question/approval seams, the permission preset, the model-facing `ask_user_question` tool, the human-command registry (`plan-mode` and `command-goal` already consume `commands` together with the interaction seams), and `tui` — the interactive channel is the plane's richest provider and consumer (peer edges to `commands` and `user-interaction`), and a one-package `tui/` group would spend a top-level name on one plugin. +- **`boot/`** is a role-complete single-package group: the shared bin boot glue that belongs to no channel and no assembly (consumed by `apps/cli`, the `scaffold/` launcher, and the `examples/` demo bins). +- **`scaffold/`** is the developer-tooling family: project helper, launcher, initializer, wire protocol with both ends (`server` is today's `ui/jsonrpc`), and launcher telemetry. Renamed from `sdk/` in review: the whole `packages/` tree *is* the SDK, so a group named `sdk/` inside it says nothing; `scaffold/` names the create/launch/drive-a-project role. Folders drop the legacy `sdk-` prefix (`protocol`, `client`, `server`), matching the `client/`/`host/` role-named folder style. +- **`guard/`** keeps its documented role, loop-hygiene guards, and gains the tool-call timeout enforcer, dissolving the one-package `timeout/` group whose name collided with `util/timeout`. +- **`self-modification/`** names the role `cordis/` obscured: the toolset with which the agent inspects and mounts plugins in its own live runtime, and the landing zone for future self-modification packages. + +40 groups become 37; the win is clustering correctness and truthful names, not count. + +## Deferred renames (FIXME markers) + +Five npm names should eventually change, but renaming inside the reorganization would turn a pure-move PR into an import-churn PR. Instead, the implementation adds a `FIXME` to each affected package's module JSDoc naming the intended new name. `FIXME` blocks a tagged release ([marker semantics](../../../../docs/development.md)), which is the wanted forcing function: these renames are only free while nothing external consumes the packages. + +| Current npm name | Intended name | Why | +|---|---|---| +| `@deepseek-ai/dsh-jsonrpc` | `@deepseek-ai/dsh-sdk-server` | Names the wire encoding, not the role; it is the server half of the SDK protocol | +| `@deepseek-ai/dsh-telemetry` | `@deepseek-ai/dsh-sdk-telemetry` | Collides with the `dsh-session-telemetry` family; it is launcher-side SDK telemetry | +| `@deepseek-ai/dsh-helper` | `@deepseek-ai/dsh-sdk-helper` | Indefensibly generic as a published name | +| `@deepseek-ai/dsh-scripts` | `@deepseek-ai/dsh-sdk-scripts` | Same | +| `@deepseek-ai/dsh-timeout-policy` | `@deepseek-ai/dsh-timeout-guard` | Suggestion, not settled: aligns the name with its `guard/` home; decide at resolution time | + +The first four are settled intent; resolving them converges the SDK wire stack's npm names on `dsh-sdk-*` (the npm prefix names the product stack; the `scaffold/` folder names the role). `@deepseek-ai/create-sdk` keeps its documented npm-initializer exception. + +## Implementation + +One mechanical PR, landed in a quiet window: a single conflict event for the many open PRs, an atomic documentation update, and pure-move reviewability (`git mv` + path fixes, rename detection intact). If review prefers, the same content splits into a three-PR stack (the session fold; `ui/` dissolution + `scaffold/`; `self-modification/` + `guard/` merge), at the cost of three rebase events for everyone else. + +A group move touches: the moved package's `tsconfig.json` relative `references` and every dependent's entry (including the `apps/cli` project references), the tsconfig aggregate and path maps, group READMEs (five new bilingual triplets, deletions for dissolved groups, the [packages/README.md](../../../../packages/README.md) hierarchy table, the root `AGENTS.md` layout map), regenerated artifacts (`docs/module-graph.md`, path-embedding catalogs, the lockfile's importer keys), and root-relative `packages/...` citations in prose (root and `apps/cli` READMEs, 7 script files + 22 docs files measured today; `verify-package-paths` gates the Markdown/TypeScript classes of stragglers). This list conveys the shape of the change, not a file inventory: every remaining group-path referent (workspace configs, test globs, lint keys) is found mechanically at implementation time by the acceptance gates failing loud, which is the repository's own misconfiguration rule. + +A group move does not touch: npm names, imports, `cordis.yml` configs, snapshot fixtures, the `pnpm-workspace.yaml`/`tsdown` globs (both `packages/*/*`), or the Python runtime manifest — all reference packages by npm name (verified by grep before this proposal). Each FIXME lands in the same PR as the move that creates its context. + +`client/` and `host/` are out of scope and unchanged. The in-flight `experimental/` group proposal (PR #844) is orthogonal — a release-boundary container, not a clustering decision; the only interaction is a trivial merge in the packages/README.md table. + +## Alternatives considered + +**Coarse domain buckets** (`exec/` = subprocess+sandbox+bash+pty+code-runtime, `workspace/` = fs+lsp+workspace, `orchestration/` = subagent+workflow+tasks, `knowledge/` = web+skill, `collab/` = plan+todo+goal; ~16 groups). Rejected: the measured graph contradicts the merges. `sandbox` and `subprocess` are shared infrastructure consumed across families (bash ×5, fs ×5, pty, lsp, mcp, subagent, sdk edges), `web` ↔ `skill` have zero edges, and a large bucket reproduces the `ui/` grab-bag at a larger scale. + +**Abstract layer names** (`capability/`, `policy/`, `extension/`, `provider/`). Rejected: they describe every plugin equally badly, and a `capability/` bucket would hold ~50 packages. + +**A full npm rename sweep** (`dsh--` for every package). Rejected: npm names are flat, so group-prefixing adds churn across imports, configs, and fixtures with no disambiguation gain; targeted FIXME-tracked renames cover the actual collisions. + +**Performing the five renames inside the reorganization.** Rejected in review: renames multiply open-PR conflicts and destroy the pure-move review property. The FIXME markers keep them visible release blockers to resolve as small follow-up PRs. + +**A two-way session split** (`session-core/` + `session-utils/`). Rejected: query belongs to neither side cleanly, and `session-core` invites confusion with `core/session` (`dsh-session`, the live in-memory service, which stays in `core/`). + +**A three-way session split** (`session-store/` + `session-query/` + `session-utils/`), this RFC's first draft. Rejected in review: `session-utils/` was a negatively-defined annex ("derived, nothing load-bearing depends on it") — the grab-bag shape the north star forbids, and factually wrong besides (`session-query` peer-depends on `dsh-session-title`). The invented compound names also read machine-generated; one plain `session/` group says what a human would say. Query stays standalone in both drafts: it is an independently consumed read surface with its own tool package and backend. + +**Recomposing `ui/` as a single `channels/` group** (tui + jsonrpc + acp + interaction seams + boot). Rejected: the same grab-bag under a new name — those packages serve four planes, `jsonrpc`'s measured cluster is the SDK wire stack, and `acp/` is an automation transport, not a human channel. + +**A standalone one-package `tui/` group**, this RFC's first draft. Rejected in review: `tui` is the interaction plane's primary provider/consumer (peer edges to `commands`, `user-interaction`), and a top-level name spent on one plugin adds a group without adding information; it folds into `interaction/`. + +**Keeping the group name `sdk/`.** Rejected in review: the whole `packages/` tree is the SDK, so an `sdk/` group inside it discriminates nothing — the same disease as `cordis/`. `scaffold/` names the actual role (create, launch, and drive projects from outside). + +**Moving `app-boot` to `apps/`** (review question). Rejected: `apps/` is the assembly tier over the package tier, and `dsh-app-boot` is a library that package-tier code imports (`scaffold/scripts`' launcher peer-depends on it) — placing it in `apps/` would invert the tiers and put a workspace library outside the `packages/*/*` build globs. It stays a package; `boot/` is its role-complete home. + +**Moving `tool-cordis` into `core/`.** Rejected: self-modification is its own product seam, expected to grow; the spine stays minimal. The group was first named `self-evolve/`; review settled on `self-modification/` as the plainer term. + +**Renaming `context/` to `request-context/`.** Rejected: within this tree the group is unambiguous in situ; the churn is unjustified. + +## Acceptance criteria + +- The tree matches the map: the six recomposed groups hold exactly the listed members; the groups `ui/`, `sdk/`, `telemetry/`, `timeout/`, `cordis/`, `session-persistence/`, `session-projection/`, and `session-title/` no longer exist; every other group's contents are unchanged. +- Zero npm renames: the workspace package-name set is identical before and after. +- The five FIXME markers are present, each naming its intended new name. +- New and updated group READMEs form complete bilingual triplets; the packages/README.md hierarchy table matches the tree; docs/module-graph.md is regenerated. +- `pnpm run typecheck`, `pnpm run test`, `pnpm run doc-sync`, and `pnpm run hygiene` pass, and the unit/snapshot suites collect the same test files before and after the move (group-scoped globs in test configs must not silently match nothing). + +## Risks + +- **Open-PR conflicts.** Every open PR touching a moved file must rebase across the move. Mitigated by landing as one PR in a quiet window, publishing the mapping table in the PR body, and the moves being pure renames (rename detection resolves most hunks mechanically). +- **Reference drift.** Docs or scripts citing old paths can go stale; `verify-package-paths` exists precisely for moved-package drift and runs in `doc-sync`. +- **Translation load.** Five new group README triplets plus edits to existing pairs land in the implementation PR. +- **Deferred renames are release blockers by design.** A FIXME that later proves wrong must be removed explicitly with rationale, never silently dropped. +- **Single-package groups remain** (`boot/`, `self-modification/`, and existing ones such as `acp/`). Accepted deliberately: each is role-complete rather than a fragment of a family, and a truthful small group beats a nominal merge. +- **What this gives up:** nothing functional — the change is navigational. Muscle memory and external links to old GitHub paths break, which is acceptable pre-release with no external consumers. diff --git a/.agents/notes/proposed/architecture/2026-07-29-package-regrouping.zh.md b/.agents/notes/proposed/architecture/2026-07-29-package-regrouping.zh.md new file mode 100644 index 0000000000..c49c5a1daf --- /dev/null +++ b/.agents/notes/proposed/architecture/2026-07-29-package-regrouping.zh.md @@ -0,0 +1,106 @@ +# Agent Note: 按实测聚类重新划分 packages/ 分组 + +Status: proposed + +[English](2026-07-29-package-regrouping.md) | 中文 + +## Problem + +两级 `packages//` 层级结构([原始决策](../../archived/architecture/2026-06-20-package-hierarchy.md))自 6 月以来已经漂移:167 个包(package)如今坐落在 40 个组里,若干组边界已经对不上这些包的实际聚类。 + +- `ui/` 混杂了四个互不相关的平面:人类终端通道(`tui`)、SDK 的 JSON-RPC 服务端一半(`jsonrpc`,它对 `dsh-sdk-protocol` 的对等依赖(peer dependency)把它绑在 `sdk/` 这一栈上)、人机交互 seam(`user-interaction`、`user-approval`、`permission`、`tool-ask-user`、`commands`),以及与通道无关的 boot 胶水(`app-boot`)。它自己的 README 只能逐一叙述这堆混杂,说不出一个统一职责。 +- 会话家族被割裂在五个组里——`session-persistence/`、`session-projection/`、`session-query/`、`session-title/` 与 `telemetry/`——而实测依赖边明明把它们连成一体(query → persistence、title → projection、projection → persistence;见 [docs/module-graph.md](../../../../docs/module-graph.md))。 +- 两个组名与不相干的包撞名:`telemetry/`(会话上报)撞上 `sdk/telemetry` = `dsh-telemetry`(启动器侧 SDK telemetry),`timeout/`(一个工具调用守卫)撞上 `util/timeout`(通用 promise 工具)。 +- `cordis/` 拿所有包共同依托的框架给自己的组命名,这个名字因此毫无区分度;组里唯一的包 `tool-cordis` 是运行时自我修改工具集。 +- `sdk/` 的目录命名不一致:`sdk/sdk-client` 和 `sdk/sdk-protocol` 重复了组名,而 `sdk/telemetry`、`sdk/helper`、`sdk/scripts` 没有。 + +这次重新分组的指导准则:**聚类紧密的包同处一组。**聚类以实测为准(对等依赖边与 co-change),而非按主题归类。孤立的 seam 家族可以自成一个小组;要避免的失败形态,是名字概括不出单一职责的大杂烩组。 + +## Proposal + +重组六个组;其余每个组都保持今天的边界与内容不变(依赖分析确认各能力家族——`bash/`、`pty/`、`code-runtime/`、`sandbox/`、`subprocess/`、`fs/`、`lsp/`、`web/`、`skill/` 及其余——本来就划得正确)。这次重组不改任何 npm 包名;整个变更全部由目录树承载。 + +| 组 | 成员(目录名) | 来源 | +|---|---|---| +| `session/` | session-persistence、session-persistence-jsonl、session-persistence-sqlite、session-checkpoint-policy、session-projection、session-projection-cache、session-title、session-title-llm、session-title-first-message-llm、session-title-all-messages-llm、session-telemetry、session-telemetry-otel | `session-persistence/` + `session-projection/` + `session-title/` + `telemetry/` | +| `interaction/` | user-interaction、user-approval、permission、tool-ask-user、commands、tui | `ui/` | +| `boot/` | app-boot | `ui/` | +| `scaffold/` | helper、scripts、create-sdk、protocol、client、server、telemetry | `sdk/` + `ui/jsonrpc` | +| `guard/` | repeat-tool-guard、timeout-policy | `guard/` + `timeout/` | +| `self-modification/` | tool-cordis | `cordis/` | + +- **`session/`** 是持久会话数据平面:持久化 seam 连同其各后端与检查点策略、从该日志折叠(fold)出全量值对外供值的投影、日志兜底的标题,以及 OTel 上报。标题折叠本身就是读取侧的承重构件(`session-query` 对 `dsh-session-title` 声明对等依赖),所以标题属于数据平面,而非某个「派生服务」附属区。用这个朴素的名字是有意为之(评审意见:名字要像人起的);旁边的 `core/session` 包仍是常驻内存的实时服务,本组则是围绕它的持久家族。`session-query/` 保持独立成组:这个读取/工具面自带模型工具和 SQLite FTS 后端,其消费不依赖持久化内部实现。吸收 `telemetry/` 之后,与 `dsh-telemetry` 的组名冲突就此终结。 +- **`interaction/`** 是人机协作平面加上应答它的终端通道:提问/批准 seam、权限预设、面向模型的 `ask_user_question` 工具、人类命令注册表(`plan-mode` 与 `command-goal` 已经把 `commands` 和各交互 seam 放在一起消费),以及 `tui`——这个交互通道是该平面最重的提供方与消费方(对 `commands` 与 `user-interaction` 均有对等依赖边),而一个单包 `tui/` 组会把一个顶层名字花在一个插件上。 +- **`boot/`** 是角色完备的单包组:不归属任何通道也不归属任何组装的共享 bin boot 胶水(被 `apps/cli`、`scaffold/` 的启动器和 `examples/` 各演示 bin 消费)。 +- **`scaffold/`** 是开发者工具家族:项目 helper、启动器、初始化器、连同两端的通信协议(`server` 即今天的 `ui/jsonrpc`),以及启动器侧 telemetry。评审中从 `sdk/` 改名:整个 `packages/` 树本身就是 SDK,树里再放一个叫 `sdk/` 的组等于什么都没说;`scaffold/` 说出了「创建/启动/驱动项目」这一实际角色。目录去掉遗留的 `sdk-` 前缀(`protocol`、`client`、`server`),与 `client/`/`host/` 的角色命名风格一致。 +- **`guard/`** 保留其文档记载的角色(循环卫生守卫),并新纳入强制执行工具调用超时的包;那个与 `util/timeout` 撞名的单包组 `timeout/` 随之解散。 +- **`self-modification/`** 把 `cordis/` 遮蔽掉的角色说了出来:它是 agent(智能体)检查并挂载自身实时运行时中插件所用的工具集,也是未来自我修改类包的落点。 + +40 个组变为 37 个;收益在聚类正确与名实相符,不在数量增减。 + +## Deferred renames (FIXME markers) + +五个 npm 名最终应当改掉,但在这次重组内部改名,会把一个纯移动的 PR(Pull Request)变成大量翻改 import 的 PR。实现改为在每个受影响包的模块 JSDoc 里加一条 `FIXME`,写明意图中的新名字。`FIXME` 会阻塞打 tag 的发布([标记语义](../../../../docs/development.md)),这正是想要的倒逼机制:只有趁还没有外部消费方使用这些包时,这些改名才是零成本的。 + +| 当前 npm 名 | 目标名 | 原因 | +|---|---|---| +| `@deepseek-ai/dsh-jsonrpc` | `@deepseek-ai/dsh-sdk-server` | 名字说的是协议编码而非角色;它是 SDK 协议的服务端一半 | +| `@deepseek-ai/dsh-telemetry` | `@deepseek-ai/dsh-sdk-telemetry` | 与 `dsh-session-telemetry` 家族撞名;它是启动器侧 SDK telemetry | +| `@deepseek-ai/dsh-helper` | `@deepseek-ai/dsh-sdk-helper` | 作为公开发布名空泛得站不住脚 | +| `@deepseek-ai/dsh-scripts` | `@deepseek-ai/dsh-sdk-scripts` | 同上 | +| `@deepseek-ai/dsh-timeout-policy` | `@deepseek-ai/dsh-timeout-guard` | 仅为建议、尚未定案:使名字与其 `guard/` 归属对齐;到解决时再定 | + +前四个是已定的意图;兑现之后,SDK 通信栈的 npm 名随之收敛为 `dsh-sdk-*`(npm 前缀指产品栈,`scaffold/` 目录名指角色)。`@deepseek-ai/create-sdk` 保留其文档记载的 npm 初始化器特例。 + +## Implementation + +整个变更作为一个机械化 PR,在低活动窗口期合入:众多开放中的 PR 只经历一次冲突事件,文档得到一次原子更新,评审享有纯移动的可评审性(`git mv` 加路径修正,重命名检测完好)。若评审偏好拆分,同样的内容也可以拆成三个 PR 的堆叠(会话折叠;`ui/` 解散加 `scaffold/`;`self-modification/` 加 `guard/` 合并),代价是其他所有人要经历三次变基事件。 + +组移动会触及:被移动包的 `tsconfig.json` 相对 `references` 及每个依赖方的对应条目(含 `apps/cli` 的 project references);tsconfig 聚合与路径映射;各组 README(五组新的双语三文件配对、被解散组的 README 删除、[packages/README.md](../../../../packages/README.md) 的层级结构表、根 `AGENTS.md` 的布局图);重新生成的产物(`docs/module-graph.md`、内嵌路径的目录、锁文件的 importer 键);以及散文中以仓库根为基准的 `packages/...` 引用(根 README 与 `apps/cli` README、今天实测 7 个脚本文件加 22 个文档文件;`verify-package-paths` 兜住 Markdown/TypeScript 这两类漏网引用)。这份清单陈述的是变更的形态,不是文件清点:其余每一处组路径引用(workspace 配置、测试 glob、lint 键)都在实现时由验收门禁的响亮失败机械地找出来——这正是本仓库自己的「配置错误必须响亮失败」规则。 + +组移动不触及:npm 包名、import、`cordis.yml` 配置、快照 fixture(测试前置数据)、`pnpm-workspace.yaml` 与 `tsdown` 的 glob(都是 `packages/*/*`),以及 Python 运行时 manifest(元数据清单)——它们全部按 npm 包名引用包(提案之前已用 grep 核实)。每条 FIXME 都与创造其上下文的那次移动落在同一个 PR。 + +`client/` 与 `host/` 不在本次范围内,保持不变。在途的 `experimental/` 组提案(PR #844)与本案正交:它是发布边界容器,不是聚类决策;两者唯一的交集是 packages/README.md 表格里一次很小的合并。 + +## Alternatives considered + +**粗粒度领域桶**(`exec/` = subprocess+sandbox+bash+pty+code-runtime,`workspace/` = fs+lsp+workspace,`orchestration/` = subagent+workflow+tasks,`knowledge/` = web+skill,`collab/` = plan+todo+goal;约 16 个组)。不予采纳:实测依赖图与这些合并相矛盾。`sandbox` 和 `subprocess` 是被各家族跨界消费的共享基础设施(与 bash ×5、fs ×5、pty、lsp、mcp、subagent、sdk 均有依赖边),`web` ↔ `skill` 之间零依赖边,而大桶只会在更大尺度上复现 `ui/` 式大杂烩。 + +**抽象分层名**(`capability/`、`policy/`、`extension/`、`provider/`)。不予采纳:这些名字对每个插件都同样地不达意,而且一个 `capability/` 桶会装下约 50 个包。 + +**一轮全量 npm 重命名**(每个包都改为 `dsh--`)。不予采纳:npm 包名是扁平的,加组前缀只会在 import、配置和 fixture 之间制造改动,却换不来任何消歧收益;用 FIXME 跟踪的定点改名足以覆盖真正的撞名。 + +**在重组内部一并完成那五个改名。** 评审中否决:改名会成倍放大开放 PR 的冲突,并破坏纯移动的评审属性。FIXME 标记让这些改名保持为可见的发布阻塞项,留待以小型后续 PR 逐一解决。 + +**会话两分法**(`session-core/` + `session-utils/`)。不予采纳:query 放哪一侧都不干净,而且 `session-core` 容易与 `core/session` 混淆(后者是 `dsh-session`,常驻内存的实时服务,留在 `core/` 不动)。 + +**会话三分法**(`session-store/` + `session-query/` + `session-utils/`),即本 RFC 的初稿。评审中否决:`session-utils/` 是靠否定条件圈出来的附属区(「派生的、没有承重方依赖」)——正是指导准则禁止的大杂烩形态,而且事实层面也站不住(`session-query` 对 `dsh-session-title` 声明对等依赖)。杜撰的复合名也读起来不像人起的;一个朴素的 `session/` 组说的就是人会说的话。query 在两版草稿中都保持独立:它是被独立消费的读取面,自带自己的工具包与后端。 + +**把 `ui/` 重组为单一 `channels/` 组**(tui + jsonrpc + acp + 交互 seam + boot)。不予采纳:不过是换个名字的同一个大杂烩——这些包服务于四个平面,`jsonrpc` 的实测聚类归属是 SDK 通信栈,而 `acp/` 是自动化传输通道,不是人类通道。 + +**独立的单包 `tui/` 组**,即本 RFC 的初稿。评审中否决:`tui` 是交互平面最重的提供方/消费方(对 `commands`、`user-interaction` 有对等依赖边),把一个顶层名字花在一个插件上只添组不添信息;它折入 `interaction/`。 + +**保留组名 `sdk/`。** 评审中否决:整个 `packages/` 树本身就是 SDK,树里的 `sdk/` 组毫无区分度——与 `cordis/` 同病。`scaffold/` 说出了实际角色(从外部创建、启动、驱动项目)。 + +**把 `app-boot` 挪到 `apps/`**(评审提问)。不予采纳:`apps/` 是包层之上的组装层,而 `dsh-app-boot` 是被包层代码 import 的库(`scaffold/scripts` 的启动器对它声明对等依赖)——放进 `apps/` 会颠倒层级,并把一个 workspace 库放到 `packages/*/*` 构建 glob 之外。它仍是一个包;`boot/` 是它角色完备的家。 + +**把 `tool-cordis` 挪进 `core/`。** 不予采纳:自我修改是独立的产品 seam,预期还会生长;主干保持精简。该组最初命名为 `self-evolve/`;评审定为更朴素的 `self-modification/`。 + +**把 `context/` 改名为 `request-context/`。** 不予采纳:在这棵树里,该组就地看并无歧义;这份改动开销并不值得。 + +## Acceptance criteria + +- 目录树与映射表一致:重组的六个组恰好持有所列成员;`ui/`、`sdk/`、`telemetry/`、`timeout/`、`cordis/`、`session-persistence/`、`session-projection/`、`session-title/` 这些组不复存在;其余每个组的内容不变。 +- npm 改名为零:workspace 的包名集合在前后完全相同。 +- 五条 FIXME 标记全部就位,每条都写明其意图中的新名字。 +- 新增与更新的组 README 构成完整的双语三文件配对;packages/README.md 的层级结构表与目录树一致;docs/module-graph.md 已重新生成。 +- `pnpm run typecheck`、`pnpm run test`、`pnpm run doc-sync`、`pnpm run hygiene` 全部通过,且单元/快照套件在移动前后收集到相同的测试文件(测试配置里按组划定的 glob 不得无声地匹配为空)。 + +## Risks + +- **开放 PR 的冲突。** 每个触碰被移动文件的开放 PR 都必须跨过这次移动做变基。缓解方式:作为单个 PR 在低活动窗口期落地、在 PR 正文公布映射表,且移动都是纯改名(重命名检测可机械化解决大多数改动块)。 +- **引用漂移。** 引用旧路径的文档或脚本可能变得陈旧;`verify-package-paths` 正是为包移动造成的漂移而设,并随 `doc-sync`(文档同步门禁)运行。 +- **翻译负担。** 五组新的组 README 三文件配对,加上既有配对的修改,都随实现 PR 一同落地。 +- **推迟的改名按设计就是发布阻塞项。** 日后若某条 FIXME 被证明不对,必须连同理由显式移除,绝不允许无声消失。 +- **单包组依然存在**(`boot/`、`self-modification/`,以及 `acp/` 等既有单包组)。这是有意接受的:每个都是角色完备的整体而非某个家族的碎片,一个名实相符的小组胜过一次徒有其名的合并。 +- **这次变更放弃了什么:** 功能上一无所失——变更只关乎导航。肌肉记忆和指向旧 GitHub 路径的外部链接会失效;在 pre-release、尚无外部消费者的前提下,这可以接受。