# 贡献指南 [文档索引](README.md) · [English](contributing.en.md) 感谢你考虑为 dsh-TUI 做贡献!本文档是 `@deepseek-harness-tui/dsh-tui` 的共享开发 契约,适用于在本仓库工作的所有人与编码 Agent。 ## 如何贡献 - **报告 bug 或请求功能**:提交 issue,附上清晰的复现步骤与你使用的终端环境。 - **提交 PR**:base 指向 `main`。保持改动聚焦——一个 PR 只做一个逻辑改动, 标题用中文或中英对照,描述写清动机、改动点与验证方式。 - **请求 review 前先跑验证矩阵**:CI 运行的就是下面这些命令。 - 新功能应附带或扩展一个聚焦的回归脚本。 ## 范围(Scope) 本文件适用于整个仓库。它是 `@deepseek-harness-tui/dsh-tui` 的共享开发契约, 适用于在本仓库工作的所有人与编码 Agent。 `@deepseek-harness-tui/dsh-tui` 是单包、纯 ESM 的 TypeScript 项目:为 DeepSeek Harness 提供 React 终端 UI 前门(通过 Cordis 挂载)。包内拥有 TUI、本地命令面、打包技能 以及移植的 Ink/Yoga 渲染器;Agent、会话、模型、工具、持久化与策略域由 DeepSeek Harness 拥有,TUI 只消费它们。 做大改动前,先读 `package.json`、相关 README 章节和你将要编辑的每个源文件。 优先复用仓库现有的服务边界与辅助函数,而不是引入平行的抽象。 ## 仓库地图(Repository Map) - `src/index.ts`:公共 Cordis 插件入口、配置 Schema,与对运行时插件的惰性移交。 - `src/plugin.ts`:TTY 校验、服务注册、Agent 创建/恢复、React 树挂载,以及 终端/进程的收尾清理。 - `src/channel.ts`:事件到视图的投影 + 非 React 的动作面。把 DSH 会话事件 翻译成 transcript 行,实现 submit、steer、rewind、resume、模型/preset 切换、 本地报告及相关状态迁移。 - `src/screens/Chat.tsx`:顶层交互协调器。负责模态优先级、全局键盘、滚动/ 搜索/选区状态、slash 命令分发与聊天屏组装。 - `src/screens/StatusLine.tsx` 与 `src/screens/StatusMetrics.ts`:底部状态栏 呈现与指标推导。 - `src/components/`:功能组件。`components/design-system/` 是主题感知原语; `components/messages/` 是 transcript 行;`components/questions/` 是 `ask_user_question` 的 UI。 - `src/ui.ts`:本地渲染器、主题化 `Box`/`Text`、hooks 与公共 TUI 原语的 首选门面。 - `src/ink/`:移植的低层 Ink 渲染器与终端实现。**敏感基础设施**:改动要聚焦, 并附渲染器专用回归覆盖。 - `src/native-ts/yoga-layout/`:渲染器使用的移植布局引擎。 - `src/cc/`:为 Claude Code 风格 UI 适配的终端格式化与呈现辅助。 - `src/*Prefs.ts`、`src/customTheme.ts`、`src/sessionHistory.ts`:持久化的 用户偏好与 `~/.dsh-tui` 下的本地会话元数据。 - `skills/*/SKILL.md`:随 npm 包分发的技能,由 `src/packaged-skills.ts` 注册。 - `cordis.patch.yml`:profile 安装时使用的包级 bundle 覆盖层。行的顺序、行 ID、 被禁用的 host 行、insert/override 语义都很关键。 - `cordis.yml`:直接 Cordis/DSH 启动的完整裸组合示例。 - `scripts/`:无头回归、复现环境、探针与诊断。运行前先读脚本头部说明。 - `lib/`:由 `src/` 生成、忽略入库并随 npm 分发的 JavaScript、声明与声明映射。 `./invariant` 也直接使用 `lib/types/dsh-adapter/invariant.js` 的编译结果。 - `README.md` 与 `README_EN.md`:中英文用户文档。行为、配置、快捷键与限制 必须两版同步。 ## 运行时形态(Runtime Shape) 核心运行时链路: ```text Cordis config -> src/index.ts -> src/plugin.ts -> DSH agent/session services -> src/channel.ts (session events -> Channel snapshot) -> src/screens/Chat.tsx -> src/components/* -> src/ui.ts -> src/ink/* + Yoga layout -> terminal ANSI output ``` 职责归属在各层,不要越权: - Agent/会话/工具事实来自 DSH 服务与持久化会话事件。 - 投影与 TUI 动作属于 `channel.ts`,不属于呈现组件。 - 交互模式与按键优先级属于 `Chat.tsx` 或当前聚焦的模态/输入组件。 - 可复用的视觉行为属于 `components/` 与主题感知原语。 - 终端协议、布局、命中测试、选区与帧差分行为属于 `ink/`。 不要仅仅为了让某个界面更好写,就在 TUI 里重新实现 DSH 域服务。通过 channel 或既有注册表缝隙去适配服务。 ## 工具链(Toolchain) - 支持 Node `^22.19 || >=24`;CI 用 Node 24。 - CI 与发布用 pnpm 11;开发也请用 pnpm。 - 干净检出安装:`pnpm install --frozen-lockfile`。 - `pnpm-lock.yaml` 是唯一锁文件。npm 消费方不读依赖包的 lockfile, `package-lock.json` 已移除(见 #173 后续处理)。 - 有意改依赖时:用 `pnpm add` 更新 `pnpm-lock.yaml`,检查完整 lockfile diff, 避免无关升级。 - 本包运行时或发布类型引用到的 `@deepseek-ai/*` 框架包(与 `UPSTREAM_BLESSED_PACKAGES` 一一对应,含 `@deepseek-ai/schemastery`)必须同时 是 peer 与 dev 依赖:框架包由宿主提供,profile 内运行时经 `$DSH_HOME/profiles/node_modules` 回退树解析到宿主实例(见 #198——声明为 runtime dependency 会在 profile 里落下真实拷贝,与宿主形成双模块实例); dev 声明只为本地类型检查。新增此类引用时两组声明都要加、范围保持一致 (verify:manifest-deps 门禁会校验)。仅测试/脚本使用的框架包 (如 dsh-settings、dsh-tools、dsh-session-persistence-*)只需 dev 依赖, 不要为它们声明 peer。`dsh-working-activity` 等非宿主包仍是 runtime dependency。历史例外已消除:`dsh-working-activity@0.2.4` 及更早版本会经其 runtime dependency 把 `@deepseek-ai/schemastery`(连带 cosmokit)的真实拷贝 带进 profile;0.2.5 起已 peer 化(working-activity#2),profile 内不再 有任何框架包拷贝。保持依赖范围不低于 `^0.2.6`(0.2.6 另修复了 web 端 WorkingLine 在未打补丁宿主上的空值守卫,working-activity#5)。 - 不要暴露、持久化或打印凭证。交互启动读取 `DEEPSEEK_API_KEY`;诊断可以 报告是否已设置,但绝不能泄露完整值。 ## 构建与生成产物(Build And Generated Files) 常规构建与类型检查关口:`pnpm build`。该命令先删除整个 `lib/`,再用 `tsc -p tsconfig.json` 把 `src/` 输出到 `lib/types/`,最后运行适配边界、上游 契约与 patch surface 门禁。`prepare` 生命周期只执行干净编译,让 npm Git URL 安装无需仓库提交生成文件也能得到可运行的包;本地与 CI 使用显式命令,不依赖 pnpm 是否隐式执行根包生命周期。 生成产物规则: - 改 `src/`,**绝不直接改 `lib/`**。 - 任何源码改动后运行 `pnpm build`,但不要提交 `lib/` 下的生成结果。 - 干净编译会先删除整个 `lib/`,源模块重命名或删除后不会留下过期输出。 - 运行 `pnpm verify:package` 检查 `main`、`types`、`bin` 与 `exports` 的所有目标 都进入 npm tarball,并 smoke-import 主入口和 invariant 入口。 - 纯文档、纯 workflow、纯 YAML 改动不需要重建(除非同时改了 TypeScript 输入)。 - 使用 `--ignore-scripts` 安装 Git URL 会跳过 `prepare`,因而不受支持;registry 包已经包含编译结果,不依赖消费者执行生命周期脚本。 `scripts/build.sh` 是面向本地 DeepSeek Harness 源码检出的备用构建器(定位 DSH 检出并重连依赖),不是本独立仓库的默认构建命令。 ## 验证(Verification) 仓库没有根级 `test` 或 `lint` 脚本;不要声称跑过它们。TypeScript 构建是通用 静态关口,随后是聚焦的可执行回归。 CI 在安装后运行: ```sh pnpm compile # 从干净目录生成运行时 test -f lib/types/index.js pnpm verify:build # 构建门禁,不重复编译 pnpm verify:package # npm tarball 与入口 smoke test node --import tsx/esm scripts/repro-askpanel.tsx node --import tsx/esm scripts/verify-askpanel-layout.tsx node --import tsx/esm scripts/repro-toolcards.tsx ``` 改动共享渲染、`Chat`、提示/问卷布局、工具卡、主题原语或 Ink core 时,三个 CI 回归都要跑。窄改动还要跑最近的聚焦脚本: | 改动区域 | 聚焦验证 | | --- | --- | | 通用无头屏幕组装 | `pnpm smoke` | | Channel submit/steer/pending 行为 | `node scripts/verify-submit.mjs` | | 提示队列行为 | `node scripts/verify-queue.mjs` | | Goal/todo 投影与渲染 | `node scripts/verify-channel-goal-todo.mjs` + `node scripts/verify-goal-todo.mjs` | | Compaction 与折叠 transcript 行 | `node scripts/verify-compact.mjs` | | 主题加载与持久化 | `node --import tsx/esm scripts/verify-themes.mjs` | | 滚动/粘底行为 | `node scripts/verify-scroll.mjs`、`node scripts/verify-resticky.mjs` 及对应 `repro-*` 环境 | | 全屏复制即选区 | `node scripts/verify-copy-on-select.mjs` | 多数用普通 `node` 调用的脚本 import `lib/types/`——先跑 `pnpm build`。import TypeScript 源的脚本在头部声明 `node --import tsx/esm