tqakdev/ctxlint
GitHub: tqakdev/ctxlint
专为 AI 编程 agent 上下文文件打造的静态审计与覆盖率工具,帮助团队评估、清理和优化 context 健康状况。
Stars: 1 | Forks: 0
# ctxlint
**Linter + profiler + coverage,但专为 agent context 打造。**
`ctxlint` 审计、分析并评分用于引导 AI 编程 agent 的 context 文件 —
`AGENTS.md`、`CLAUDE.md`、`.cursor/rules/*.mdc`、`.github/copilot-instructions.md`、
`.windsurf/rules/*.md` 以及 skills。它回答了目前没人能回答的关于他们 repo 的问题:
- **我的 agent 到底加载了什么?** 按工具、按目录划分,加载顺序是什么,以及为什么。
- **成本是多少?** 每次请求注入的 context 的 token 总量。
- **哪些规则失效了?** 在不同工具间重复、发生偏差、相互矛盾、
指向已不存在的文件,或者被深埋以至于被忽略。
- **它们有帮助吗?** 根据你的规则评判最近的 commit,以发现违规情况和
对任何内容都不起作用的死规则。
## 安装
npm 包是 **`@tqakdev/ctxlint`**;它安装的命令是 **`ctxlint`**。
```
npx @tqakdev/ctxlint scan # one-off, no install
npm i -g @tqakdev/ctxlint # then run `ctxlint` anywhere
```
## 快速开始(30 秒)
```
npx @tqakdev/ctxlint scan
```
就是这样 —— 无需配置。你将获得一个 Context 健康分数(0–100)、一个精确到每个 agent 加载了哪些文件及其成本的按工具划分的表格,以及一个让你在不到一分钟内就能采取行动的发现列表。
## 报告示例
在一个包含手动维护且已发生偏差的 context 文件的 repo 上运行 `ctxlint scan`:
```
ctxlint — 5 context file(s), 95 rules
Context Health Score: 46/100
freshness 0 uniqueness 8 consistency 76 budget 82 structure 84
Context files
file kind tools tokens≈ rules
.cursor/rules/broken.mdc cursor-rule cursor ≈37 2
.cursor/rules/style.mdc cursor-rule cursor ≈184 7
.cursorrules other — ≈56 5
.github/copilot-instructions.md copilot-instructions copilot ≈210 9
CLAUDE.md claude-md claude-code ≈2812 72
claude-code @ . — ≈2812 tokens always-on
# file tokens≈ why
1 CLAUDE.md ≈2812 CLAUDE.md at repo root
cursor @ . — ≈184 tokens always-on (+ ≈37 conditional)
# file tokens≈ why
1 .cursor/rules/broken.mdc ≈37 frontmatter unparseable — activation unknown, assumed not auto-attached
2 .cursor/rules/style.mdc ≈184 alwaysApply: true
Findings: 15 error(s), 6 warning(s), 2 info
✖ [duplication] Same rule maintained twice for different tools: .cursor/rules/style.mdc:10-11
and CLAUDE.md:65-66 are 100% identical. Keep one canonical copy (prefer AGENTS.md) and
delete the other.
✖ [stale-reference] CLAUDE.md:45-49 references `src/api/v1/` which does not exist —
actively misleading the agent. Update the reference or delete the rule.
▲ [drift] These rules started identical and diverged (66% similar):
.github/copilot-instructions.md:7-8 vs CLAUDE.md:104-105.
diff: … against the [-main-] {+develop+} branch and …
▲ [contradiction] Contradictory instructions about the same thing (polarity heuristic
is English-only):
A: "Always use named exports in shared modules."
B: "Never use named exports for React components."
▲ [budget] 9 critical rule(s) buried past 70% depth of an oversized file (CLAUDE.md,
deepest at line 217 = 99%) — likely lost in long sessions. Move critical rules to the front.
ℹ [load-semantics] .cursorrules is read by no tool ctxlint knows (legacy format) —
migrate its rules into .cursor/rules/*.mdc or AGENTS.md, then delete this file.
```
## 命令
### `ctxlint scan [path]` — 静态分析(默认命令)
发现每一个 context 表面(遵循 `.gitignore` 和 `discovery.exclude`,
跳过符号链接和超过 1 MB 的文件),将它们拆分为原子规则,解析
特定于工具的加载语义,运行五个分析器,并打印报告。
加载顺序语义是根据每个工具的官方文档建模的;报告带有
文档链接、最后验证日期,以及模型做出的每个假设
(`--format md`/`json` 中的“加载顺序模型出处”表),因此当工具改变
行为时,过时的假设是可见的,而不是默默出错。
| flag | 作用 |
|---|---|
| `--format text\|json\|md\|sarif` | 输出格式(默认 `text`);`sarif` 可接入 GitHub code scanning |
| `--output ` | 将报告写入文件 |
| `--ci` | 当存在 error 级别的发现时以退出码 1 退出 |
| `--max-files ` | 在超大型 monorepo 上限制发现遍历 |
| `--no-user-global` | 忽略 `~/.claude/CLAUDE.md`(在 `fix` 时也是如此)—— 默认情况下它被视为真实的 context,这意味着每台机器上的发现可能不同;在 CI 中请传入此参数 |
### `ctxlint fix [path]` — autofix 规划器
写入 `ctxlint-fixes.md`,将每个修复分组为 **安全**(可自动应用)和
**建议**(需要人工判断)。`--write` 仅应用安全集合:
1. 删除完全重复的内容,保留规范副本(AGENTS.md 优先级最高);
2. 当 git 历史显示存在唯一的重命名目标时更新过时路径 —— 且仅当
该目标仍存在于代码树中,并且仅限发现指出的确切引用
(规则中其他有效的路径绝不会被动);
3. 将被深埋的关键规则移至前面 —— 仅限作者大写
(NEVER/MUST/ALWAYS)的规则;小写的判断性建议保持为建议。
`--write` 拒绝在脏的 git 树上运行,因此每次更改都是可审查和
可回滚的。
### `ctxlint compliance [path]` — 规则真的有帮助吗?(需要 `ANTHROPIC_API_KEY`)
抽样你最近的 N 次合并更改(默认 30 次;回退到普通 commit),跳过
lockfile/vendored/generated 代码,根据文件和
关键字重叠预过滤 (rule, diff-chunk) 对,然后让模型对每对输出严格的 JSON 判定:
`followed | violated | not-applicable`,并带有一行证据引用。
- **预算上限**:总成本 —— 包括 `--calibrate` 的第二个模型样本 —— 都会
预先估算;任何超过 $1(可配置)的消耗都需要 `--yes`。
- **磁盘缓存**:判定以 (rule, chunk, model) 为键,因此重新运行是增量的
且几乎是免费的。
- **死规则**:会指出在整个样本中不适用于任何内容的规则 —— 它们
在每次请求时都会消耗 token 且从不改变行为。
- **`--calibrate`**:使用第二个模型重新判定 10% 的样本,并打印
一致率。低于 80% 时,报告会醒目地
告诉你将每条规则的分数仅视为方向性参考。
### `ctxlint report` — 重新渲染上次扫描
从 `.ctxlint-cache/last-scan.json` 重新生成报告,无需重新扫描
(`--format`、`--output` 同上)。
## GitHub Action
添加一个 Context 健康检查,它会在每个 PR 上发表评论:
```
# .github/workflows/ctxlint.yml
name: context-health
on: [pull_request]
permissions:
contents: read
pull-requests: write
jobs:
ctxlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: tqakdev/ctxlint/action@action-v1
with:
fail-on-error: "true" # gate the job on error-severity findings
fail-on-score: "0" # optionally require a minimum Context Health Score (0 disables)
comment: "true" # post/update the report as a PR comment
```
该操作还会将报告写入作业摘要,并公开 `score` 和 `errors`
作为输出。
### GitHub code scanning (SARIF)
通过标准的 SARIF 上传,发现的问题可以进入 repo 的 Security(安全)选项卡 — 并在 PR 上带有文件/行注释 —
:
```
- run: npx @tqakdev/ctxlint scan --format sarif --output ctxlint.sarif
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: ctxlint.sarif
```
## Pre-commit hook
在损坏的 context 文件提交之前捕获它们。纯粹的 git hook —— 无需额外依赖:
```
# .git/hooks/pre-commit (chmod +x)
#!/bin/sh
# 仅当 commit 中包含 context file 时运行。
if git diff --cached --name-only | grep -qE \
'(^|/)(AGENTS|CLAUDE|SKILL)\.md$|\.cursor(rules)?(/|$)|\.mdc$|\.windsurf(rules)?(/|$)|copilot-instructions\.md$'; then
npx @tqakdev/ctxlint scan --ci --no-user-global || {
echo "ctxlint: error-severity findings — fix them or commit with --no-verify" >&2
exit 1
}
fi
```
使用 [husky](https://typicode.github.io/husky/):
```
echo "npx @tqakdev/ctxlint scan --ci --no-user-global" > .husky/pre-commit
```
`--ci` 仅在遇到 error 级别的发现(过时引用、跨工具
重复)时以退出码 1 退出;警告和信息永远不会阻止 commit。`--no-user-global` 保持
门禁确定性 —— 没有它,发现可能涉及你个人的
`~/.claude/CLAUDE.md`,而队友的机器上并没有这个文件。
## 配置
一切都有针对典型 repo 的默认值;创建 `ctxlint.config.json` 仅用于
更改某些内容(schema 见 [`ctxlint.config.schema.json`](./ctxlint.config.schema.json)):
```
{
"budgets": { "surfaceWarnTokens": 1500, "effectiveContextWarnTokens": 4000 },
"compliance": { "model": "claude-haiku-4-5", "commits": 30, "spendCapUsd": 1 }
}
```
| section | option | default | meaning |
|---|---|---:|---|
| budgets | `surfaceWarnTokens` | 1500 | 当单个文件超过此估算 token 数时发出警告 |
| budgets | `effectiveContextWarnTokens` | 4000 | 当单个工具的常驻 context 超过此限制时发出警告 |
| budgets | `buriedRuleDepthRatio` | 0.7 | 标记深度超过超大文件此比例的关键规则 |
| discovery | `maxFiles` | 20000 | 对 repo 遍历的硬性上限(`--max-files` 可覆盖) |
| discovery | `exclude` | `[]` | 用于非活跃表面 context 文件的 glob(测试固定装置、示例)—— 被分析跳过但保留在 repo 索引中,因此对它们的引用仍然有效 |
| analysis | `maxRules` | 5000 | 成对分析器在此限制之上会优雅退出 |
| compliance | `model` / `calibrationModel` | haiku / sonnet | 判定模型和第二意见模型 |
| compliance | `spendCapUsd` | 1 | 超过此估算花费时需要 `--yes` |
## 分数意味着什么 —— 以及不意味着什么
诚实说明部分。在相信一个数字之前,请阅读此内容。
- **Token 计数是估算值。** 离线计数使用 js-tiktoken (`o200k_base`) 并
在各处标记为 `≈`;不同的供应商的 tokenizer 会有所不同,因此请将计数视为 ±10–20%,对于 CJK(中日韩)内容丰富的场景误差会更大。设置了 `ANTHROPIC_API_KEY` 后,`scan` 会获取
精确的 Anthropic 计数并将其标记为 exact。
- **Context 健康分数是确定性的,而不是神谕。** 相同的输入,相同的分数 ——
公式记录在 `src/core/scoring.ts` 中(五个加权子分数;error/warn/info 的每项发现
惩罚分别为 25/10/4,子分数内的每次重复计为前一次的 0.8 倍,这样一个糟糕的文件就不会导致整个子分数降至最低,尽管约 8 个 error 仍会
使其降至 0)。它是一个趋势指标:在 CI 中观察它的变化,不要迷信
绝对数字。
- **合规性判定是基于判定者的。** LLM 阅读规则和 diff 并给出
意见。运行 `--calibrate` 来衡量跨模型的一致性;低于 80% 时,报告
本身会告诉你分数仅供参考。死规则检测取决于你的
样本规模 —— 在 30 次 commit 中都不适用的规则可能下周就会适用。
- **加载语义是尽力而为的。** 工具行为是根据文档化的
行为编码的;在未文档化的地方,表格会写上 "(assumed)" 而不是将
猜测作为事实呈现。旧版 `.cursorrules` 被视为没有被任何东西加载(假设);
旧版 `.windsurfrules` 被视为仍被 Windsurf 读取(已弃用的格式)。
Cursor 的 `.mdc` frontmatter 按照 Cursor 读取它的方式读取:未加引号的 glob
(`globs: **/*.ts` —— Cursor 自身编辑器写入的内容)不是有效的 YAML,但会
被恢复,并且 `{ts,tsx}` 花括号 glob 会正确激活;只有没有
工具能读取的 frontmatter 才会被标记为损坏。
- **某些启发式方法仅支持英语。** 重复/偏差的 shingle 适用于任何
语言;基于极性的矛盾检测只能理解英语的
always/never 表达方式,并在每个发现中予以说明。
- **成对分析是 O(n²) 的。** 超过 5000 条规则(可配置)时,重复/偏差
分析会带着提示优雅退出,而不会挂起你的 CI。
## 基准测试:在真实 repo 上测量的准确率
ctxlint 在七个指定的开源 repo(openai/codex、
sst/opencode、All-Hands-AI/OpenHands、cline/cline、block/goose、vercel/ai、
browser-use/browser-use — `bench/corpus.json`)上生成的每个发现都是根据实际的 checkout(`bench/labels.json`,120 项发现)手动标记为真阳性/假阳性
:
| category | precision | tp / fp | notes |
|---|---:|---|---|
| budget | 98% | 57 / 1 | token 数学就是 token 数学 |
| stale-reference | 91% | 52 / 5 | 旗舰分析器 |
| duplication | 100% | 2 / 0 | 小样本 |
| contradiction | 33% | 1 / 2 | 小样本 —— 正在重新制作 |
| structure | — | 0 / 0 | 早期所有的误报已修复 |
| **overall** | **93%** | **112 / 8** | |
第一次标记阶段测得的总体准确率为 67%(stale-reference 为 52%)。而不是
发布它就完事了,被标记的假阳性成为了修复列表:
根据祖先目录和 `cd` context 解析引用,补全
import 指定符的扩展名(`./native-request` → `native-request.ts`),将
任何地方存在的纯文件名视为可找到的,理解创建/移除/
条件语句("do not create X"、"when `.pr/` exists"),并丢弃
非路径 token(`text/*`、`Schema.Json`、`kebab-case.ts`、省略号路径)。
同样的方式发现了两个解析 bug。存留下来的真阳性是
真实的:cline 的整个 `copilot-instructions.md` 描述了一个
不再存在的 repo 布局,而 OpenHands 的 AGENTS.md 仍然指向其
重构前的代码树。
使用 `pnpm bench` 复现(克隆指定的 SHA,约 200 MB);如果
分析器输出偏离了已提交的快照,`pnpm bench --check` 将会失败。每当快照发生变化时都会重新审计标签 —— 精度声明始终与
赚取它们的精确代码绑定。
## 开发
```
pnpm install
pnpm test # vitest, fixture-driven — no network calls anywhere in tests
pnpm typecheck # TS strict
pnpm lint # biome
pnpm build # emits dist/, runnable as node dist/cli.js
```
`test/fixtures/` 下的三个 fixture repo 是测试床:`clean-repo` 得分为
100,`messy-repo` 触发每个发现类别,`monorepo` 运行子树和
glob 范围的解析。
## 许可证
MIT
标签:AI编程助手, MITM代理, SOC Prime, 云安全监控, 开发工具, 性能分析, 暗色界面, 自动化攻击, 静态分析