dsh-ian-rules
DSH(DeepSeek Harness)个人插件:把你的项目开发规则交给 agent —— 右侧栏面板可视化维护(全局 + 按项目),并自动注入每个会话的系统提示。
- Stars
- 0
- Language
- JavaScript
- Created
- Sep 21, 2026
- Updated
- Oct 6, 2026
Introduction
dsh-ian-rules · 项目开发规则
DSH(DeepSeek Harness)开源插件(MIT):把你自己的一套项目开发规则交给 DSH,让 agent 在开发项目时自动遵守。代码可自由使用、修改与分发,也欢迎读源码学习研究。
- 面板:Web GUI 右侧栏新增「开发规则」页(与「Docker 容器」并列,走 DSH 原生
sidebarRightTabs+sidebar.right.pane.tab契约),可视化维护规则(全局 + 按项目)。 - 自动注入:每个会话组装系统提示时,按该会话的工作目录解析生效规则并注入,无需手工提醒。
- 对话内维护:另带
ian_rules模型工具,直接说「把这条记进开发规则」也能落库。 - 即时生效:保存后正在运行的会话下一步就生效,不用重启 DSH。
一、功能
| 能力 | 说明 |
|---|---|
| 全局规则 | 对所有会话生效,是「凡项目都适用」的底线约定 |
| 项目规则 | 每个项目在页签栏里有自己的一页;会话工作目录落在项目路径之下即命中;多个命中时取路径最长(最具体)的一个;工作目录走软链时自动用 realpath 再匹配一次 |
| 页签 | 3 + N:全局规则 / 项目(名册)/ 每个项目一页 / 效果预览;页签条横向滚动不折行 |
| 追加 / 覆盖 | 项目可「追加」(全局 + 本项目)或「覆盖」(只生效本项目规则);覆盖模式下注入文本会写明被挡掉了多少条全局规则 |
| 分组 | 每条规则可填分组名;面板按分组归拢成二级分块,注入文本里是二级标题 ### 组(第一级是 ## 全局规则 / ## 项目:名(路径));组与组之间的先后取组名第一次出现的位置 |
| 搜索 | 按标题 / 正文 / 分组过滤当前页签(筛选时禁用上移下移,避免顺序错乱) |
| 卡片版式 | 规则卡两列式网格:栏宽够(内容盒 ≥ 520px)才两列,窄栏单列;规则卡 / 项目卡 / 空态 / 上手块共用同一套形状变量 |
| 单条开关 | 每条规则可单独停用(保留内容、不注入);总开关可整体关闭注入 |
| 删除保护 | 删除规则 / 项目条目需要二次确认(3 秒内不确认自动还原) |
| 保存冲突检测 | /save 带 revision;磁盘被别的会话或手工编辑改过时返回 409,面板提示「载入磁盘版本」或「用我的改动覆盖」,不会静默覆盖。revision 只在内容真的变了时前进 —— 插件自己写盘触发的目录事件不会把 revision 顶高一格(否则「保存成功后再保存一次」会假报冲突,真冲突的提示也就没人信了) |
| 外部改动同步 | 面板在后台按 revision 轮询:没有未保存改动就静默跟上,有改动就明确提示 |
| 备份 | 每次保存前把上一版写到 ian-rules.json.bak |
| 导入 / 导出 | 导出 JSON / Markdown;导入 Markdown / JSON(可替换或合并,按 id 与标题+正文去重) |
| 成本提示 | 注入预览显示字符数与估算 token,并列出最占预算的 5 条规则;每条规则卡片还标出它给每轮对话增加的字符数 |
| 注入预览 | 输入任意目录,看该目录下真正注入的文本(含未保存修改、命中项目、是否被截断) |
| 上限保护 | 单次注入文本上限 12000 字符,超出时先裁全局、保住项目规则,并在提示开头写明少了哪几条(不再静默丢失) |
| 按场景注入 | 标了「按场景」的规则不常驻系统提示,只在命中当前任务时送到对话末尾(默认关闭,在「更多 → 按场景注入」里开) |
ian_rules 工具 | action = list / lookup / add / update / remove,支持 global / project 归属与分组;list 默认只给有界摘要,完整注入原文要显式 full: true、列举全部项目要 all: true;lookup 按关键词取某几条的正文(工具结果会永久留在对话历史里,所以默认不能倒全文) |
| 插件更新 | 面板顶部有更新时才出现一条细提示,点「更新」即可更新本插件(走插件市场公布的同源更新 API,含进度、失败原因与回滚);没装插件市场时整块隐藏 |
| 接口硬化 | 所有接口校验 Origin / Sec-Fetch-Site(跨站 403),POST 要求 Content-Type: application/json(否则 415) |
没有生效规则时注入空串 —— 等于这个插件「隐身」。
二、安装
本插件是标准的 DSH profile bundle(双半体:宿主 + 浏览器),需要已安装 dsh CLI(npm install -g @deepseek-ai/dsh)与 pnpm。按你的网络从下面两条来源里选一条:
# 能直连 GitHub
dsh plugin --profile web add github:Grant-Felix/dsh-ian-rules
# 国内走 Gitee 镜像(pnpm 没有 gitee: 简写,用完整地址)
dsh plugin --profile web add git+https://gitee.com/Grant-Felix/dev-rules.git
# 仓库开发调试:链到本地检出
dsh plugin --profile web add link:$PWD
dsh plugin 就是 pnpm 的透传:装完它会按包名把插件自动登记进 dsh.profile.bundles,不必手工改 profile 的 package.json。装完重启该 profile(侧边栏底部 ↻),右侧栏页面列表里就会出现「开发规则」。
为什么还没有 npm 包可以
add:dsh-ian-rules这个包名在 npm 上没有被占用(2026-10-01 实测registry.npmjs.org/dsh-ian-rules→ 404),但它还没发上去 —— 首发要走一次性凭据,且 trusted publisher 只能配在「已存在的包」上(前置条件记在.github/workflows/publish.yml顶部)。所以对外仍以 git 来源分发:GitHub 给直连用户,Gitee 给国内用户。
改动生效范围(本部署实测):
| 改了什么 | 怎么生效 |
|---|---|
宿主半体 lib/index.js、lib/rules.js | 重启 profile(侧边栏底部 ↻) |
浏览器半体 lib/client.js | 同样是重启 profile |
为什么客户端改动也要重启:DSH 的
client-modules在启动时把每个插件的 client bundle 读进内存(readFileSync+ 内容哈希当rev),之后只按「已登记的 URL」出字节;文件内容变化要经 HMR watcher 的rebuilt(id)才会重新登记,而 watcher 只有在源码检出里跑着pnpm run dev:web时才装得上。本机是安装版部署、没有跑 dev watcher,所以硬刷新页面拿不到新 bundle,必须重启一次 profile。待复核(2026-10-01):这条结论与一次沙箱实测不符 —— 在沙箱(
link:安装、独立DSH_HOME)里,改完lib/client.js只做一次页面加载(不重启实例)就能拿到新 bundle:往 CSS 里加一行注释,刷新页面后style[data-plugin="ian-rules"]里就有了它。差别可能来自安装形态(link vs 安装版)或页面加载与硬刷新的区别,尚未在本机日常 profile 上复现,所以上表暂时按老结论执行(拿不准就重启,代价只是几秒)。
升级:面板顶部在有新版本时会自动出现一条提示,点「更新」即可(走插件市场公布的同源更新 API;没装插件市场时这条提示不出现)。也可在插件市场的「更新」里做(它按 profile 的 lockfile 锁定的提交与远端 HEAD 比对,并自带 git 更新的回滚),或命令行 dsh plugin --profile web update dsh-ian-rules(重新解析到分支最新提交)。卸载:dsh plugin --profile web remove dsh-ian-rules,重启。规则文件会留在 $DSH_HOME/ian-rules.json。
三、数据
规则存在 $DSH_HOME/ian-rules.json(默认 ~/.dsh/ian-rules.json),原子写入(临时文件 + rename);保存前上一版留在 ian-rules.json.bak。
更名迁移(dsh-dev-rules → dsh-agent-rules → dsh-ian-rules):启动时若发现新名字的文件不存在、而旧名字的还在,会按由新到旧的顺序取第一个存在的(
agent-rules.json优先于dev-rules.json)复制一份到ian-rules.json(原文件保留,回退到旧版本插件仍可读),面板的「更多 → 文件位置」会提示这一次迁移。确认新文件正常后,旧的那份可以自行删除。包名也跟着改了,所以升级时依赖键也要换:
dsh plugin --profile web remove dsh-agent-rules再dsh plugin --profile web add github:Grant-Felix/dsh-ian-rules(否则 profile 里会同时留着两代,bundle 阵容里还挂着旧包名)。旧包名的数据文件按上面那条规则自动搬过来,规则不会丢。
这份文件属于使用者本人,不在本仓、不受本仓许可约束(见
NOTICE.md)。插件只读写本机这一份文件,不联网、不上传。
{
"version": 2,
"enabled": true,
"sceneMatching": "off",
"global": [
{ "id": "g1", "title": "提交前跑测试", "content": "npm test 必须绿", "group": "提交", "enabled": true, "mode": "always", "tags": [] }
],
"projects": [
{
"id": "p1",
"path": "~/项目/foo",
"label": "示例项目",
"enabled": true,
"mode": "append",
"rules": [
{ "id": "r1", "title": "本项目用 pnpm", "content": "禁止 npm install", "group": "依赖", "enabled": true, "mode": "auto", "tags": ["依赖", "包管理"] }
]
}
]
}
mode:项目级是append(默认)或override;规则级是always(默认,常驻注入)或auto(按场景注入,见第五节)。tags:规则的匹配标签,最多 8 个、每个 24 字符;只在auto模式下参与匹配,但任何时候都可以填。sceneMatching:off(默认)或auto—— 按场景注入的总开关。- 老文档(version 1)读进来会自动补齐:每条规则补
mode: "always"、tags: [],文档补sceneMatching: "off"。补齐之后注入文本与旧版逐字节相同(这条由test/rules.test.mjs的黄金快照钉住),所以升级不会让任何用户的提示词缓存失效。 - 命中多个项目时不再是「只取最具体的一个」:会话目录命中的所有项目按路径由短到长串成一条链(父级在前、子级在后),规则依次叠加。链上最后一个
override的项目会把它之上的所有层(含全局规则)一并挡掉 —— 所以「父 override + 子 append」=父与子都生效、全局不生效;「子 override」=父级与全局都不生效。单层项目时与旧行为完全一致。 - 路径支持
~;保存时统一规范化成绝对路径,尾部分隔符会被去掉;Windows 风格路径(C:\…)无论宿主平台都按 Windows 规则收敛。 - 目录为空的项目条目不会被保存 —— 面板保存前会提示补全或删除。
- 手工编辑该文件会被宿主自动重载(监听目录 + 15 秒轮询兜底)。
四、面板
位置:右侧栏的页面列表里点「开发规则」(与「文件 / 终端 / 浏览器 / Docker 容器」并列),内容在右列打开。
页签是 3 + N 个:全局规则 / 项目(名册)/ 每个项目一页 / 效果预览。项目一多,把它们的规则全塞在同一个页签里,就得在几个大卡片之间反复上下找;一个项目一页之后,页签栏本身就是「第一序列」的目录。页签条横向滚动,不折行。
打开就能用的三步
- 面板顶部吸顶条写明「这里是干什么的」+ 当前共几条规则;右侧只有一个主动作 保存并生效(有改动才可点,
Ctrl/Cmd + S同效)—— 导出 / 导入 / 重载都是低频动作,一并收在「更多」里,不跟它抢位置。 - 一条规则都没有时,面板给出两步上手说明 + 「先插入 4 条虚构示例」(同一目录风格一致 / 依赖升级单独提交 / 配置项集中管理 / 发布前核对版本号),插进来直接改成自己的。示例入口只有这一处,规则列表下面不再重复摆一个。
- 「项目」页只管每个项目的身份(目录 / 别名 / 与全局的关系 / 启用 / 删除),规则本身在它自己那一页里编辑——同一个字段只有一处可改。新建项目后会自动跳到它的规则页。
日常操作
- 规则卡片按分组归拢成二级分块(组名 + 条数),与注入给 agent 的文本同序;一条具名分组都没有时不摆「未分组」的形式标题,免得全是噪音。
- 卡片是两列式网格:单张卡片窄下来,同一段文字的横向跨度短了,眼睛不用长距离横扫。栏宽够(内容盒 ≥ 520px)才两列,窄栏自动退成单列 —— 两列挤到 200px 一栏反而更难读。
- 规则卡片:标题(一句话)→ 正文(具体怎么做)→ 分组(可选)/ 标签(可选)+「生效」+「按场景」+ 上移下移 + 二次确认删除;卡片只标「约 N 字」(这条规则给每轮对话增加的上下文量,「按场景」的规则会标成「按场景 · 约 N 字」,因为它不是每轮都注入)。标题与分组/标签平时不描边(读起来是文字,不是并排的输入框),鼠标移上去或聚焦时才浮出可编辑的形状。
- 上移下移只在同一组内换位(组与组之间的先后由「组名第一次出现的位置」决定,跨组移动做不到,按钮就在组边界禁用,而不是点了没反应)。
- 停用的规则:卡片加虚线边 + 标题旁标「已停用」,只有正文变灰 —— 「生效」勾选框和右侧工具保持正常对比度,重新启用时点得着。
- 每个项目页顶部一条只读身份栏(名字 / 目录 / 与全局的关系 + 「改目录 / 别名 / 关系 →」),要改身份就跳去「项目」页。
- 「效果预览」:选一个目录点「查看」,用大白话告诉你:命中了哪个项目、用了几条规则、全局规则有没有被挡掉、一共多少字 / 约多少 token、是否被截断,下面给出实际注入的原文(等宽正文限宽 84 字符,栏拉宽也不会变成一行 150 字)。
- 顶部「生效中 / 已停用」小胶囊就是总开关(点一下切换),不用去翻设置。
- 冲突与导入都弹横幅并给出明确选择:「载入磁盘版本(放弃我的修改)/用我的修改覆盖」、「替换现有规则/合并进来/算了」。
- 保存有两个档:顶部的 保存并生效(立刻生效,正在跑的会话下一步换新规则)与「更多 → 维护」里的 保存(只对新会话生效)。后者把已经在跑的会话钉在它们手里的旧规则上,新会话才用新规则 —— 规则一改系统提示就变了,正在跑的会话要重算提示缓存,长会话上这一下可能比一整天所有规则的开销还贵;不想付这个代价就选它。选了之后顶部状态行会写「N 个正在运行的会话仍在使用保存前的旧规则」,旁边一个「让它们改用新规则」按钮一键解冻。
- **更多(折叠)**里是低频功能:插件更新(手动查一次本插件有没有新版本)、导出备份(Markdown / JSON)、从备份导入、保存(只对新会话生效)、放弃修改并重新载入、文件位置、使用说明。
- 搜索与分组筛选只在有规则卡片的页签(全局 / 每个项目)出现,且只在该页规则较多(> 6 条)时才出现,避免一上来就堆控件;「项目」名册与「效果预览」都不吃筛选 —— 在那里摆一组不生效的控件,用户会以为结果被筛过。只要筛选条件还在,这一行就不会消失,否则规则会被静默藏起来,连清空条件的地方都没有。
- 卡片风格统一:规则卡 / 项目卡 / 空态 / 上手块 / 身份栏 / 横幅共用同一组形状变量(圆角、描边、底色、内边距),差异只用描边样式与颜色表达(例如停用=虚线),不另起一套形状。
- 面板渲染若抛错,会就地显示错误原文(错误边界),而不是整块空白。
五、注入形态
规则分四层送达,位置是刻意的:
| 层 | 内容 | 走哪条通道 | 变化频率 |
|---|---|---|---|
| L1 常驻 | mode: "always" 规则全文(含项目链) | 系统提示 section(plugin:ian-rules,order 100) | 只在你改规则时 |
| L2 目录 | mode: "auto" 规则的标题 + 标签 | 同一个系统提示 section | 只在你改规则时 |
| L3 场景 | 命中当前任务的 auto 规则正文 | 消息尾部一条 user 消息(<system-reminder>) | 每轮命中集合变化时 |
| L4 按需 | 想看哪条就用工具取 | ian_rules 的 lookup | 模型决定 |
位置纪律(本插件最重要的一条):逐轮会变的内容只能放消息尾部,绝不进系统提示。 系统提示一变,它之后的全部内容(工具定义 + 整段对话历史)都要重算提示前缀;在有前缀缓存的供应商上,缓存命中与未命中的输入价差是 50 倍(按供应商价目,与用量无关)。放错一次,代价是本插件规则本身开销的几十到几百倍 —— 按 7k token 规则集 / 100 轮会话的量级估算,同一个会话会从 ¥0.002 变成 ¥0.86。L3 因此走
agent/pre-step往消息尾部追加,而不是systemPrompt.context()/section。
系统提示 section 的正文是常量 {{ian_rules_body}};真正的文本由同名提示变量提供。
为什么要绕一道变量:DSH 会对 section 正文做严格的
{{变量}}插值,遇到未知 / 畸形引用会直接抛错,而这一步发生在插件回调之外——用户规则里的{{placeholder}}会把整个模型步打挂。变量值不会被二次扫描,所以用户写什么都不会破坏提示组装。
渲染形如(两级标题:第一级是「全局 / 哪个项目」,第二级才是你的分组):
# 项目开发规则
以下是本机用户维护的开发规则……如有冲突,以后者为准。
## 全局规则
### 提交
1. **提交前跑测试**
npm test 必须绿
## 项目:示例项目(~/项目/foo)
### 依赖
2. **本项目用 pnpm**
禁止 npm install
- 第一级
##是全局规则与命中的那个项目:项目名写进标题(## 项目:别名(路径),没有别名就只写路径),不再单占「适用项目 / 规则来源」两行 —— 标题本身就说清了这段规则从哪来。 - 二级标题
###是你的分组;同组规则并到一起,编号跨两级连续。分组顺序取「组名第一次出现的位置」(面板上看到的顺序与它一致)。 - 未命中项目时明确写「当前目录未匹配到项目规则集,以下为全局规则」;覆盖模式下不出现
## 全局规则那一段,改为在项目标题下写明「本项目为覆盖模式:全局规则(N 条)在本项目内不生效」—— 避免 agent 误以为规则不存在。 - 打开「按场景注入」后,多出一段
## 按场景规则(目录):auto规则的标题与标签(最多 60 条),正文不在系统提示里。
按场景匹配怎么算(lib/scene.js,零依赖纯函数):
- 信号:只用本步的真人消息(最近 3 条),外加
runtime-context快照的排除项。工作目录不进信号 —— 它只用来决定候选规则范围(effectiveRules已按 cwd 过滤),进信号会把目录名喂给匹配器(~/项目/…里的「项目」曾经把标题含「项目」的规则每轮都喊出来)。 - 分词:ASCII 转小写按词切;中文用二元字组(「提交前跑测试」→
提交/交前/前跑/跑测/测试)+ 单字兜底,不引分词器。只认汉字 —— 标点与全角形式不算词(否则一个「,」也能参与打分)。 - 打分:命中标签 ×3 / 标题 ×2 / 正文 ×1,单字再乘
0.25降权,最后按正文长度归一化(否则一条 4000 字的巨型规则通吃所有场次)。 - 零词命中不算相关:一个词(二元字组 / ASCII 词)都没命中时直接判 0,单字只能给已成立的匹配加分。这是必要的 —— 单字权重再低,跨「标签 + 标题 + 正文」三处求和也会翻过阈值(实测:
这段代码复现不了靠段/现/复/不四个常用字拿 2.81 分,误命中一条毫无关系的规则)。 - 阈值
1.5,每轮最多 8 条、最多 2000 字。 - 轮内 sticky:同一轮的第 2 步往后不再有新的用户消息,命中的集合只增不减,否则规则会在步与步之间抖动;跨轮重新匹配,避免第一轮的话题粘到最后一轮。
- 集合没变就不重复注入:命中集合的指纹(sha256 前 16 位)一样就不再追加消息。指纹跨轮保留(否则同一话题连着聊几轮,历史里就躺着几份一模一样的正文);只有在历史被压缩过(
surface.contentGeneration变了)时才连指纹一起重置 —— 那时旧的那份可能已经被丢掉,必须重发。
L3 送出的文本形如:
<system-reminder>
本机用户维护的「项目开发规则」中,与当前任务相关的这几条(按场景匹配后送达,不是全部规则):
### 依赖
1. **依赖升级单独提交**
升级依赖不要和功能改动混在同一个提交里
另有 1 条按场景规则本次未命中;需要时用 ian_rules 工具按关键词取用。
</system-reminder>
- 「另有 N 条未命中」这句是必须的:让漏命中从静默失效变成可发现,同时告诉 agent 还有
lookup这条路。 - 面板「效果预览」里的场景模拟器可以输入一句话,看它会命中哪几条、差一点命中的那几条各是多少分——匹配器是纯函数,模拟不改任何状态。
- 「更多 → 按场景注入 → 记录命中日志」打开后,每次注入往
$DSH_HOME/ian-rules.hits.jsonl追加一行(会话 / 轮次 / 工作目录 / 命中的 id)。默认关,它只对回头调阈值有用。 - 命中多个项目(monorepo)时,父子规则拼在同一段里、父级在前,并明确写「本段含继承自上层项目的规则(父级在前):/repo → /repo/packages/a」—— 否则 agent 会把父级规则当成当前项目的规则。
- 超出单次注入上限(12000 字符)时:先裁全局规则(项目规则更具体、更该活下来),从各自末尾往前裁,至少留一条;并在提示开头写明「本轮有 N 条未注入:标题一 / 标题二 ……」。这句必须在开头 —— 放尾部会被截断自己切掉,等于没写。规则数没超限时输出与旧版逐字节相同。
六、接口
| 方法 | 路径 | 作用 |
|---|---|---|
| GET | /ian-rules/state | 读当前文档 + 元信息(文件、备份、revision、规模、错误、被「下个会话生效」钉住的会话数) |
| GET | /ian-rules/workspaces | 项目路径下拉的数据源(工作区注册表 + 活动会话目录) |
| POST | /ian-rules/save | { doc, revision, applyMode? } 保存;revision 过期 → 409 + 当前文档 |
| POST | /ian-rules/reload | 从磁盘重新读取 |
| POST | /ian-rules/preview | { doc?, path } 渲染注入文本 + 字符 / token / 逐条体积 + 命中链 + 按场景目录 |
| POST | /ian-rules/scene | { path, text } 场景模拟:这句话会命中哪几条按场景规则、各多少分、差一点的是哪几条 |
| POST | /ian-rules/export | { doc } → Markdown 与 JSON 文本 |
| POST | /ian-rules/import | { text } → 解析 Markdown 或 JSON 得到文档 |
排障示例:curl -s 127.0.0.1:3080/ian-rules/state | head -c 400
/save 的 applyMode:now(默认)=立刻生效,正在运行的会话下一次请求就用新规则;next-session=只对新会话生效,已经在跑的会话继续用它们已经吃进上下文的旧规则。后者是零代价的:规则一改系统提示就变了,而正在跑的会话下一次请求要重算提示缓存(长会话上这一下可能比一整天所有规则的开销还贵)。面板的入口在「更多 → 维护 → 保存(只对新会话生效)」;被钉住的会话数在顶部状态行里显示,旁边有「让它们改用新规则」一键解冻。不带 doc 的 /save 保存的是内存里的当前文档(用于只改生效方式、不动规则)。
安全边界:以上接口只接受同源请求(跨站 Origin / Sec-Fetch-Site 直接 403),POST 必须 Content-Type: application/json。但同机的其它本地进程仍可无凭据访问(DSH 的 webServer 不对插件路由做登录鉴权)——规则内容会进模型提示,别把不能外发的东西写进去。
七、开发
node --test # 53 个用例
npm run check # 语法检查 + 身份自检 + 全部测试
npm run check:identity # 只跑身份自检
名字只有一处出处(package.json 的 name),其余标识都由它派生(slug = 包名去掉 dsh-)。scripts/check-identity.mjs 会把这几处对一遍:cordis patch 的 name、客户端 __ModuleLoader__.load({ id })、插件市场认的 PACKAGE_NAME、页签 TAB_IMPL_ID、路由 /ian-rules、section plugin:ian-rules、提示变量 ian_rules_body、数据文件 ian-rules.json、页签 kind —— 它们必须一致。客户端注册 id 与包名不一致时,界面启动直接报「Failed to load plugins」,而宿主接口一切正常,只 curl 接口的隔离自检根本抓不到这种错(这个坑踩过一次,见 test/client.test.mjs 里那条按包名对齐的用例)。
同一个脚本还守住两件容易被「全局替换」误伤的事:
- 历代更名前(
dsh-dev-rules、dsh-agent-rules)的旧名字不许再出现在代码与配置里。允许留下的例外逐条写在脚本的ALLOWED里 —— 目前 18 条,每条都要说明为什么可以不改(数据文件迁移的来源名、页签 kind 的兼容注册、Gitee 镜像地址……)。新增一条等于承认多欠了一笔账。 - Gitee 镜像地址必须仍是
dev-rules:镜像没随更名改动,而「把文档里所有旧名字换成新名字」这种操作会顺手把它改掉 —— 那是文档里国内用户的安装路径,本地测试全绿也发现不了。写这条守卫时我自己就误伤了一次。
于是改名=三步:改 package.json 的 name → 把旧名字补进两个 lib 里 LEGACY_* 那几行(历代名字的清单,由新到旧)→ 跑 npm run check:identity 看还差哪里。
改完先在隔离沙箱里验,别拿日常在用的那个 profile 试。 宿主半体是在 profile 启动时加载的:一个有问题的改动足以让整个 DSH 起不来,那时你连界面都进不去,只能去终端里拆插件。
npm run sandbox # 在 .sandbox/home 里装本地检出并起一个实例(默认 :3199,Ctrl-C 结束)
npm run sandbox:check # 只做自检 + profile 组装,不启动(提交前跑这个最快)
npm run sandbox:clean # 删掉沙箱
# 想验「使用者装到的到底是什么」:把来源换成发布的那份再起
DSH_SANDBOX_SOURCE=github:Grant-Felix/dsh-ian-rules npm run sandbox
DSH_SANDBOX_SOURCE=git+https://gitee.com/Grant-Felix/dev-rules.git npm run sandbox
沙箱有独立的 DSH_HOME,所以它读写的是自己的 ian-rules.json,不会碰你的真实规则文件;脚本还会拒绝把沙箱 home 指到真实 home。默认端口可用 DSH_SANDBOX_PORT 改。
lib/rules.js纯逻辑(规范化 / 路径匹配 / 生效规则 / 渲染 / 分组 / token 估算 / Markdown 往返),宿主、面板与测试共用;路径匹配的 win32 分支通过 platform 参数可测。lib/index.js宿主半体:存储、提示变量注入、按场景注入(agent/pre-step)、/ian-rules/*接口、ian_rules工具。lib/scene.js场景匹配器:分词、打分、挑选(纯函数、零依赖、可node --test直跑)。lib/client.js浏览器半体:手写的window.__ModuleLoader__.load({ id, factory })bundle,只依赖react,不需要打包器;纯函数内部件(token 估算 / 导入合并 / 更新提示判定)通过exports.__internal暴露给测试。scripts/sandbox.sh:上面那套隔离环境的实现(独立DSH_HOME+ 独立端口 + 安全闸)。- 测试:
test/rules.test.mjs(逻辑 / 两级标题渲染)、test/host.test.mjs(接口 / 备份 / 409 / 403 / 415 / 400 / 软链回退 / 工具 / 多字节请求体跨块解码 / revision 不被自己的写盘事件推高)、test/client.test.mjs(槽位接线 / 服务晚出现 / 内部件 / 两列网格与卡片统一的源码级守卫)。 - CI:
.github/workflows/ci.yml在 node 20 / 22 / 24 上跑语法检查 + 测试。
八、代码托管
本仓遵循「本地 Forgejo 开发 / GitHub 与 Gitee 对外并受理反馈」的三平台分工:代码与提交历史以本地 Forgejo 为准,两个公开平台只做对外窗口与镜像。
| 平台 | 角色 | 状态 |
|---|---|---|
| 本地 Forgejo(私有,走回环) | 开发主仓,代码与历史以它为准 | 已建仓并推送(默认分支 main) |
| GitHub https://github.com/Grant-Felix/dsh-ian-rules | 对外窗口 + 反馈受理 | 已发布(公开,MIT) |
| Gitee https://gitee.com/Grant-Felix/dev-rules | 国内镜像 + 同样受理反馈 | 已发布(由 GitHub 单向同步) |
代码单向流动:本地 Forgejo → GitHub → Gitee,禁止把某个平台的提交反向直推到另一个平台(会造成历史分叉与重复改动)。两个平台上的 Issue 与 PR 都一样受理,但同一个问题只在先提出的那一侧开正式讨论,另一侧贴链接引导过去,避免两边各说各话。
2026-10-05 更名:GitHub 仓库由
dsh-agent-rules更名为dsh-ian-rules(GitHub 对旧地址自动重定向),插件包名与全部派生标识(slugian-rules、路由/ian-rules、数据文件ian-rules.json、页签 kind、提示变量ian_rules_body、模型工具ian_rules)同步更换;上一代的名字只留在迁移与兼容那一小块里(见前文「更名迁移」)。Gitee 镜像地址仍是dev-rules,未随更名改动。
推送凭据按平台分开配置:每个 host 各有一个 git credential helper,且都校验 host=、只对自己那一个平台应答,其它 host 一律静默退出 —— 否则会把 A 平台的令牌回给 B 平台。
发版版本号用发布日期式:<YY>.<M>.<D>-<当日序号>,如 26.9.21-1(2026-09-21 当天第 1 个版本),同日第 2 个是 26.9.21-2;年月日不补零(26.09.21-1 不合法——semver 禁止数字标识符带前导零)。标签、清单 version、Release 标题与产物名用同一串(tag 加 v 前缀),三个平台推同一个 tag。
-x在 semver 里是 prerelease,两点要记住:一直用它、别与不带序号的26.9.21混用(排序上26.9.21-1 < 26.9.21,混用会把「第几个」和「新旧」搞反);发布到 npm 必须显式给 tag(npm publish --tag latest),否则 npm 拒绝发布 prerelease。
代码与规则内容分开。 本仓只装插件(开源,MIT);作者本人维护的规则内容不在本仓、也不随本仓分发,只存在于作者本机。这条边界由文件位置本身保证,不靠约定去守 —— 详见
NOTICE.md。
九、许可
代码开源、规则内容闭源:
| 范围 | 许可 |
|---|---|
本仓源代码(lib/、test/ 等) | MIT(见 LICENSE) |
| 本仓内的示例规则 | 随代码 MIT —— 全部虚构,不是作者的真实规则 |
| 作者本人的规则内容 | 不开源,也不在本仓:只存在于作者本机的 ~/.dsh/ian-rules.json |
| 使用者自己的规则内容 | 归使用者所有,与本项目许可无关 |
范围与边界见 NOTICE.md。