dsh-workspace-rules: Solidifying Project-Specific Coding Conventions into CLAUDE.local.md

前言

每个项目都有自己的口癖:「不要写无意义的强制类型转换」「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 机制走,不直接用上述命令。

典型用法

启用后,四步把约定落盘:

  1. 打开「设置 → 工作区规则」。
  2. 选择或填写工作区目录。
  3. 写下这个项目的约定。
  4. 保存。

约定本身是普通 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
羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

Xiaoye