# 贡献指南 欢迎来到 dsh-ops!本指南说明如何认领工作、如何在本机构建与验证,以及 PR 评审所依据的约定。English: [CONTRIBUTING.md](CONTRIBUTING.md)。 ## 目录 - [dsh-ops 是什么(以及不是什么)](#dsh-ops-是什么以及不是什么) - [工作流程:先 issue,后 PR](#工作流程先-issue后-pr) - [开发环境](#开发环境) - [构建插件与源码安装](#构建插件与源码安装) - [构建原生 worker](#构建原生-worker) - [验证门禁](#验证门禁) - [每个 PR 必须有一次 pull_request run](#每个-pr-必须有一次-pull_request-run) - [编写测试](#编写测试) - [代码与文档约定](#代码与文档约定) - [提交与 PR](#提交与-pr) - [不可触碰的边界](#不可触碰的边界) - [许可证与署名](#许可证与署名) ## dsh-ops 是什么(以及不是什么) - **极简、低侵入。** dsh-ops 只为 DSH 提供额外的易用工具,连 bash 本身也由 `ops_bash` 提供;对原版逻辑的改动仅限于**可选地**禁用原版重复工具,以保证与其他插件兼容。 - **联动层与核心解耦。** dsh-ops 会与一些 DSH 插件联动;插件间联动层单开逻辑区域,完全与核心逻辑解耦。 - **刻意保持小。** 团队希望代码和功能保持简洁:新功能必须先有经批准的 issue,再写代码。 ## 工作流程:先 issue,后 PR 1. **PR 必须关联 issue,且须主动在 issue 认领**(评论或被指派)——否则不予通过。以 issue 是否被认领来确认彼此的工作,防止重复。 2. **issue 是 PR 的工作区。** 反馈 bug 的讨论或新功能设想请移步 [Discussions](https://github.com/T-Auto/dsh-ops/discussions);issue 描述的应是某个 PR 会关闭的具体工作。 3. **feat 需先获批。** 先写 issue,经维护团队批准后再写功能。 4. **大改动要拆分。** 大的改动(例如 #151)拆成可独立评审的 issue,每个切口在各自的 `work/` 分支上开发。 ## 开发环境 | 组件 | 要求 | | --- | --- | | Node.js | `^22.19` 或 `>=24`(见 `package.json#engines`;CI 用 24) | | Rust | `1.97.1`(CI 固定的工具链);构建原生 worker 需要 | | 目标宿主 | DSH `0.2.0-rc.2` | | 仅 Linux | `acl` 软件包(POSIX ACL 检查需要) | ```console npm ci --ignore-scripts npm run build # 将 src/ 编译到 lib/ 并复制组件资源 npm run build:ptc # 构建并暂存原生 worker(见下) npm run verify ``` ## 构建插件与源码安装 插件源码在 `src/` 中使用严格 TypeScript 或 TSX 编写。`npm run build` 将 JavaScript 产物输出到 `lib/`,并复制组件 manifest、语言文件、图标和面板 HTML。`lib/` 已被 Git 忽略,不得手动修改。包导出、组件入口和测试加载 `lib/`;运行时导入保留 `.js` 路径。 修改源码或资源后,先运行 `npm run build`,再通过 `node test/run.mjs <过滤词>` 直接运行套件。`npm test`、`npm run test:ptc` 与 `npm run verify` 会先构建;`npm run typecheck` 检查全部插件源码,不生成产物。构建拒绝 `src/` 中的 JavaScript 文件,包括 TypeScript 编译器原本会静默忽略的未导入文件。仓库脚本与测试仍可使用 JavaScript。 从本地目录或 Git 源码安装时,先在检出的仓库中构建并打包,再安装到 DSH: ```console npm ci --ignore-scripts npm run build npm run build:ptc npm pack ``` Windows 上固定的 Bash 载荷由 `npm ci` 安装;若缺失(例如用 `--omit=optional` 安装的检出),先运行 `node bin/dsh-ops.mjs bash-runtime install`,再运行 `npm run build:bash-ipc`。离线构建可把 `DSH_OPS_TEST_BASH` 设置为固定版本 `bin/bash.exe` 的绝对路径,构建/测试会核验摘要。未提供该变量时,门禁会获取自己的临时测试副本。使用目标 profile 对应的 DSH CLI 安装生成的 `dsh-ops-.tgz`(见 README)。除非禁用生命周期脚本,`npm pack` 会通过 `prepack` 执行构建。未构建的 Git 检出没有 `lib/`,不能直接加载;项目没有 `prepare` 钩子。构建时须保留开发依赖,其中包含编译器。发布包包含编译后的 `lib/` 及其资源,不包含 `src/`,使用者无需编译插件。 ## 构建原生 worker 大多数套件驱动的是真实的原生 worker;没有 worker 时它们无法作答,会**跳过**: ```console cargo test --workspace --locked --manifest-path native/Cargo.toml # worker 单元测试 npm run build:ptc # 构建并暂存到 runtime/-/ npm run build:bash-ipc # 仅 Windows:Bash IPC 代理 ``` 本机跳过意味着什么: - `test/run.mjs` 把「跳过」与「通过」分开计数。没有 worker 的机器会报 `N suite(s) passed, M skipped`——这**不是**绿的门禁。 - `DSH_OPS_REQUIRE_RUNTIME=1` 把任何「缺运行时」导致的跳过记为失败。门禁在 Linux 与 macOS 上会设置它,所以没构建 worker 时,这些套件在那里会**失败**。 - `DSH_OPS_ALLOW_SKIPS=tui-host,tui-native` 让依赖可选外部检出(`DSH_OPS_TUI_ROOT`、`DSH_OPS_TUI_WORKER`)的套件即使在 `DSH_OPS_REQUIRE_RUNTIME=1` 下也仍算跳过。 - 平台跳过是具名的,固定在 `scripts/verify-gate.mjs` 的 `ALLOWED_SKIPS` 里:Windows 上是 `file-metadata`、`linux-attributes`、`linux-shell`;POSIX 上只有 `mount`,且仅在解析不到 worker 时。其他任何跳过都会让门禁失败。 ## 验证门禁 - `npm run verify` —— 先将 `src/` 构建到 `lib/`,再运行 `scripts/validate-manifest.mjs` 和 `test/run.mjs`(每个 `test/*.test.mjs` 独立进程、独立临时 `DSH_HOME`)。 - `node scripts/verify-gate.mjs --skip-install` —— **发布门禁**,即 `.github/workflows/ci.yml` 在 Windows x64、Linux x64、macOS arm64 上运行的同一脚本:worker 单元测试、构建与暂存、manifest 下限、全部套件、允许跳过的集合、磁盘上的套件清单,以及打包产物。 - `node test/run.mjs <过滤词>` 只运行文件名包含该词的套件,例如 `node test/run.mjs mount file-operations`。 如何阅读门禁输出、各宿主的预期数字:见 [verify-locally.md](verify-locally.md)。CI 在每个 PR 和每次推送到 `main` 时运行;PR 必须在三个平台上全绿。 ## 每个 PR 必须有一次 pull_request run 每个 PR 都必须有一次由它**自己的 `pull_request` 事件**触发的 [Cross-platform validation](https://github.com/T-Auto/dsh-ops/blob/main/.github/workflows/ci.yml) 运行。聚合 job **`CI required`** 汇总 Windows x64、Linux x64 与 macOS arm64 三腿,PR 模板要求贴出的就是它的 run URL。 - **触发条件。** `ci.yml` 在 `pull_request` 指向 `main`、`0.3.2` 与 `release/0.4.0` 时触发 (同时对同分支的 `push` 与 `merge_group` 触发)。GitHub 从 PR 自身的 head 读取 workflow 文件, 所以对上述任一分支开 PR 或更新 PR 时都会起一次运行。 - **如何确认。** 先按分支列出运行、核对事件,再取 run URL: ```console gh run list --branch --workflow ci.yml --event pull_request --limit 1 gh run view --json url,event,conclusion --jq '"\(.url) \(.event) \(.conclusion)"' ``` PR 的 **Checks** 标签页显示同一次运行;那里 `CI required` 变绿才是真正通过。 - **fork 贡献者(#150)。** 两条可验证路径:(a) 直接向上游仓库开 PR,让本仓库触发 `pull_request` 运行——首次来自新 fork 贡献者的运行可能停在 `action_required`,需要维护者批准后各 job 才会开始; (b) 在 fork 内从 feature 分支向 fork 的默认分支开 PR,事件同样是 `pull_request`,贴那次 run 的 URL 并注明它是 fork 内 run。手工运行 workflow 或依赖 `push` 运行仍然不算数。 - **手工 dispatch 不算数。** 合入 `release/0.4.0` 的 27 个 PR 里有 26 个完全没有 `pull_request` 运行:绿色来自手工 `workflow_dispatch`,它验证的是某个分支,从来不是这次合并(#303)。 - **必需检查。** 把 `CI required` 设为 `main`、`0.3.2` 与 `release/0.4.0` 的*必需*状态检查属于 仓库设置,需要 admin 权限;已在 #303 跟踪,代码 PR 无法完成。在它落地之前,这条规则靠评审执行。 - **Dependabot PR。** 设为必需检查后,依赖升级 PR 若变红会像其它 PR 一样被挡住:先 rebase/重跑, 需要该升级就在 PR 里修版本或构建,不需要就说明理由后关闭;不要用 admin 权限绕过必需检查—— 过不了 CI 的升级不合并。 ## 编写测试 - **新增行为必须同时补断言**,且撤掉实现后断言会变红(行为级红绿,而非只看代码结构)。与挂载相关的改动必须能在 `test/mount.test.mjs` 里被证伪。 - **真实调用优先于 schema 断言。** 一个工具算被覆盖,是指某个套件经真实 worker 或宿主调用它并断言结果,包括失败路径(权限拒绝、CAS 冲突、无 follow、元数据不支持、`AUTHORITY_CHANGED`)。 - **一个套件 = 一个 `test/*.test.mjs` 文件 = 一个进程。** 门禁断言磁盘上的套件全部被收集。共享 helper 放在 `test/lib/`,文件名**不得**以 `.test.mjs` 结尾。 - **不得静默跳过。** 整个套件只能通过 `test/lib/harness.mjs` 的 `skipSuite()` 跳过,且必须在允许清单内。某条检查的夹具在某宿主上无法建立时,要打印具名的 `... fixture unavailable: <原因>`,而不是悄悄通过。 - **不得靠重试掩盖偶发。** 时好时坏的并发断言是实现的缺陷,要修根因。门禁的重试(#164)只覆盖套件超时。 能力名称、所属组件、呈现模式、变更/并发标记以及文件与 PTC schema 统一存放在 `src/capabilities/`;新增能力元数据应在此维护,适配层负责注册与渲染。`src/kernel/` 定义 0.4 的 `Outcome`、覆盖率/搜索/引用类型及错误重试策略,后续适配层迁移才会调整线上返回结构,本次基础切片保留现有输出。运行 `node test/run.mjs capabilities kernel-errors kernel-types` 验证目录、重试规则和 TypeScript 正反类型断言。Node 声明固定为 `@types/node@22.20.5`(2026-10-01 发布),用于受支持的 Node 22 API 类型,不引入运行时代码。 ## 代码与文档约定 - **语言**:代码、注释、CLI 输出、提交信息一律英文;对外文档中英双语;面向维护者的说明可用中文。 - **文档成对**:`docs/` 下同一文档中英各一份,例如 `docs/usage.md` 与 `docs/使用指南.md`,同一个 PR 里一起更新。 - **文件规模**:新文件保持在约 500 行以内。文件超过这个规模时,拆成模块,并把相关模块归到一个文件夹中,参照现有的 `src/components/*` 与 `test/lib/` 布局。 - **工具名**:一律经 `src/policy.ts` 的 `publicToolName()` 生成,不要手写 `ops_` 前缀。 - **配置校验**:放在 `src/config.ts`,未知键必须报错并点名;`cordis.patch.yml` 里的默认值必须与代码默认值一致。 - **解析与探测**:同步放在 `apply` 里;`required: true` 必须在加载期拒绝激活。 ## 提交与 PR - 小步提交,提交信息用 conventional 形式的英文祈使句,例如 `fix: report undeclared background arguments as argument errors (#14)`。 - 使用真实作者身份;不空提交刷数、不伪造署名、不重写已推送历史。 - 按 `.github/PULL_REQUEST_TEMPLATE.md` 填写;**Validation** 段必须给出实际命令与输出(含 passed/skipped/failed 计数),而不是只打勾。 - 行为或发行物文档有变化时,在 [CHANGELOG.md](CHANGELOG.md) 的 `## [Unreleased]` 加条目;若判定不需要,在 Validation 段写明理由。 - 除非 issue 要求,不新增依赖,不改 `package.json` 的版本与 `optionalDependencies`。 ## 不可触碰的边界 - 宿主半边**不得在加载期 import `@deepseek-ai/*` 值**;需要宿主服务时用 `ctx.inject([...])` / `ctx.get()`。 - **绝不改写共享工具注册表**:只通过本插件自己的 `ctx.tools.register` 发布 `ops_*` 工具。 - 提示词段的取值必须用 `ctx.get('tools')`,不能用 `ctx.tools`。 - 不得为降低 schema / 提示词字节数删掉工具契约(参数语义、默认值、路径基准、分页完整性、权限边界等)。 ## 许可证与署名 除 `licenses/fastctx/` 外均为 MIT;FastCtx 为 Apache-2.0。`NOTICE` 与 README 中上游要求逐字转载的致谢**必须原样保留**,不要「顺手润色」。 原生组件的编译与验收细节见 [native-ptc.md](native-ptc.md) 与 [manual-validation.md](manual-validation.md)。 凭据围栏、可信路径锚点和权限辅助函数已迁移为严格 TypeScript 模块(`src/credential-policy.ts`、`src/authority-paths.ts`、`src/permissions.ts`),生成的 `.js` 导入路径及线上错误结构保持不变。权限策略仅以类型方式引用宿主定义;未知 worker 错误必须先缩窄类型才能使用。运行 `node test/run.mjs kernel-types authority credential-fence credential-denial safety-atomicity permissions` 验证类型边界与既有权限行为。 `src/profile-install.ts` 与 `src/session-temp.ts` 已迁移为严格 TypeScript,生成路径不变。配置档修改仍委托官方 CLI,manifest 数据先缩窄再读取;临时目录保持按会话派生命名并拒绝链接/文件目标。运行 `node test/run.mjs profile-temp kernel-types` 验证委托、预览、失败及类型化结果。 `src/index.ts` 使用公开 Cordis `Context` 与 `Agent` 契约约束组件入口。原始配置在验证前保持 `unknown`,回调与工具拒绝决策遵循宿主公开签名;运行时导入路径仍为 `.js`,模块加载时仅以类型方式引用宿主依赖。运行 `node test/run.mjs kernel-types mount bundle-startup progressive-loop` 验证类型边界、作用域启动/卸载及下一步发布。 设置集成测试使用固定 `0.2.0-rc.2` 版本的宿主设置、配置启动及配置编辑器开发依赖, 并固定兼容的 Cordis loader/group/include、CosmoKit 和 Schemastery 补丁版本。 测试实际的配置文件写入及组件重新加载,而不是用内存保存代替;这些包不属于生产依赖, 插件宿主代码也不会在运行时导入它们。 浏览器设置使用严格 TSX。构建复用现有 TypeScript 编译器生成宿主的延迟 `./client` 工厂, React 由宿主提供。精确锁定且仅用于开发的 React 18.3.1、React 类型包、jsdom 26.1.0 与 `0.2.0-rc.2` 客户端契约用于编译、DOM 交互及真实宿主插槽生命周期测试,不添加浏览器打包器或 生产依赖。`settings-ui`、`settings-client`、`settings-host` 和 `settings-runtime` 套件覆盖 表单到配置持久化以及先检查后执行的操作边界。 可选承载行回归测试还使用仅开发依赖 `@deepseek-ai/dsh-client-modules` `0.2.0-rc.2`(2026-09-29 发布),在临时安装 profile 中验证真实客户端注册表的启用、 卸载与重启。`settings-carrier` 覆盖精确配置合成、备份和原子回滚,不迁移开发者实际 profile。 `src/panel/collect.ts` 使用类型化文件系统接口,并在折叠前验证持久化事件字段。有效采集文档保留正文、计数和归档行为;错误的正文、用量或时间字段计入 malformed,结果标记为降级且用量不完整,不能静默转换。停止仍会取消排队工作、等待已发起 I/O 并保留追加日志,显式封存保持独立。运行 `node test/run.mjs ops-panel-collect panel-records panel-stop kernel-types` 验证恢复、数据边界、物理停止和类型契约。