# AGENTS.md — dsh-workspace-enhancement > 给在本仓库工作的 AI 代理(DSH / Cursor / ZCode / 其他)与人类协作者的**工作规则**。 > 本文只放「规则与命令」;事实与设计在 `docs/`,待办在 `docs/backlog.md`。 > 本文档**入库**:clone 下来就能读到。请保持 ≤200 行。 ## 1. 这个项目是什么 把 DSH 生态里散落的「工作区」能力(远程 SSH 工作、目录选择、机器/连接管理)收进**一个**插件包: `ctx.subprocess` / `ctx.fs` 的远程 provider 让框架里所有消费这两条接缝的工具零改动地跑在远端。 架构见 `docs/architecture.md`,关键取舍见 `docs/decisions/ADR-*.md`。 技术栈:Node ≥ 22.8、ESM only、TypeScript + `tsc`(类型)+ `tsdown`(打包)。 宿主半编译到 `lib/`,客户端半(`src/client/` TSX)打包为单文件 `lib/client.js`。 ## 2. 真相源(**不要另建第二份**) 完整地图(想知道什么 → 看哪份 → **不要写在哪**)见 `docs/README.md`。代理开机用的短表: | 想知道 | 看 | |---|---| | 待办 / 需求 / 状态 | `docs/backlog.md`(唯一待办真相源;改状态改这里) | | 当前版本、提交、待办分布 | `docs/status.md`(`npm run status` 生成,**禁止手改**) | | 现状怎么跑 | `docs/architecture.md` | | 为什么这么做 | `docs/decisions/`(索引 `docs/decisions/README.md`) | | 某一轮做了什么 | `docs/rounds/`(**档案,不是现状**) | | 测试 / 兼容 / 验收 | `docs/testing.md` · `docs/compatibility.md` · `docs/uat/` | `docs/backlog.md`:**分区 = 状态列**(`doing` 只在 §1,不得留在 §2);§2 按 P0→P3,有「提出」列; §4 有「完成」列(都是 `YYYY-MM-DD`)。备注只写未做动作 + 指针(闸门限长)。调查进 `docs/rounds/` / `docs/decisions/`。 `drafts/`、`.agent-teams/`、`.workbuddy/`、`.tmp/` 是**本地素材**(不入库、可能含机器专属数据)。 任何结论一旦拍板,必须搬进 `docs/README.md` 点名的那些文件——否则下一个 clone 的人看不到。 ## 3. 命令(直接跑,不要猜) | 命令 | 用途 | 谁能跑 | |---|---|---| | `npm run check` | **唯一质量门**:静态闸门 + typecheck + 单测 + build + pack 冒烟 | CI / 本地 shell | | `npm run check:static` | 静态闸门(词典/密钥/peer 家族/版本/提交信息等 12 项 + `lib/` 漂移 WARN) | 代理 / CI | | `npm run typecheck` | `tsc --noEmit` | 代理 / CI | | `npm test` | 全量单测(`node --test`) | CI / 本地 shell | | `npm run test:agent` | 沙箱内单测(单进程、无 esbuild;自动分类沙箱受限失败) | **代理** | | `npm run build` | `tsc` + `tsdown` | 代理 / CI | | `npm run e2e` | Playwright 黑盒(lab 50599) | 本地 shell / CI | | `npm run e2e:l1 -- --cli (--key \| --password )` | L1 实机冒烟 M3–M5(假模型 headless,真宿主真工具;参数详见 `scripts/e2e-l1/run-cases.mjs` 头注;CI 的 e2e-l1 作业自动跑,INFRA-23) | 代理 / CI | | `npm run status` | 重新生成 `docs/status.md` | 任何人 | | `npm run preflight` | **能力预检**(node/npm/git/gh/wsl/端口/lab/打包产物;browser-use 需 agent 自报)——每轮开工与接手先跑,缺能力 ⇒ blocked 不跳过(§7) | 代理 | | `npm run slots -- --list \| --key \| --diff ` | 上游客户端**槽位/服务目录**读取与 diff(磁盘权威源读取器,零依赖、只读;见 §5 红线 7) | 代理 / CI | | `node scripts/boot-smoke.mjs [--no-channel]` | **真 boot 哨兵**:临时 `DSH_HOME` → 起宿主 → 断言进程存活 / `GET /` 200 / `POST /api/dsw/connections.list` 200 + `result.ok=true`(`upstream.yml` 每条通道都跑,**强断言**;见 §4) | 代理 / CI | | `pwsh -File scripts/dev-lab.ps1` | 起隔离 lab 实例(win32 宿主,50599) | 本地 shell | | `pwsh -File scripts/dev-lab-wsl.ps1 [-Smoke]` | 起 **WSL Linux 宿主** lab(50600;家族校验跟 pin、装 pack tarball;INFRA-19) | 本地 shell | | `node scripts/upstream-tags.mjs [--write-baseline]` | 宿主 dist-tag 轻探(每日 workflow 跑;漂移开 issue;认领=重写基线提交) | 代理 / CI | | `node scripts/upstream-desktop.mjs [--write-baseline]` | 桌面组合周探(monorepo 四文件指纹 + `PROFILE_TEMPLATES.web` 语义断言,UPSTREAM-11;认领=重写基线提交) | 代理 / CI | 实机测试分层与 case 清单见 [`docs/uat/matrix.md`](./docs/uat/matrix.md)(L1 每 PR / L2 发版前;能力缺失→blocked,不静默跳过)。 改完代码后**至少**跑 `npm run check:static && npm run typecheck && npm run test:agent`; 能跑 shell 时跑完整 `npm run check`。 **改了 `src/` 必须 `npm run build`**:`lib/` 是 gitignore 的产物,历史上以 `link:` 装的 profile 直接加载 `lib/`——不 build 就重启,跑的仍是旧代码(2026-09-09 实锤:进程跑着 6 小时前的 build, REQ-I6 没生效,见 `INFRA-11`)。**2026-09-11 起产品 profile(3080)已不再安装本插件**。 **2026-09-17 起:lab 校验用 `npm pack` 的 tarball 安装**(与真实发包场景一致),不再是 `link:`: ```powershell $env:DSH_HOME = 'C:\Users\Admin\.dsh-lab' # ← 必须先设!否则 dsh 默认写到产品 home ~/.dsh(踩过) dsh plugin --profile web remove dsh-workspace-enhancement dsh plugin --profile web add \.tmp\dsh-workspace-enhancement-.tgz ``` ⇒ 改完代码要进 lab,固定动作是 **`npm run build` → `npm pack` → 上面的 remove/add → 重启 lab**; `npm run build` 本身**不再影响 lab**。真机验证一律在 lab(`DSH_HOME=.dsh-lab`、端口 50599); `.tmp/engineer-host/lab-home-probe.ps1` 是现成的探针(不删 home、自带清理)。 闸门在「`lib/` 早于 `src/`」时打 WARN(非阻断,CI 无 `lib/` 时跳过)。 **改了测试或跨平台代码,push 前必须跑 Linux 复验**(Windows 全绿抓不到 Linux-only 假设): ```bash wsl -e bash -lc "bash /mnt/d//scripts/verify-linux.sh" ``` > PR #3 的教训:一条用例用裸 POSIX 路径冒充远程 cwd,只有 Ubuntu 红;而该脚本曾因 > `.sh` 在 Windows 检出为 CRLF 而**根本跑不起来**(见 `FIX-7`),所以「本地全绿」是假的。 ## 4. 沙箱现实(代理必读) DSH 文件沙箱(workspace-write)**不能开管道**: - `npm test` / `node --test` / `tsx` → `spawn EPERM`(逐文件 spawn / esbuild worker)。 **代理用 `npm run test:agent`**:单进程 + Node 内置 TS transform,退出 0 = 没写坏。 - `tsc --noEmit` 正常(不 spawn)。 - 真进程用例(`(process-level)`、`AUDIT-TC*`)与 `SetFileSecurityW` 失败属**沙箱受限**, `test:agent` 会归类并仍返回 0;权威判定是 CI 的 `npm test`。 - 子进程捕获输出要用**文件描述符重定向**(见 `scripts/lib/run.mjs`),不要用 `execFileSync` 默认管道。 **「能编译 ≠ 能运行」**:typecheck 与单测**抓不到宿主装配问题**——2026-09-10 的 F1 就是标本: 插件在 `0.1.5` 家族上**启动即崩**,而两代家族的 typecheck + 240 例单测**全绿**。 凡改动**服务/inject/cordis 组合/路由挂载**,验证必须含一次真 boot (`node scripts/boot-smoke.mjs`,§3);`upstream.yml` 每条通道已强制包含此步骤。 ## 5. 红线(违反即回滚) 1. **凭据零泄漏**:密码/passphrase/私钥内容只进 `$DSH_HOME` 注册表或 OS 钥匙串; 不写日志、不写仓库、不进 git(`.gitignore` 已覆盖 `machines.json` / `known_hosts.json`)。 2. **不碰产品 profile**:`$DSH_HOME/profiles/web` 由插件管理器重管;装/卸一律 `dsh plugin --profile web add|remove `,禁止手工编辑。 3. **不碰 3080**:一切开发验证在隔离 lab(`DSH_HOME=.dsh-lab`、端口 50599); 3080 的重启**只由用户执行**(`scripts/restart-3080.ps1`,会话外)。 4. **push/PR 归代理,tag/publish/合并归用户**(2026-09-09 用户授权):代理可 `git push` 分支、 `gh pr create`、盯 CI 并自己迭代到绿;`git tag`、`npm publish`、squash 合并进 `master`、 重启 3080 仍**只由用户执行**。**发布闸门**:`.github/workflows/release.yml` 只在 `v*` tag 上触发, 且 publish 作业挂在 `npm-publish` environment 上——**推 tag 只排队,真正的 `npm publish` 必须由 仓库所有者在 Actions 里点 Approve**。代理任何情况下都不得推 tag 或触发/批准发布,除非用户在当次 对话里明确说「发」。 5. **主机指纹默认校验**(TOFU:首次记录、变化即拒),降级必须文档警告。 6. **远程命令注入防护**:拼进 shell 的参数一律 POSIX 单引号转义。 7. **子代理禁止调用 `cordis_inspect_list` / `cordis_inspect_query`**:client 查询依赖页面应答, 页面不响应会**永久挂起**(本仓库已两次卡死)。契约核验走磁盘权威源: ① 部署包 `$DSH_HOME/profiles/web/node_modules/@deepseek-ai/*`; ② 全局安装包 `/@deepseek-ai/dsh/node_modules/@deepseek-ai/*`; ③ 本仓库 `node_modules/@deepseek-ai/*`。找不到就回来问,不要猜服务/槽位名。 **被禁的 Inspect 有磁盘等价物**:`/@deepseek-ai/dsh/node_modules/@deepseek-ai/` `dsh-cordis-client-runner/lib/client.js`(生成产物 `CLIENT_SLOT_API` / Service Catalog / Event Catalog, 含每个槽位的 registerOptions / ownerProps / standardProps / declaredBy / occupants / replaceRisk / example / source)。它是**生成产物**(文件头 `do not edit by hand`),只读引用、不可修改。 **不要手写解析脚本**:直接 `npm run slots -- --list|--key|--diff `(§3)。 **行号不是契约**:只认 `key`/字段名与符号名;引用行号必须注明它属于哪份 bundle, 不同快照的行号**不可互换**。详见 `docs/architecture.md` §6 与 `ADR-0017` §1.1。 ## 6. 写插件代码 / 改 Cordis 组合前的固定动作 - 先加载技能:`editing-cordis-compositions`(组合/挂载/host-agent 判定)、 `cordis-plugin-development`(动态插件、Service/Event/Slot/Tool)。 - **依赖**:宿主已提供的 `@deepseek-ai/dsh-*` 一律 `peerDependencies`(范围必须带 `-rc.`) + `devDependencies`(取类型);`dependencies` 只留 `ssh2`。理由:Cordis 服务身份是**模块级 Symbol**,自装副本会遮蔽宿主单例,导致 service/工具注册分裂。静态闸门会拦。 - **服务**:可选服务 `ctx.get(...)` 判空;硬依赖才 `inject`。 - **生命周期**:所有副作用(服务/事件/定时器/槽位/样式)挂 `ctx.effect()` / `ctx.on()`, 保证卸载可逆;重装后再挂一次不得抛 "already registered"。 - **数据**:只读叶子字段,不序列化 Cordis 活对象。 - **i18n**:所有面向用户/模型的文案进 `src/locale/`(命名空间 `dsw`),禁止硬编码(闸门会拦)。 - **测试**:逻辑放 `.ts`,`.tsx` 只做视图(否则沙箱内覆盖不到);见 `docs/testing.md`。 ## 7. 一轮的工作流(短分支 + PR) **分支模型**:`master` 只接受经过 CI 的提交;改动走短分支 + PR,**squash merge** 保持线性历史 (当前全历史 0 个 merge 提交,不要破坏它)。 **派发 · 接续 · 能力(INFRA-21)**: - **一轮 = 用户点名的一组 backlog ID = 恰好一个 PR**;同会话未点名的顺手改动折进同一 PR (`Refs:` 列全 ID)。代理不自行扩围、不开第二个 PR。 - **开工先跑 `npm run preflight`**,结果连同 agent 自报能力(browser-use 等探测不了的)贴进 PR / round 报告;缺能力的步骤把对应 backlog 行标 `agent-missing:` 挪 §3,**绝不静默跳过**。 - **跨 agent 接续**:交接 = WIP 提交到分支(测试红也无妨)+ backlog doing 行备注「现状 + 下一步」+ `gh pr comment` 留交接说明。接手者凭 preflight + 本文件 + backlog + PR 即可续作——项目状态 不依赖任何 agent 的会话记忆。 - **脚本化文档编辑一律用 `scripts/lib/doc-edit.mjs`**(`replaceOrThrow` / `mustInclude`)—— 裸 `String.replace` 不命中是无操作,R41–R43 三次静默丢编辑的教训。 1. 在 `docs/backlog.md` 加行(ID + `todo`,放进 §2 对应优先级块),写清目标与验收标准。 2. 状态改 `doing` 时**整行挪到 §1**,从 `master` 拉短分支:`feat/-slug` / `fix/-slug` / `chore/-slug`。 3. 小步提交,Conventional Commits,尾行 `Refs: `。 4. 验证:`npm run check`(或沙箱内 `check:static` + `typecheck` + `test:agent`)。 5. **代理 push + 开 PR + 盯 CI 到绿**(改测试/跨平台代码前先跑 §3 的 WSL Linux 复验): ```powershell git push -u origin gh pr create --title "fix(): " --body-file .github/PULL_REQUEST_TEMPLATE.md gh pr checks --watch # PR 标题 + ubuntu 22/24 + windows 22 ``` CI 红了就自己定位、修、再推,直到全绿。**合并仍由仓库所有者执行**: ```powershell gh pr merge --squash --delete-branch ``` > **PR 标题必须是 Conventional Commit**:squash 合并把它当作 master 上的提交标题, > 而闸门校验的正是这个标题(浏览器默认填分支名 → 合并后 master 必红)。 6. 合并后:整行挪到 §4,状态改 `done`/`shipped`,`npm run status`。round 只写里程碑/事故;其余用 PR 描述。拍板队列:`docs/decisions/queue.md`。 7. **发布只由仓库所有者决定**:合并进 `master` 不会发布。`release.yml` 只在 `v*` tag 上跑,且 publish 作业挂在 `npm-publish` environment 上——**推 tag 只是排队,真正的 `npm publish` 要 owner 在 Actions 里点 Approve**。所有者确认要发后: `git tag vX.Y.Z && git push origin vX.Y.Z` → 在 Actions 页面点 Approve → CI 用 OIDC 发布。 > **例外**:错别字、一行文案、纯注释这类不可能改变行为的改动,可直接提交到 `master`—— > 但 `npm run check` 不能跳。UI 改动在 PR 里附 `docs/uat/` 脚本。 ## 8. 接手三分钟 0. `npm run preflight` —— 能力预检:缺什么、什么会 blocked,一目了然。 1. `npm run status` —— 看版本、HEAD、待办分布、被挡住的项。 2. `docs/backlog.md` §1/§3 与 `docs/decisions/queue.md` —— 在做什么、被挡住的、待拍板。 3. `docs/README.md` —— 文档地图(现状 / 决策 / 档案各看哪)。 4. `docs/compatibility.md` —— 别在错误的上游家族上开工。