你的代码库有自己的风格。argot 让 AI 代码遵循它。
AI 编写代码。argot 利用唯一不会产生幻觉的东西来驾驭它:你的仓库自身的历史。通过统计而非第二个 LLM —— 标记出你从未使用过的依赖、已经写过的函数、破坏架构分层的 import、被悄悄弱化的测试,以及只有你的团队才知道的规范。100% 本地化,可重放。
argot.tmonier.com
·
文档
·
基准测试
·
实际抓取案例
·
研究日志
· 12 languages →
类型检查器会问 *“这合法吗?”* argot 则提出过去存在于代码审查中的问题:*“这是**我们**这里的做法吗?”* —— 从而捕捉到那些完美无缺、类型正确、lint 清洁,但却不合群的 AI 代码。它通过统计你仓库自身的历史来回答 —— 核心统计是确定性且可重放的,所有数据都在本地 —— 绝不是用第二个 LLM 去评判第一个。
它还会提出一个其他工具都不会问的第二个问题:**AI 是否投机取巧了?** 当 agent 无法让失败的测试通过时,达到“完成”状态最廉价的方法就是让测试停止报错。argot 会读取每个 diff 的两侧,将被弱化、禁用或删除的测试与其掩盖的生产环境变更关联起来。
### 五个学习型检测器 —— 外加只有你的仓库能编写的规则
| | 规则 | 它能捕获 | |
| :-- | :-- | :-- | :-- |
| 🚫 | **`foreign-import`** 及相关规则 | 你的仓库**从未使用过**的依赖、API 或惯用法 | *“我们这里不这么干”* |
| ♻️ | **`redundant`** | **重复造轮子**的新函数 | *“你已经有这个了”* |
| 📍 | **`misplaced`** | 正确的代码,却放在了**错误的位置** | *“这不应该放在这里”* |
| 🧱 | **`layering`** | **破坏了你的架构分层**的内部 import | *“我们绝不跨越这条边界”* |
| 🧪 | **`test-deleted`** 及相关规则 | 在生产环境变更的同时,被**悄悄弱化、禁用或移除**的测试 | *“不要在测试上作弊”* |
| 📜 | **你自己的规则** | **仅存在于你的仓库中**的规范 —— 可脚本化,无需重新编译 | *“我们这里的准确做法是这样的”* |
前五个是从你的 git 历史中学习到的。第六个[由你编写](#your-conventions-as-rules) —— 并且它是每个 linter 配置中你的团队真正关心的部分。
argot 还会注意到代码库本身的演进:它会挖掘你已接受的历史记录中的**迁移** —— 比如一个旧的依赖或调用被新的替代 —— 这样替代项就不会被识别为外来的,仍然使用旧代码的代码会触发 `superseded`(默认警告),并且它会列出重构中被遗漏的文件。证据就是你自己的提交记录;或者你可以在 `argot.toml` 中用两行代码声明一个迁移,立即生效,无需重新拟合。
## 快速开始
```
# 安装 (单一静态二进制文件 — 无需 Python,无需 Node)
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/get-tmonier/argot/releases/latest/download/argot-installer.sh | sh
```
Windows: `powershell -c "irm https://github.com/get-tmonier/argot/releases/latest/download/argot-installer.ps1 | iex"` · npm: `npm install -g @tmonier/argot`
**基于你自身的历史,60 秒验证,零设置:**
```
argot audit # ⏪ what did AI sneak into your last 50 commits?
```
`audit` 会拟合 50 次提交前的代码风格(在一个临时 worktree 中 —— 你的代码树保持原样),对之后的记录进行重新评分,并将每个发现归因于引入它的提交 —— **AI 辅助 / 人类 / 未知**,仅基于具体的提交标记:
```
━━ argot audit ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
last 50 commits · 52% carry AI markers · 1 finding would have met review
Worst offender — commit cae8349 · ai-assisted
! landing/src/pages/llms-full.txt.ts:L1-32 · foreign-import
↳ astro (L1), astro:content (L2) — 0 of 49 module specifiers…
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
终端卡片以可复制粘贴的**分享文案**结尾,而 `argot audit --format html` 则是一张可以直接截图的卡片 —— 发布你的得分吧。
然后拟合今天的代码风格,以便 `check` 在它们合并*之前*抛出警告:
```
argot init # learn this repo's voice (~25 s on a 1,100-file repo)
argot check # score your working changes against it
```
准确性取决于设置 —— argot 从它能看到的内容中学习。最佳途径:`npx skills add get-tmonier/argot`,然后在你的编程 agent(Claude Code、Cursor、70 多种 agent)中运行 `/argot-setup`,它会读取你的仓库,排除不应影响代码风格的内容,并验证捕获效果。完整指南:[设置](https://argot.tmonier.com/docs/setup/) · [入门指南](https://argot.tmonier.com/docs/getting-started/)。
**Claude Code —— 一次安装搞定一切。** [argot 插件](https://argot.tmonier.com/docs/plugin/)捆绑了六项技能、[MCP server](https://argot.tmonier.com/docs/agents/)(`argot mcp` —— 在你的 agent 编写代码时提供主动的代码风格上下文),以及一个可选的、非阻塞的写入前护栏,它会在引入外部依赖之前*发出询问*:
```
/plugin marketplace add get-tmonier/argot
/plugin install argot@argot
```
其他 agent(Cursor、Codex 等 70 多种):`npx skills add get-tmonier/argot`。
## 演示
一个 PR 在全 FastAPI 的代码库中添加了一个 **Django 风格的视图**。mypy 和 ruff 毫无反应 —— 因为它调用的框架是这个仓库从未导入过的:
```
argot check · 1 hunk above threshold (1 foreign)
fastapi/receipts.py
! L1-L10 1.00 foreign · staged · foreign-import [94a92c256ea1]
↳ django (L1) — 0 of 74 module specifiers in repo
common here: fastapi (357×), pydantic (129×), typing (129×) (+7 more)
1 | from django.views import View
^^^^^^
```
`redundant` 指出你已经拥有的函数(`↳ 重复了 slugify (src/utils/text.py:14) — 相似度 0.86`),`misplaced` 指出代码应该归属的位置,`layering` 指出 import 破坏分层的方向,而每一行 `↳` 都是你仓库自身的证据。完整剖析:[解读输出](https://argot.tmonier.com/docs/reading-the-output/) · [捕获内容](https://argot.tmonier.com/docs/what-it-catches/)。
## 你的规范,即规则
每个团队都有通用 linter 不具备的规范:*“展示组件只接收 props —— 它们不获取数据”*,*“文件必须通过我们的加载器解析,绝对不能用原生的 `JSON.parse`”*,*“每个仓库只能有一个 HTTP 客户端”*。它们往往存在于代码审查的评论和新人入职文档中 —— 直到某个根本没读过这些的 AI agent 绕过了它们进行合并。有了 argot,它们就成了**仓库本地规则**:一个 TOML manifest 和 `.argot/rules/` 中的一个沙盒化小脚本,与你的代码一起版本化,在运行时加载 —— 无需构建插件,无需重新编译,一种规则格式适用于所有 12 种语言。
**而且你不需要从空白页面开始 —— argot 会为你*发现*你的规范。** `argot conventions` 会读取你代码库自身的结构,并向你展示它已经包含的内容:它共享的内部 API,以及*每种代码存放的位置*。没有其他工具能检测到最后这一部分 —— 团队在代码审查中要求执行但从未记录下来的**放置规范**,仅通过你的文件树和调用图以框架无关的方式学习得到:
```
$ argot conventions
── typescript (1,240 files) ──
Naming camelCase 94% · PascalCase 6%
Vocabulary db (86 files), logger (74), apiClient (61), AppError (44)
Type funnels Money, Result, DateTime
── placement · where a kind of code lives ──
dir:migrations queryRunner, addColumn, createTable 100% confined
role:schema z.object, z.string, validate 96% confined
dir:services db.transaction, logger.info, publish 92% confined
dir:controllers req, res, next 98% confined
ext:.tsx useState, useQuery, styled 99% confined
```
这段话可以理解为:*“验证逻辑位于 `*.schema.ts` 中,数据库访问只能在 migrations 中,业务逻辑在服务层 —— 而不是在控制器或视图中。”* 将任意一行交给 **argot-suggest-rules** 技能,它就会以逆否命题的形式写出这条规则 —— *这属于它的领地;在其他任何地方出现就标记它* —— 并且在它处理任何 diff 之前,必须通过一套绿色的 fixture 测试套件。你只需一步就能从 *“我们真的应该把这个记录下来”* 变成一条强制执行的规则。
并且你的规则可以做到经典 linter 在结构上做不到的事情。Linter 只能看到文件的一个版本;而 argot 会将 diff 的**两侧**都交给你的规则 —— 因此你可以编写关于变更*删除了什么*的规则:
```
# .argot/rules/no-dropped-endpoints/rule.toml
[rule]
schema = 1
name = "no-dropped-endpoints"
description = "removing a public endpoint requires a deprecation cycle — catch the route that silently disappears in a diff"
severity = "error"
languages = ["typescript", "javascript"]
```
```
// check.rhai — a route that existed before this change, and is gone now
const ROUTES = "(call_expression function: (member_expression property: (property_identifier) @verb)
arguments: (arguments (string (string_fragment) @path)))";
let now = [];
for m in ts_query(ROUTES) { if m.capture == "path" { now.push(m.text); } }
for m in ts_query_old(ROUTES) {
if m.capture == "path" && !now.contains(m.text) {
report(m.line, "endpoint '" + m.text + "' removed without a deprecation cycle — see docs/api-lifecycle.md");
}
}
```
没有哪个 ESLint 插件能表达这条规则 —— linter 中不存在所谓的“旧版本”。而且由于 argot 拟合了你的历史,规则的白名单可以是**你自己的 git log**:`import_attested("moment")` 会问 *“这个仓库曾经使用过这个日期库吗?”* —— 无需硬编码列表,也无需维护列表。规则仅在**更改的文件**上运行(采用此规则不会产生任何积压的噪音),并且它们的发现将像内置规则一样被抑制、配置和呈现。`argot rules test` 提供了红/绿(失败/通过)的创作循环。完整参考及实战示例:[自定义规则](https://argot.tmonier.com/docs/custom-rules/)。
## Agent 无法作弊的规则
当 AI agent 无法满足某项检查时,它会寻找下一个最廉价的通过方式:禁用该规则、在本地配置中降低其严重级别、`--rule it=off`,或者 —— 对于自定义规则 —— 直接重写捕获它的脚本。锁定规则后,所有这些途径都会被堵死:
```
[rules]
layering = { severity = "error", locked = true }
custom = { severity = "error", locked = true } # lock every repo-local rule
```
**被锁定**的规则(可选,仅能通过已提交的 `argot.toml` 启用):
- **冻结其严重级别** —— 拒绝 `argot.local.toml` 和 `--rule` 的覆盖;
- **拒绝针对其发现的所有抑制方式** —— 行内的 `# argot: ignore`、`[[mute]]` 和 `[exclude].paths` 均不适用;
- 最致命的是 —— **弱化锁定本身就是一项发现。** `rule-tampered`(属于 `governance` 组别,固定为 `error`,不可抑制)会读取*被检查 diff 的两侧*,并在更改移除锁定、降低锁定规则的严重级别、在锁定规则上添加 `[[mute]]` 或编辑锁定自定义规则的脚本时触发 —— 并会发出 CI 展示的高级别运行警告(在 `--format github` 下的 PR 批注)。
篡改**有迹可循**,而不是防篡改 —— 这与测试完整性规则的哲学相同:agent *可以*触碰警报,但触碰警报**本身**就是警报。放松锁定规则的唯一安静方式是一份由人类审查的、已提交的 `argot.toml` diff。指南:[锁定规则](https://argot.tmonier.com/docs/configure/#locked-rules--the-agent-cant-turn-off-the-alarm)。
## 像配置任何 linter 一样配置它
每个规则(内置的或你的)都通过 `argot.toml [rules]` 设定默认值 —— `error` / `warn` / `off`,可按规则或组别设定 —— 也可以在每次运行时通过 `--rule layering=warn` 设定。`[rules]` 条目还可以将规则范围限定在特定路径(`layering = { include = ["src/**"] }`)。排除规则采用 gitignore 风格的 `[exclude].paths`;内联的 `# argot: ignore-next-line rule=… — reason` 和 `argot mute
` 提供了行级别的和持久且已提交的默认接受。指南:[配置](https://argot.tmonier.com/docs/configure/) · [命令](https://argot.tmonier.com/docs/the-commands/)。
### argot 与你已在使用的工具的对比
| | 类型检查器 | Linter | Copilot · SAST | argot |
|---|:---:|:---:|:---:|:---:|
| 捕获无效代码 | ✅ | ✅ | ~ | — |
| 标记对*此*仓库而言外来的内容 | ❌ | ❌ | ❌ | ✅ |
| 标记你**已经有**的函数 | ❌ | ❌ | ❌ | ✅ |
| 标记放在**错误位置**的代码 | ❌ | ❌ | ❌ | ✅ |
| 标记**破坏你的架构分层**的 import | ❌ | ❌ | ❌ | ✅ |
| 标记为了在失败的套件中作弊而**悄悄弱化**的测试 | ❌ | ❌ | ❌ | ✅ |
| 跨语言、在 diff 中强制执行**你团队自己的规范** | ❌ | ~ | ❌ | ✅ |
| Agent 无法在不被注意的情况下静音、覆盖或重写的**锁定规则** | ❌ | ❌ | ❌ | ✅ |
| 审查已合并的历史 · **将发现归因于 AI 还是人类** | ❌ | ❌ | ❌ | ✅ |
| 从*你的*历史中学习 · 100% 本地运行 | ❌ | ❌ | ❌ | ✅ |
argot 是补充性的:它位于你的类型检查 linter *之后*,捕获它们无法做到的一件事 —— 代码合法且 lint 清洁,但却与你团队以往编写的任何内容都格格不入。它是围绕 AI 输出构建的约束机制,建立自唯一不会产生幻觉的东西之上:你仓库自身的历史。
## 十二种语言,每种语言一个模型
这不是一个带有十二个前端的共享语法 —— 而是十二个**真正的 tree-sitter 适配器**,每个都有自己独立的 import/callee 提取、独立的命名和惯用法模型,以及独立的语言校准(Python 代码块会根据 Python 进行评判,TypeScript 代码块根据 TypeScript 评判 —— 没有跨语言干扰)。每一个都在真实的开源语料库上进行了基准测试:
| 语言 | 文件 | 基准测试基于 |
|---|---|---|
| Python | `.py` | fastapi · rich · faker |
| TypeScript | `.ts` `.tsx` | hono · ink · faker-js |
| JavaScript | `.js` `.jsx` | express · commander · eslint |
| Go | `.go` | gh-cli · hugo |
| Rust | `.rs` | ripgrep · bat |
| Java | `.java` | guava · junit5 |
| C# | `.cs` | powershell · jellyfin |
| C | `.c` `.h` | redis · curl |
| C++ | `.cpp` `.cc` `.hpp` | rocksdb · fmt |
| Ruby | `.rb` | homebrew · rubocop |
| PHP | `.php` | laravel · composer |
| Pascal | `.pas` `.pp` `.dpr` | castle-engine · mormot2 |
各语言徽标版权归各自项目所有,引自 Devicon (MIT)。
## 基准测试
**真实、无数据泄露的数据**,由真实的 `fit → check` 流水线测量 —— 将外部 fixture 插入到真实的宿主文件中;在模型从未见过的历时数据对照集上统计误报情况:
- **外部内容捕获率 —— 595/605 (98%)** (当外部符号在 diff 中可见时) · **误报率为 0.29%**(基于 22,513 个真实代码块,最差的语料库为 1.46%)
- **架构 —— 捕获 244/252 (96.8%)** · **对照组标记 0/140** · 在重放的真实历史上过度触发率 ≤2.7%
- **重复造轮子 —— 中位数 89%**(每个代码块的误报率 ≤4.5%) · **放置错误 —— 85–99% (中位数 96%)**(误报率 ≤1.2%,前提是仓库具有可分离的架构)
- **测试完整性 —— 144/153 (94.1%)** 的作弊手段被捕获 · **0/102** 合理重构对照组未受影响 · 在 5,268 个重放的已提交测试变更中,有 1.12% 触发了门禁严重级别的警告
一个已公开的限制:**被掩盖的外部内容** —— 即名称与你正在使用的名称冲突的外部符号 —— 在统计上对风格模型是不可见的。我们公布这个数据而不是隐瞒它。方法论说明如下:捕获率是我们在评分前冻结的[预注册评分标准](benchmarks/catalogs/RUBRIC.md)下,根据我们编写的 fixture 测量的;误报率则是在模型从未见过的真实提交上统计的。欢迎独立验证 —— 这正是可重放的测试工具集的作用所在。各语言和各语料库的详细表格、方法论、置信区间:[基准测试页面](https://argot.tmonier.com/benchmarks)(由 CI 输出,不会偏离已发布的版本)。想要验证某种语言?[提交一个 issue](https://github.com/get-tmonier/argot/issues/new)。
数据是一方面;真实的 diff 是另一方面。**[实际抓取案例](https://argot.tmonier.com/caught-in-the-wild)** 收集了过去一年中在 33 个真实的开源仓库(dagster、hono、rich、saleor、faker 等)上运行 argot 历史记录的已验证发现 —— 每一个发现都遭到了旨在推翻它的审查者的对抗性攻击,并且全部经受住了考验。
## CI
```
- uses: get-tmonier/argot@main # non-blocking voice score on every PR
```
`--format github` 会打印内联的 PR 批注;`--format sarif` 会将数据馈送至代码扫描工具;`--format json` 提供稳定的 schema。添加 `publish-badge: true`(配合 `contents: write`)即可获得一个实时更新的 README 徽章 —— `argot · N% in-voice`(N% 符合代码风格),在每次推送时更新。包含 pre-commit 在内的复制粘贴设置:[CI 指南](https://argot.tmonier.com/docs/ci/)。
## 工作原理
五个学习型检测器,一个真理来源 —— 你的 git 历史 —— 外加你自己编写的脚本规则。一个统计语言风格模型(两个频率表 + 一个 callee 集群划分 —— 没有神经网络)用于捕获外部的 import、callee 和 token 形状;一个本地代码嵌入模型(通过静态链接的 llama.cpp 运行 jina-code)用于捕获重复造轮子和放置错误;一个模块依赖图用于捕获架构分层倒置;一个测试清单 diff 用于捕获作弊的测试。几秒钟拟合,几毫秒检查,没有任何数据离开你的机器 —— 而且没有任何生成过程:每一个判定都是你可以重放的统计结果。完整细节:[工作原理](https://argot.tmonier.com/docs/how-it-works/) · [评分模型](https://argot.tmonier.com/docs/the-scoring-model/) · [性能](https://argot.tmonier.com/docs/performance/) · 位于 [docs/research/](docs/research/README.md) 的实验日志。
## 致谢
argot 的基准测试是针对用作**只读语料库**的真实仓库进行的 —— 在基准测试时克隆,从未重新分发,各自拥有自己的许可证,均未与 argot 有关联:FastAPI、rich、faker、Saleor、Wagtail、Dagster、Scrapy、Hono、Ink、faker-js、Excalidraw、Outline、Express、Commander.js、ESLint、GitHub CLI、Hugo、ripgrep、bat、Guava、JUnit 5、PowerShell、Jellyfin、redis、curl、RocksDB、fmt、Homebrew、RuboCop、Laravel 和 Composer。
基于 [tree-sitter](https://tree-sitter.github.io/tree-sitter/)(11 种语法)、通过 [git2](https://docs.rs/git2/) 调用的 [libgit2](https://libgit2.org/)、HuggingFace [tokenizers](https://github.com/huggingface/tokenizers)(UnixCoder BPE)、[Rhai](https://rhai.rs/)(脚本化规则)、[clap](https://docs.rs/clap/)、[Serde](https://serde.rs/) 和 [cargo-dist](https://opensource.axo.dev/cargo-dist/) 构建。语义层通过 [`llama-cpp-2`](https://crates.io/crates/llama-cpp-2) 静态链接了 [llama.cpp](https://github.com/ggml-org/llama.cpp) (MIT);其模型为 [Jina AI](https://jina.ai/) (Apache-2.0) 开发的 [**jina-embeddings-v2-base-code**](https://huggingface.co/jinaai/jina-embeddings-v2-base-code),在首次使用时作为 `Q4_K_M` GGUF 量化版本(Apache-2.0 §4 下的衍生作品)从 [`semantic-model-v1`](https://github.com/get-tmonier/argot/releases/tag/semantic-model-v1) 发行版中获取。argot 不附属于 Jina AI,也未获得其认可。
## 隐私
argot 100% 在本地运行 —— 没有遥测,无需账号,你的代码绝不会离开你的机器。完整政策:[argot.tmonier.com/privacy](https://argot.tmonier.com/privacy)。
## 许可证
MIT