前言¶
每个项目都有自己的口癖:「不要写无意义的强制类型转换」「SQL 全部收敛进 model 层,service 和 handler 里不准拼」「动手前先确认现有实现」。这类话每个新会话都要重说一遍,人多了就变成提醒成本。
常见的做法是在项目里放 AGENTS.md / CLAUDE.md,但这会把约定带进版本库,同事的机器上照样生效,偶尔并不代表整个团队都同意。dsh-workspace-rules 走的是另一个方向:约定写在 <项目>/CLAUDE.local.md 里,这份文件是「本地私有」的,配合 .git/info/exclude 完全不进版本库,同时由 DSH 内置加载器在新会话开始时自动读取。插件本身只做一件事——给你一个设置面板去读写这个文件,不负责注入。
下面介绍这个插件,维护者是 winson-young,仓库地址见文末。
这是什么¶
一句话:一个 DSH 插件,用于按工作区管理 CLAUDE.local.md,并提供设置面板。
- 它解决的是「项目约定文字」的存放和加载,不解决智能体调度、工具编排这类问题。
- 注入环节不归它管:真正负责把规则塞进会话上下文的是 DSH 内置的
dsh-agent-instructions。 - 插件坏了、被跳过了,磁盘上那份
CLAUDE.local.md照样会被原生加载器读到。这一点是它设计上的兜底。
核心能力¶
1、按工作区隔离。规则文件就在项目目录下,A 项目的约定不会出现在 B 项目里,不需要额外的命名空间配置。
2、本地私有,不进版本库。配合 .git/info/exclude,同事在 Cursor、Codex、CC 里看不到任何痕迹。
3、保存时的 git 泄露防护。保存前自动补 exclude;如果文件已经被索引追踪,直接拒绝保存并提示执行 git rm --cached。子模块和 worktree 都做了兼容。
4、加载状态可判定。面板会用会话里真实的 digest 比对,告诉你「当前会话读到的规则是不是磁盘上这份」,而不是凭猜测。
5、备份不落在项目里。覆盖写入前,旧内容存到 $DSH_HOME/backups/workspace-rules/。这一点是刻意的:备份如果落在项目目录里,会让 exclude 的精确匹配失效。
6、零运行时依赖、零构建。源码即产物,装完就能用。
安装与启用¶
从 npm 安装,web 换成你想装进去的 profile 名:
dsh plugin --profile web add dsh-workspace-rules
装完重启 DSH 进程,设置页会多出「工作区规则」一节。
从 GitHub 源码安装,拿到的是最新提交,可能比 npm 上的新:
dsh plugin --profile web add github:winson-young/dsh-workspace-rules
开发期想挂本地源码:
dsh plugin --profile web add link:D:/Projects/dsh-workspace-rules
注意用 link: 而不是 file:。file: 是把源码复制进 profile,之后改代码完全不生效,而且没有任何提示。
安装范围上有一条硬约束:每个 profile 是独立环境,桌面版由 Electron 独占管理,CLI 装不进 desktop profile。桌面版想要这个功能,得从 npm 或 profile 机制走,不直接用上述命令。
典型用法¶
启用后,四步把约定落盘:
- 打开「设置 → 工作区规则」。
- 选择或填写工作区目录。
- 写下这个项目的约定。
- 保存。
约定本身是普通 Markdown:
# 编码约定
- 不要写无意义的强制类型转换
- 所有 SQL 收敛进 model 层,不要在 service / handler 里拼 SQL
- 改动前先确认现有实现,别重复造轮子
保存后的行为需要说清楚:新会话在第一步就读到最新内容;当前会话不一定。工作区层会热重载,但已经进入上下文的旧指令不会被撤回。所以不必为了生效去重开对话,但也别指望旧规则在当前窗口立刻失效。
面板底部的诊断区会告诉你几件事:当前加载状态、取自哪个会话、指令文件的加载链(只显示文件存在与否和字节数)、以及 exclude 路径。排障时从那里看起。
它怎么改动你的工作区¶
数据链路分三层,搞清楚这层关系,后面读源码和排障都会简单一点。
DSH 内置加载器 dsh-agent-instructions ← 真正负责注入
│ 读取
▼
<项目>/CLAUDE.local.md ← 数据,纯 Markdown
▲
│ 读写(HTTP)
本插件 dsh-workspace-rules ← 只管编辑
host: /api/workspace-rules
client: 设置页面板
host 端(lib/index.js)通过 ctx.connection.fetch.register 注册 GET/POST /api/workspace-rules,走 Connection 的 /api 通道,因此自动落在 Host/Origin 栅栏和浏览器鉴权之内。client 端(lib/client.js)手写 window.__ModuleLoader__.load bundle,require('react') 之后 createElement 渲染,没有 JSX、没有打包器。
写盘有几道保护:临时文件加 rename 做原子写,遇到 EPERM / EBUSY 会重试,重试还不行就降级为直写并在响应里标记;并发用 baseMtimeMs 做乐观锁,冲突返回 409,并区分 appeared / changed / removed 三种情况。
限制与注意¶
这些都是 README 里明确写出来的限制,装之前值得先过一遍。
- 没有会话级隔离。同一工作区的所有会话共享同一份规则文件。
- 只管理
CLAUDE.local.md这一个文件名。同目录下如果还有AGENTS.md、CLAUDE.md、AGENTS.local.md,面板只提示它们存在,不改动、不预览。 exclude检查是保存时刻的一次性动作。之后别人改了忽略规则,插件不知道。手动补一条也简单:
echo "CLAUDE.local.md" >> .git/info/exclude
用 .git/info/exclude 而不是 .gitignore,理由前面说过了:前者不进版本库。
- 当前目录不在 git 仓库里时,插件降级为警告,不会阻断保存。如果你只是想留一份本地草稿,这没问题。
- 原生加载器按目录链加载,从项目根扫到当前会话目录的每一层,越具体的文件优先级越高。这是 DSH 的行为,不是本插件的行为,别在这里找它的配置项。
兼容性方面,README 给出的范围是 DSH >= 0.2.1-rc.5 < 0.3.0-0,Node >= 20,已在 0.2.1-rc.5 和 0.2.2-rc.1 上实测,运行时依赖为零。这个范围写在 package.json 的 engines.dsh 里,DSH 在加载 bundle 前做准入判断:范围外的版本(0.3.0 起)会直接跳过本插件并在诊断里写明原因,而不是装进去之后静默出错。个中缘由 README 也解释得直接:不声明 engines.dsh 又不写 peerDependencies,等于对所有版本宣称兼容,哪天宿主改了 API,坏的是运行时,用户只看到面板打不开。
是否适合你¶
适合的人:同时跑多个项目、每个项目都有自己的编码约定、不想再每个会话开头重复一遍的人;以及想把约定留在本地、不希望它进了公共仓库的人。
不适合的人:需要按会话隔离规则、或者需要团队共享一份统一约定的场景。前者它不做,后者应该走 CLAUDE.md 这类进版本库的文件,插件本身不负责管理那些文件。
安装前照例做两件事。一是确认来源,从 GitHub 源码安装时最好先看看 lib/index.js 和 lib/client.js,插件是以当前 DSH 进程权限运行的。二是知道许可证是 MIT。仓库目前星标为 0,规模不大,源码本身不长,值得花十分钟读完。
结尾¶
它的价值很集中:把「项目私有约定」这个词落到一个具体文件上,换来的是新会话自动载入、本地不进版本库、坏掉也不影响原生注入。功能面不大,README 里的限制也说得足够坦白。想试用的话,目录页和源码都在下面。
- 目录页:https://www.skillhub.cn/plugins/winson-young/dsh-workspace-rules
- GitHub:https://github.com/winson-young/dsh-workspace-rules