dsh-semantic-drift
一份按项目维护的业务概念词表、只读命名漂移扫描,以及注入每个 Agent 提示的同一套命名规则。
安装
dsh plugin --profile web add dsh-semantic-drift
沙箱测试结果
| 阶段 | 结果 |
|---|---|
| 1 安装 | ✓ |
| 2 列装 | ✓ |
| 3 挂载 | ✓ |
| 4 启动 | — |
四阶段在隔离沙箱里跑:安装(有网)→ 列装 → 挂载 → 启动(后三步断网)。
README
dsh-semantic-drift · 语义漂移词表
[English](README.md) · 简体中文
一个 DSH 插件:用一份可版本化的词表记录项目的业务概念,只读扫描出代码里把它们写得不一样的地方,并给人和 Agent 一个地方去决定"哪种写法才算数"。
DSH 中显示的名字是 语义漂移词表(英文 Semantic Drift Vocabulary)。
目录
- [这个想法从哪来](#这个想法从哪来)
- [它做什么](#它做什么)
- [它不做什么](#它不做什么)
- [安装](#安装)
- [开始使用](#开始使用)
- [各模块信息](#各模块信息)
- [词表文件](#词表文件)
- [检测器](#检测器)
- [Agent 工具与提示注入](#agent-工具与提示注入)
- [面板](#面板)
- [配置](#配置)
- [工作详细信息](#工作详细信息)
- [Token 成本](#token-成本)
- [保证](#保证)
- [限制](#限制)
- [开发](#开发)
- [许可](#许可)
这个想法从哪来
思路来自 kurapica/schema-node-csharp,它主张的三件事是本插件的前提:
1. 语义应当是可版本化的独立实体,而不是每次从代码里重新推导;
2. 有限可枚举的词汇表比无限类型系统更容易保持正确;
3. 漂移必须可观测、可裁决,而不是指望团队靠记忆维持。
但不借实现:没有 Meta/Property/Relation 建模,没有语义编译,没有代码生成。只有一份 YAML、一个只读扫描器、一个面板、五个 Agent 工具。
它做什么
它记住"东西叫什么"。 每个概念一条记录:点分 id、人怎么称呼它(术语)、种类、规范写法(必要时还有类型与单位)、一句说明。这份文件跟着项目走版本,所以一次改名就是一次可 review 的 diff。
它找出代码里不一致的地方。 只读扫描把代码树与词表对照,报告:
- 同一个类型被定义了两遍——比较成员名,也比较成员取值;
- 两份拷贝已经分叉:少了成员、多了成员、枚举值被互换;
- 看起来是拷贝、其实是参数化派生,不能合并;
- 规范名的拼写变体,包括"词表里的写法在代码里根本不存在";
- 术语出现在注释或资源里,但代码里没有对应符号。
词表为空时也能用:结构性检测器拿代码和代码比较,所以新项目在登记第一个概念之前就有结果。
它给"决定"一个落点。 接受一条例外要写理由、选最小范围;之后可以改范围、改理由、移除、撤销,每条都显示它当前真正静音了多少条发现。这个决定是词表里的一行,而不是某个人脑子里的记忆。
它把词表放到 Agent 面前。 每个会话收到一节系统提示:所在工作区的概念,以及配套规则——改名前先查、名字与规范符号逐字一致、缺概念先登记、刻意偏离要写理由记录。五个工具让 Agent 读取、登记、扫描、裁决。
它会消耗 token,而且可以关掉。 提示小节是持续性的成本;面板设置里的开关可按 profile 关闭它,扫描本身在没有请求时不产生任何开销。实测数字见 [Token 成本](#token-成本)。
它不做什么
- 它是词法级工具,不懂语义。 比较的是标识符、成员名与注释文本;两个同名但无关的类型仍可能被报出来。
- 它不强制任何事。 提示里的规则是建议;Agent 可以偏离,唯一的防线是偏离必须记录。
- 它不改代码。 没有自动重命名、自动合并或补丁;扫描之所以能被信任,正因为它从不写源码。
- 结构性检测只覆盖三种语言的声明:C#、TypeScript、Python。其它文件只贡献注释/字符串证据。
- 单位只存不查。
canonical.unit会被展示并做字符串校验,但没有量纲漂移检测器。 - 一个工作区一份词表。 没有跨仓库视图。
- 它不替你决定词表是否提交。 那是项目自己的选择:提交则可以团队共享与 review,不提交则只在本机生效。
安装
以下任一方式都可安装到某个 profile:
# 从 npm dsh plugin --profile desktop add dsh-semantic-drift # 从 GitHub dsh plugin --profile desktop add github:Yubickari/dsh-semantic-drift # 从本地目录 dsh plugin --profile desktop add link:/path/to/dsh-semantic-drift
Web GUI 里对应「设置 → 插件 → 从源安装」。安装后刷新一次页面让浏览器半边加载,侧边栏会出现「语义漂移词表」。
lib/ 已随仓库提交,从 git 安装不需要构建。插件要求 DSH >=0.2.1-rc.3。
更新
插件里没有自动更新器:DSH 在重新安装时替换已安装的 bundle。
# npm,指定版本 dsh plugin --profile desktop add dsh-semantic-drift@0.2.0 # GitHub,锁定到某个 tag dsh plugin --profile desktop add github:Yubickari/dsh-semantic-drift#v0.2.0
之后重启 DSH,版本号看面板右下角的底栏(vX.Y.Z)或 GET /semantic-drift/health;设置弹窗里不再显示版本。更新不会改动你的词表文件与已接受例外。
开始使用
1. 在某个工作区打开「语义漂移词表」。
2. 点「创建词表」——写入 .dsh/semantics.yaml(含默认排除项,暂无概念)。
3. 点「重新扫描」。还没有概念时就有发现:结构性检测器会指出"同名类型被定义了两遍",这是冷启动的价值。
4. 用「新建概念」(或 semantic_upsert)登记概念,再扫一次。依赖词表的检测器这才有比较基准。
5. 对打算保留的写法点「接受」:选范围(仅此文件 / 同文件名的所有目录 / 整个符号 / 自定义 glob),看清会静音多少条,再写理由。
各模块信息
词表文件
version: 1
meta:
project: demo
updated: 2026-10-25
exclude: ["**/node_modules/**", "**/bin/**", "**/obj/**"]
concepts:
- id: device.target_temperature # group.name,全局唯一
terms: ["目标温度", "设定温度"] # 人怎么称呼它
kind: quantity # entity | quantity | enum | status | reference
canonical:
symbol: TargetTemperature # 代码里必须这么写
type: double
unit: "°C"
note: 设备被要求达到的温度。
accepted: # 已裁决的例外,必须带理由
- symbol: TargetTemp
path: src/Legacy/** # glob;省略表示该符号全局接受
reason: 公开字段保留一个版本
terms 可以写任何语言:扫描器把它们当纯文本在注释、字符串与资源文件里匹配。
写入前一定先校验;不通过就不会写入,并给出行号或文档路径:
id唯一且形如group.name;canonical.symbol是合法标识符且全局唯一;terms非空,且不能被两个概念共用;- 每条
accepted必须有reason; - 每个 glob 必须能解析;
- YAML 语法错误给出行列号,文件保持原样。
保存是规范化、幂等的;用原文编辑器手改时保留你自己的格式。
检测器
检测器 · 置信度 · 信号
duplicate-type · 高 · 成员名与取值都相同:真正的复制,建议合并是站得住的
· 高 · 派生副本:成员名相同、取值不同、共享同一基类——参数化派生,不作为合并候选
· 高 · 同名不同值:没有共同基类,两侧对这些名字的含义已不一致
contract-drift · 高 · 同名类型的成员不同,或同名成员的取值不同。若一方是另一方的同名子类,或两方都是私有嵌套助手类,则不报
symbol-variant · 中 · canonical.symbol 的拼写变体(大小写/分隔符、已知错拼、编辑距离 ≤ 2),或同一契约另一种命名:同基类、同成员、同稳定常量
· 中 · 词表拼写不在代码里:canonical.symbol 作为代码 token 全仓不出现,而另一种大小写写法有声明或作为声明词头
term-unmapped · 低 · 术语出现在注释或资源里,但规范符号从未在代码中出现
每条发现还带一个稳定的规则 id(detail.rule),面板就是按它本地化的:
规则 · 检测器 · 含义
identical-members · duplicate-type · 成员名与取值都相同
derived-copy · duplicate-type · 成员相同、取值不同、共享同一基类
literal-variant · duplicate-type · 成员相同、取值不同、无共享基类
diverged-members · contract-drift · 成员或成员取值不同
normalized · symbol-variant · 去掉大小写与分隔符后拼写相同
confusable · symbol-variant · 符号中某个词的已知错拼
edit-distance · symbol-variant · 与规范写法相差 ≤ 2 次编辑
contract-alias · symbol-variant · 同一契约、另一个名字
canonical-case · symbol-variant · 规范写法不存在,只有另一种大小写
canonical-missing · term-unmapped · 术语被使用,规范符号未出现
成员的取值也参与比较(仅限单行简单字面量):两个枚举成员值互换,过去因为只比名字而看起来"完全一致"。一侧是 struct、另一侧是 class 的拷贝会在消息里点明。
两条过滤避免刷屏:变体必须同文件还提到该概念的术语,或与规范符号共享首个词元;"契约漂移"的两份声明必须至少共享一个成员。
刻意排除:入口类(带 Main 的 Program/Startup)、partial 声明、生成文件(.Designer.cs、.g.cs、auto-generated 头)、成员少于两个的声明、同名子类遮蔽基类、私有嵌套助手类。
Agent 工具与提示注入
工具 · 用途
semantic_list · 列出概念;hasDrift 跑一次扫描并只保留有发现的概念
semantic_get · 按 id 或符号读一个概念与它的已接受例外;includeDrift 追加扫描
semantic_upsert · 新建或更新概念(merge 保留未提供字段,replace 重建)
drift_scan · 结构化发现,带检测器名、置信度与引用
semantic_accept · add(默认)、update 改范围或理由、remove 移除一条、clear 清空一个概念;新增与修改必须带 reason;定位已有条目用 previousPath
announceToAgent 开启时(默认),每个会话收到一节提示:所在工作区的概念与配套规则。整节最多 200 行,超过 120 个概念折叠为计数。面板顶部、工作区开关右边的「注入提示」按工作区记忆,关掉即停止该工作区的持续成本,工具不受影响。
拒绝一律是返回值({ ok: false, code, message, issues }),不抛异常。词表无法解析时返回 valid: false 与前几条问题。
它设计的循环:
1. Agent 接到任务,先 semantic_get 查要动的概念;
2. 缺失的概念先 semantic_upsert 登记,再写代码;
3. 改完 drift_scan,看有没有引入变体或分叉;
4. 确属刻意的发现,用 semantic_accept 带理由、带最小范围接受;
5. 词表 diff 和其它改动一样走 review。
面板
- 左侧树:概念按 id 前缀分组,组内有发现计数、组头有加号,顶部有搜索与快速新建。
- 概念详情:字段、归属
…(truncated)
以上内容来自 npm 上的 README,我们只做格式化,不改内容。
版本
npm 上共发布:3,最后更新 2026-10-31