内容
<!-- mcp-name: io.github.Byggarepop/unwritten -->
# 未写
[](https://www.nuget.org/packages/Unwritten)
[](https://www.nuget.org/packages/Unwritten)
[](LICENSE)
**免费、适用于智能代理的变更耦合切片。** Unwritten 从您的 git 历史记录中学习哪些文件通常会一起更改,并提示统计上可靠的缺失:"您更改了 `OrderService.cs` 但没有 `OrderServiceTests.cs`,而它们通常一起更改 94% 的时间。"
它作为 **MCP 服务器** 运行,因此 AI 编码代理(Claude Code、Copilot)可以在会话中检查自己的编辑是否有漏洞,并且作为 **CLI** 用于预提交钩子。只需执行一次 `dotnet tool execute`,在 `.unwritten/` 中建立索引,无需服务器,无需订阅,无需令牌。
**适用于任何语言。** 文件级规则只需要 git 历史记录,因此漏洞检测在 Python、TypeScript、Go 或混合仓库中同样有效。C# 仓库还额外获得方法级规则("您更改了 `CalculateFreight` 但没有其测试")和外观编辑过滤;JSON 文件获得键级噪声过滤。运行该工具需要 [.NET SDK](https://dotnet.microsoft.com/download)(10+)— 已经存在于任何 .NET 开发机器上 — 但它分析的仓库可以是任何语言。
## 快速开始
从您的仓库根目录:
```bash
# 1. 预热索引(可选 — 每个命令都会在第一次使用时建立索引并自动保持更新;这只是使第一个查询快速)
dotnet tool execute Unwritten --yes -- reindex
# 2. 注册为 MCP 服务器(Claude Code)
claude mcp add unwritten -- dotnet tool execute Unwritten --yes -- mcp
# 3. (推荐)使检查确定 - git 预提交钩子和 Claude Code 停止钩子,将失败的漏洞反馈给代理:
dotnet tool execute Unwritten --yes -- install-hook --git --claude-code
```
就这样 — 您的代理现在可以调用 `check_holes` 以在编辑后检查,并且钩子会在代理忘记时捕获这些情况。
## 这不是一个新想法 — 而这正是重点
Unwritten 现代化了一个 20 年前的研究成果,以适应智能代理时代。 该领域被称为 *变更耦合*(或 *进化耦合*),由 Mining Software Repositories 社区广泛研究:
- **ROSE(Zimmermann, Weißgerber, Diehl & Zeller, 2004–2005)** 从版本历史记录中挖掘关联规则,基于 Eclipse 插件 - “程序员修改了这些函数,也修改了……” - 明确地是为了防止由于不完整的修改而导致的错误,包括关于缺少项的警告。 Unwritten 基本上是 ROSE 的重现。
- **CodeScene(商业)** 是最接近的邻居。 其增量分析也可以在预期更改模式被破坏时发出警告 - 通常在拉取请求中缺少一个文件。 区别在于 *哪里* 和 *如何* 运行:CodeScene 是一个服务器产品,分析拉取请求之后,作为代码健康、热点和团队分析的付费平台的一部分。 Unwritten 是一个免费的小工具,在编写代码 *期间* 回答相同类型的问题 - 在编码代理的循环中,在提交任何内容之前 - 本地运行,无需服务器,且每次警告都带有明确的置信度评分和示例提交作为证明。
**Unwritten 新增功能:**
**为 AI 助手构建。** 编码代理如 Claude Code 和 Copilot 可以在工作 *期间* 而不是在打开拉取请求后询问 Unwritten “我是否遗漏了什么?” 老工具是为在编辑器中点击的人类(ROSE,2004 年)或在事后在服务器端审查更改(CodeScene)而构建的。
**免费且简单。** 开源,在您的机器上运行,不需要服务器或帐户。 一个已安装的工具,一个小型索引文件在您的仓库中 - 就是这样。
**对确定性诚实。** 如果两个文件一起更改了 4 次中的 3 次,这可能只是巧合。 如果它发生了 100 次中的 90 次,这是一个真正的模式。 Unwritten 使用一个统计公式(Wilson 下界),该公式根据模式 *保持* 的频率 *和* 证据量来评估证据:
- 4 次中的 3 次 → 原始比例 75%,但置信度仅为 **0.30** — 证据太少,无法信任
- 17 次中的 15 次 → 原始比例 88%,置信度 **0.66** — 开始变得可靠
- 100 次中的 90 次 → 原始比例 90%,置信度 **0.82** — 一个真正的模式
- 15 次中的 15 次 → 完美记录,置信度 **0.80** — 小型项目 *可以* 获得高置信度,但前提是历史记录无瑕疵
因此,更多的证据总是更有价值:4 次中的 3 次和 100 次中的 90 次获得非常不同的分数,即使百分比看起来相似。 当两个模式最终获得相同的分数时,它们已经获得了 - 一个小型项目需要一个近乎完美的记录才能达到一个大型项目通过几个例外就能达到的水平。
## 为什么选择这些阈值(在真实数据上测试)
我们在一个大型微软项目(EF Core,13,642 次提交)上测试了这个想法。 该工具从较旧的历史记录中学习了 1,976 个模式,然后在最近的 2,047 次提交中标记了“漏洞” - 预期文件中缺少的文件。 然后我们检查:实际是否在稍后更改了缺少的文件(在 10 次提交内)? 如果是,则警告可能是正确的。
| 置信度 | 标记的漏洞 | 结果证明是正确的 |
|---|---|---|
| 0.3–0.5 | 1,429 | ~10%(基本噪声) |
| 0.6 | 254 | 50% |
| 0.7 | 328 | 66.5% |
| 0.8 | 133 | 79.7% |
模式很明确:在 0.6 以下,警告大多是噪声;在 0.6 以上,它们迅速变得值得信赖。 这就是为什么这些默认值是内置的(不要在重新测试之前更改它们):
- 警告从置信度 **0.6** 开始 - 数据显示警告开始正确的地方。 CLI 仅在 **0.7** 时阻止提交。
- 触及超过 30 个文件的提交在学习期间被忽略 - 大型重构和合并将教授错误模式。
- 文件必须至少更改 10 次,然后关于它的规则才会被信任。
- **每个警告都带有证明** - 模式保持了多少次,以及链接到实际示例提交。 关于 *缺少* 的警告无法指向文件,因此必须带上自己的证据。
## 用法
无需安装步骤 - `dotnet tool execute` 在第一次使用时下载工具并运行它(更短的别名 `dnx` 也有效,但示例此处全部拼出):
```bash
dotnet tool execute Unwritten --yes -- check --staged
```
`--` 之前的所有内容都是工具运行器使用的;`--` 之后的所有内容都是 Unwritten 命令行使用的。
索引位于 `.unwritten/`,一个本地缓存,根据需求从历史记录中重建。 该目录附带自己的 `.gitignore`,因此无需在您的目录中添加任何内容。 (要与团队共享 `.unwritten/config.json`,请使用 `git add -f` 提交它。)
### 作为 MCP 服务器
**Claude Code:**
```bash
claude mcp add unwritten -- dotnet tool execute Unwritten --yes -- mcp
```
**VS Code(Copilot Chat):** 在工作区中创建或编辑 `.vscode/mcp.json`:
```json
{
"servers": {
"unwritten": {
"type": "stdio",
"command": "dotnet",
"args": ["tool", "execute", "Unwritten", "--yes", "--", "mcp"]
}
}
}
```
**Visual Studio 2026(GitHub Copilot Chat):** 与 VS Code 相同的 `servers` 代码段,在 `%USERPROFILE%\.mcp.json`(所有解决方案)或 `.mcp.json` 文件旁边 - 然后重启 Visual Studio 以加载服务器。
任何其他 stdio MCP 客户端:`dotnet` 与 `["tool", "execute", "Unwritten", "--yes", "--", "mcp"]`。
**通过 MCP 网关(例如 [McpOrchestrator](https://github.com/Byggarepop/dotnet-mcp-orchestrator)):** 路由器的 LLM 只看到功能描述,因此请为它提供一个描述调用 Unwritten 的时机。 复制此作为功能说明:
```text
Unwritten - 仓库约定守卫。 从 git 历史记录中学习哪些文件和代码成员(方法、类)通常一起更改(从 git 历史记录中挖掘,具有统计置信度和证据),并在两个级别上标记预期但缺失的伴侣更改。
在以下情况下调用此功能:
- 您在 git 仓库中进行了任何代码更改 - 修改了方法、添加了类、重命名了某些内容、编辑了配置、创建或删除了文件 - 并即将完成任务、提交或交回给用户 -> 使用 `check_holes` 检查更改的文件(或省略文件以自动检测所有未提交的更改;传递 `baseRef` = 预工作提交,如果您在会话期间提交了)。
在成员级别也适用:“您更改了 `CalculateFreight` 但没有其测试” - 因此即使只有方法主体更改,也要调用它。
- 您计划进行更改,并想知道哪些文件或成员通常伴随您即将修改的文件 -> `expected_companions`。
- 标记了一个漏洞,您需要判断此情况是否是合法的例外 -> `explain_rule`(返回历史证据和过去的例外)。
- 用户询问“是否遗漏了什么?”,“通常与此更改的是什么?”,或提到了共同更改/约定 -> `check_holes` 或 `expected_companions`。
- 索引维护:历史记录重写后重新索引;索引健康统计。
不要用于:代码质量/检查/样式问题、测试执行、非 git 工作区或有关代码 *内容* 的问题。
警告是模式,而不是规则 - 高置信度漏洞(≥0.7)应修复或明确证明;较低的漏洞是建议。
```
公开的工具:
| 工具 | 目的 |
|---|---|
| `check_holes(repoPath, files?, minConfidence?, baseRef?)` | 编辑后,传递您触摸的文件 - 或省略 `files` 以自动检测每个未提交的更改。 返回预期但缺失的文件,带有证据,以及 `checkedFiles`(每个输入的历史记录有多少:空结果表示文件没有历史记录,意味着“无数据”,而不是“全部正常”)。 传递 `baseRef`(预工作提交),当会话已经提交,以便仍然看到已提交的编辑。 |
| `expected_companions(file, repoPath, top=10)` | 文件的排名共同更改伴侣,无论阈值 - 打开文件时的主动上下文。 |
| `explain_rule(fileA, fileB, repoPath)` | 一个规则的完整证据:计数,两个方向的置信度,共同更改提交,以及 A 单独更改的最近提交(例外)。 |
| `stats(repoPath)` | 索引健康:索引的 HEAD,事务/实体/对计数,索引大小,地板 0.5/0.6/0.7/0.8 处的规则计数。 |
| `reindex(repoPath)` | 强制完全重建 - 历史记录重写或配置更改后。 |
### 作为 CLI
```bash
dotnet tool execute Unwritten --yes -- check # 所有未提交的更改
dotnet tool execute Unwritten --yes -- check --staged # 暂存文件(预提交钩子)
dotnet tool execute Unwritten --yes -- check --base abc1234 # 自该提交以来发生的所有更改
dotnet tool execute Unwritten --yes -- check src/Foo.cs src/Bar.cs # 显式文件集
dotnet tool execute Unwritten --yes -- check --staged --fail-at 0.8 # 更严格的门槛
```
报告门槛(0.6)或以上具有漏洞会被打印;退出代码为 1,当任何漏洞达到失败门槛(0.7)时。 被检查的文件如果没有或历史记录太少,会以 `note:` 行提示 - 索引从未见过的文件的沉默意味着“无数据”,而不是“无问题”。
索引是完全自我维护的:对仓库的第一次调用会索引其完整历史记录(大型仓库需要几秒到一分钟);之后,只有新的提交会被摄取,配置更改会被自动拾取,查询是纯内存查找。 您永远不需要自己运行 `reindex`,除非 git 历史记录被重写。
### 作为钩子 - 确定性检查
依赖于代理(或人类)记住运行检查是最薄弱的环节。 `install-hook` 使其每次都触发:
```bash
dotnet tool execute Unwritten --yes -- install-hook --git # git 预提交钩子
dotnet tool execute Unwritten --yes -- install-hook --claude-code # Claude Code 停止钩子
```
- **`--git`** 编写 `.git/hooks/pre-commit`(拒绝触摸现有的外部钩子,除非 `--force`)。
- **`--claude-code`** 在仓库的 `.claude/settings.json` 中添加一个 [停止钩子](https://docs.claude.com/en/docs/claude-code/hooks):每当代理完成一个回合时,如果未提交的更改存在失败的漏洞,报告会反馈给代理,以便它可以修复或证明它。 钩子打开失败(在基础设施问题上永远不会阻止),并且在同一回合内永远不会重新阻止。
预提交手动替代 - 使用以下内容创建 `.git/hooks/pre-commit`(无扩展名):
```sh
#!/bin/sh
exec dotnet tool execute Unwritten --yes -- check --staged
```
并使其可执行(`chmod +x .git/hooks/pre-commit`;Windows 上不需要)。 当任何漏洞达到失败门槛(默认为 0.7)时,提交将被阻止; 漏洞在 0.6 和 0.7 之间会被打印为警告,但不会阻止。 要绕过一次:`git commit --no-verify`。
使用 [pre-commit](https://pre-commit.com) 框架,添加到 `.pre-commit-config.yaml`:
```yaml
repos:
- repo: local
hooks:
- id: unwritten
name: unwritten 漏洞检查
entry: dotnet tool execute Unwritten --yes -- check --staged
language: system
pass_filenames: false
```
使用 [Husky](https://typicode.github.io/husky/):`echo "dotnet tool execute Unwritten --yes -- check --staged" > .husky/pre-commit`.
### 静音错误规则 - `unwritten ignore`
有时,高置信度规则对于您的情况来说只是错误的,并且不断阻止提交。 内容感知抑制已经自动吸收了 C# 和 JSON 编辑的修饰,`git commit --no-verify` 也绕过了一次提交 - 但对于 *持续* 的错误配对,请静音它:
```bash
dotnet tool execute Unwritten --yes -- ignore docs/api.md src/openapi.json --for 30 --note "现在单独生成"
dotnet tool execute Unwritten --yes -- ignore --list
dotnet tool execute Unwritten --yes -- ignore --remove docs/api.md src/openapi.json
```
忽略 **按设计有界限** - 永久静音会过时,最终隐藏一个真正的遗漏。 忽略在触发器已更改 `--for` 更多次(默认为 30)后过期:这些提交要么侵蚀错误规则(每个触发器单独提交降低其置信度,因此它通常会在静音过期之前自然死亡),要么证明耦合确实存在。 被静音的漏洞仍然作为 `suppressed` 出现,并带有其剩余预算 - 永远不会被静默删除 - 而 `check --strict` 会覆盖它们。 忽略存在于 `.unwritten/ignores.json`(机器管理;请将您的手放在 `config.json` 中)。
没有用于创建忽略的 MCP 工具:抑制警告是人类判断,而不是编码代理应该对其自身发现采取的措施。 环路仍然是封闭的 - `check_holes`/`explain_rule` 工具描述和停止钩子反馈指示代理,当它将规则持续判断为错误时,它必须说明并指向 `unwritten ignore`,因此决策到达您,而不是在代理的转录中消亡。
### 配置 - `.unwritten/config.json`
第一次索引构建会将一个完整的注释模板放到 `.unwritten/config.json`,因此设置在它们所在的位置是可发现的 - 只需取消注释一行即可更改。 所有设置都是可选的;缺失的设置会使用已验证的默认值:
```jsonc
{
"minSupport": 10, // 触发文件在存在规则之前的最少更改次数
"maxTransactionSize": 30, // 触及更多文件的提交不被排除在训练之外
"defaultMinConfidence": 0.6, // 警报阈值(`check_holes` + CLI 报告阈值)
"failConfidence": 0.7, // CLI:当漏洞达到此阈值时退出 1
"maxExamplesPerPair": 10, // 每对文件保留的证据提交
// JSON 触发器文件(内容感知异常):
"facetFloor": 0.6, // 威尔逊地板,用于键被视为预测性
"minFacetSupport": 5, // 键在被分类之前的最少更改次数
"facetCandidateFloor": 0.5, // 文件级置信度,从中训练键
"maxFacetsPerEntity": 64, // 超过此值的文件是键不可跟踪的(开放失败)
"facetMaxDepth": 3, // 键路径深度上限(更深的向上滚动)
// 成员级索引(C#/Roslyn),默认关闭:
"memberLevel": false, // 启用成员 -> 成员规则(首次构建成本为秒到分钟)
"memberHistoryWindow": 5000, // 成员训练涵盖最近的提交次数
"memberMinSupport": 10, // 成员触发器在存在规则之前的最少更改次数
"memberMaxTransactionSize": 50 // 更改更多成员的提交被排除(重构噪声)
}
```
阈值立即生效。训练设置(`minSupport`、`maxTransactionSize`、`maxExamplesPerPair`、面元训练设置)描述了索引的构建方式 - Unwritten 在更改时检测到(包括成员设置),并在下一次查询时自动重建,因此不需要手动步骤。`reindex` 仍然是历史记录重写的工具。命令行标志会覆盖配置文件。
## 它是如何工作的
对于每个触及 ≤ 30 个文件的非合并提交,Unwritten 统计每个文件的更改和每个文件对的共同更改。 当 A 至少更改了 10 次,并且 `wilson_lb(co(A,B), count(A)) ≥ floor` 时,规则 A→B 存在。 索引位于 `.unwritten/index.json`,按索引的 HEAD 编号; 当 HEAD 移动时,只有新的提交被计数。
内部引擎是领域中立的 - 它在 *事务* 中计算 *实体*,而 git 只是映射提交到事务和文件路径到实体 ID 的第一个适配器。 这为成员级实体(Roslyn)和非 git 事件流保留了大门。
## 未来 Roadmap
- **更多语言支持**:Unwritten 目前仅支持 C# 和 JSON 文件的成员级索引。 未来版本将为其他语言添加更多支持。
- **更多 MCP 客户端支持**:Unwritten 目前支持 Claude Code、Copilot 和其他 stdio MCP 客户端。 未来版本将为其他 MCP 客户端添加更多支持。
## 结论
Unwritten 是一个免费、简单、有效的工具,用于在编码过程中检测代码更改中的漏洞。 它适用于任何语言,并与流行的编码代理和编辑器集成。 借助其先进的统计分析和机器学习算法,Unwritten 有助于防止错误并改善代码质量。
面向 JSON 文件的上下文感知异常
对于触发条件 文件的规则,Unwritten 还会额外学习 **哪些键**共同更改 — 使用相同的威尔逊评分,深入到下一层路径如 `info.version`,数组折叠为 `packages[].)。当您当前的编辑仅涉及历史记录中非预测性的键(例如,仅修改描述的编辑,而 `version` 键驱动了耦合该洞察会被 **抑制**:代理接收到它时带有 `suppressed: true` 和每个证据,并且可以推翻;打印一行摘要并将其排除在退出代码之外(`--strict` 恢复旧行为)。
该层在所有方向上 **开放失败未知键、文件在差异的任一端无效 JSON、键>64)、缺少历史记录这些警报保持不变。键训练仅针对参与规则的运行,因此对于没有 JSON 存储库,索引构建成本保持不变。`explain_rule`时公开每个键的详细情况(`facetBreakdown`) C# 的方法级规则(需选择加入)
文件级说“您更改了 `OrderService.cs` 但没有更改ServiceTests.cs`”。使用Level": true`,Unwritten 在更深一层学习相同的模式 — 按方法:*“您 `CalculateFreight` 但没有更改 `CalculateFreight_Apcharge`”*(“成员”是配置和 JSON 输出使用的 C — 方法、构造函数、字段 — 但实际上大多数规则都是关于方法的)。
**工作原理。**记录窗口中的每个提交,每个更改的 `.cs` 文件都会进行 **语法** 差异比较(使用 Roslyn,无需编译),以查找哪些成员实际发生了更改。这些第二个索引(`.unwritten/members.json`)中形成提交的交易,ID 类似于 `Namespace.Type.Method/arity`,并使用与文件相同的威尔逊统计数据进行评分。然后,`oles` 还会报告 **memberHoles**:历史记录您实际更改的方法相关的配套方法,但这些方法在您的缺失。
**为什么它有帮助:**
- **精度。** 文件级规则对文件的任何编辑都会触发。而方法级规则仅在耦合的方法本身发生更改时才会触发 — 编辑同一文件中的无关辅助函数不会引发警报,因此警告更少,也更值得信赖。
- **噪声免疫性。** “更改”意味着去掉琐碎信息后的语法不同,因此仅注释和空白的编辑不会更改任何成员。这也会抑制文件级漏洞的 cosmetic C# 编辑(`suppressed: true`,CLI `--strict` 覆盖)— 无论是否启用成员索引。
- **在重构中存活。** 成员故意排除文件路径,因此当文件被移动或重命名时会保留其历史记录 — 正是在文件级历史记录重置为零的地方。
成本和注意事项:第一次成员构建会解析历史中的每个更改文件版本(默认:最近 5,000 个提交)— 对于小型存储库,耗时为秒,对于大型存储库,耗时为分钟;增量更新很便宜。排除生成代码(`*.g.cs`、`*.Designer.cs`、`obj/`)。更改超过 50 个成员的提交作为重构噪声被排除。成员重命名会重置历史记录,具有相同参数的同名函数共享一个实体。成员历史记录比文件历史记录稀疏得多:年轻的存储库通常在支持的默认值为 10 时 **没有** 成员规则 — 这是统计数据诚实,而不是错误。`expected_companions` 和_rule` 直接接受成员 ID(自动检测)。
## 它的功能
诚实的部分 — 已知第一阶段的
- **无重命名跟踪。** 路径按原样处理;重命名的文件其历史记录从零开始。(`git log --follow` 风格的重命名跟踪是一个已知空白点。)
- **内容级异常仅覆盖 JSON 和 C#.** 其他结构化格式(YAML、TOML、XML)和其他语言在文件级别处理编辑都会触发规则。
-级(成员)规则 C#,需选择加入,且支持耗费高。** 存储库需要方法在历史记录窗口内更改 ≥10 次,才能存在方法规则;方法重命名和具有相同参数的重载会模糊身份。
- **“稍后填写”验证注意事项。** 上面的经验表衡量了标记的漏洞是否在 10 次提交内被填补 — 一个代理“警报是正确的”的指标,而不是因果关系的证明。一些“未填补”的漏洞可能仍然是实际的疏忽,但从未被修复。
- **`explain_rule` 中的一次 git 调用。** 热点查询(`check_h、`expected_companions`)是纯索引查找;`` 另外运行一次定向 -- <fileA列出 A 单独更改记录。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器