关于 since-cutoff
since-cutoff 是面向 Python 项目和编码 Agent 的命令行工具及 MCP 服务。它静态比较锁定依赖与模型训练截止时间附近版本的 API,找出代码使用的变更接口,并可生成说明,无需调用模型。
主要功能
- 01静态比较 Python API
- 02查找代码使用的变更接口
- 03将说明写入 AGENTS.md 或 CLAUDE.md
- 04通过 CI 和钩子检查说明是否过期
- 05可选的模型评测与类型检查
适用场景
- 01使用编码 Agent 前检查依赖
- 02升级后保持 API 变更说明最新
- 03检查模型是否使用过时 API
MCP 相关
MCP 服务向 Agent 提供相同的依赖 API 差异信息,便于其在编写代码前查询变更。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
3收录的工具列表 · 更新于 2 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| model | string | 是 | 模型 ID,例如 "claude-haiku-4-5"、"claude-sonnet-4-5"、"claude-opus-4-6[1m]"、"gpt-5.4"、"gemini-2.5-pro"、"anthropic/claude-haiku-4.5" 或 "openai:gpt-5.4"。请传入你自己的 ID。别名 "sonnet"、"opus" 和 "haiku" 表示该系列的最新模型。 |
详细介绍
since-cutoff
你的编码助手的训练数据早于依赖项的最新版本,因此它可能会编写固定版本已不再接受的调用。since-cutoff 会指出代码中使用了哪些在模型训练截止日期之后发生变化的 API,并编写简短说明,帮助助手避免使用旧形式。
since-cutoff 是一款面向 Python 项目的命令行工具和 MCP 服务。默认情况下,scan 会读取锁定文件,选取每个依赖项在编码模型训练截止日期至少 30 天前发布的最新版本,以静态方式将该版本的公开 API 与你固定的版本进行比较,并指出代码中使用了哪些已变更的 API 及其位置,同时为每项变更提供说明。sync 会将说明写入 AGENTS.md 或 CLAUDE.md,并确保其与锁定文件保持同步;status、pre-commit 钩子和 GitHub Action 会在说明过时时提醒你。可选的 run 会衡量模型实际误用了哪些变更,以及这些说明是否有帮助。只有 run 会调用模型。
English | 简体中文 | Español | Français
安装
需要在 Linux、macOS 或 Windows 上使用 Python 3.10 或更高版本。
pipx install since-cutoff # or: pip install since-cutoff
也可以不安装直接运行:uvx since-cutoff scan 会下载当前版本并运行(uvx since-cutoff@latest scan 会获取新版本,而不是使用 uv 缓存的第一个版本)。
run 会使用 basedpyright 对模型的回答进行类型检查。这是一个额外的 180 MB 依赖,其他命令不会使用:pip install "since-cutoff[run]"(或 pipx install "since-cutoff[run]")会安装它;uvx --with basedpyright since-cutoff run 则会在运行时添加它,而不进行安装;如果没有它,run 会立即停止并提示原因。
请在项目根目录下运行。它会读取 uv.lock、poetry.lock、pdm.lock、pylock.toml、Pipfile.lock、requirements*.txt、pyproject.toml、Pipfile、conda environment.yml 中的 pip: 块或 .venv(不会读取 setup.py 或 setup.cfg)。如果未指定 --model,它会根据 Claude Code、Codex、Gemini CLI、OpenCode 或 Aider 的设置使用编码 Agent 已配置的模型;如需使用其他模型,请传入 --model(参见选择模型)。scan、sync 和 status 不会调用模型,也不需要 API 密钥;run 会向模型提供方发送提示词,并使用你的 API 额度或 Claude Code 用量。
在编码 Agent 中,技能会为你运行这些命令:参见在 Claude Code 中使用和在其他编码 Agent 中使用。MCP 服务可为任何 Agent 提供相同的差异信息。
快速开始
示例项目
调用 client.messages.create,并固定使用 anthropic 1.8.0。在 Claude
Sonnet 4.5 的训练截止时间,anthropic 的最新版本是 0.60.0,其中 create 仍接受
temperature;1.8.0 则会因此引发 TypeError。scan 输出的内容,截取 anthropic 部分后如下(它从 0.56.0 开始比较,这是截止时间前 30 天发布的最新版本:模型对截止时间前几周的信息掌握得最少):
$ uvx since-cutoff scan --model anthropic:claude-sonnet-4-5
Your code uses 2 APIs that changed after 2025-07-01, 30 days before claude-sonnet-4-5's training
cutoff (2025-07-31)
huggingface-hub 0.33.1 -> 2.0.0 (0.33.1 was the latest release 30 days before the cutoff;
pyproject.toml pins 2.0.0)
...
anthropic 0.56.0 -> 1.8.0 (0.56.0 was the latest release 30 days before the cutoff; pyproject.toml
pins 1.8.0)
Messages.create: temperature, top_k and top_p were removed uses this API
app/main.py calls create
Note: `Messages.create()` no longer accepts `temperature`, `top_k` or `top_p` as keyword
arguments. If the API still needs them, pass them through its `extra_body` argument.
[diff]
2 notes ready: `since-cutoff sync` writes them to AGENTS.md and keeps them in step with
pyproject.toml.
app/main.py 中的调用没有传入这些参数,因此会标记为“uses this API”,而不是“old form”:它目前可以正常工作,但编写 0.60.0 版本代码的助手在编辑该调用时,可能会加入 temperature=0.2。since-cutoff sync 写入 AGENTS.md 的说明就是为了避免这种情况;其中的标签 [diff] 表明了说明的依据:对两个版本公开 API 的静态比较。这些参数已从签名中移除,但这不等于 API 已删除该字段,因此,如果固定版本的方法有 extra_body 参数,说明会指出这一点,而不是让助手删除该字段。
接着,since-cutoff sync 会写入这两条说明。它会以差异形式打印该代码块,并询问
Write this to AGENTS.md? [y/N](--yes 会直接写入,不再询问;--dry-run 仅显示
差异)。对于示例项目,该代码块以以下内容结尾:
**anthropic 1.8.0** (compared from 0.56.0)
- `Messages.create()` no longer accepts `temperature`, `top_k` or `top_p` as keyword arguments. If the API still needs them, pass them through its `extra_body` argument. [diff]
**huggingface-hub 2.0.0** (compared from 0.33.1)
- `huggingface_hub.hf_hub_download()` no longer accepts `proxies`, `force_filename`, `local_dir_use_symlinks` or `resume_download`; do not pass them. huggingface-hub's deprecation text says there is no replacement for `force_filename`, `local_dir_use_symlinks` or `resume_download`. For `proxies`, see https://github.com/huggingface/huggingface_hub/releases for the changes since 0.33.1. [diff]
要点上方是起始标记、一行元数据(模型、截止时间、版本来源、依赖项哈希和代码块文本本身的哈希,以便显示手动编辑)以及一个标题,其中列出了模型、截止时间和版本来源文件,说明了各标签的含义,并指出没有运行任何库代码。整个代码块约 410 个 token。代码块外的文本会原样保留,而 since-cutoff unapply 会移除该代码块。升级后,请再次运行 sync;在 CI 中运行 sync --check,以及离线运行 status,可以了解说明是否已过时(保持说明为最新)。
在你的项目中试用。 无需 API 密钥,也不会调用模型。在项目根目录中:
uvx since-cutoff scan
它会检测你的编码模型(或传入 --model),读取锁文件,并获取每个依赖项在模型训练截止时间前至少 30 天发布的最新版本(默认情况下),然后以静态方式将该版本的公开 API 与你固定的版本进行比较:不会运行任何包代码。截止时间只决定要检查哪些变更,并不说明模型记住了什么。你的模型是否真的会弄错这些内容,以及这些说明是否有帮助,可通过
since-cutoff run
来衡量;它会调用你的模型,是可选步骤。
适用场景及其不做的事
当你使用编码 Agent 编写 Python,并固定了模型训练截止时间之后发布的依赖版本时,可以使用它;对于 Python AI 技术栈中的大多数依赖,情况都是如此 (问题所在):
- 用
scan查看代码:找出代码使用的已更改 API、它们所在的位置,以及各自的说明; - 用
sync将说明写入 AGENTS.md 或 CLAUDE.md 并保持更新;如果说明过时,则可通过sync --check、status、pre-commit hooks 或 GitHub Action 获知; - 使用 MCP 服务,让 Agent 在编写代码前询问某个库有哪些变化;
- 用
run确认模型是否真的会写出旧 API,以及这些说明能否纠正它。
它不会:
- 调用模型,但
run除外:scan、sync、status、MCP 服务、Action 和 hooks 均不需要 API 密钥; - 运行包代码或模型编写的代码:包只进行静态分析,回答只做类型检查;
- 发现未更改签名背后的行为变化,或仅在运行时发出警告的弃用;
- 指出替代方案,除非库自身的弃用说明中有所说明;
- 判断模型记住了什么:截止时间用于选择对比版本,只有
run会询问模型; - 重写你的代码:对于迁移已有代码,codemod 才是合适的工具(对比说明);
- 支持其他语言:目前仅支持 Python,TypeScript 已列入 #1;此外,它会指出文件而非行号(#8)。
完整列表请参阅限制。
问题所在
每个模型都有训练截止时间,而你的 lockfile 却在不断变化。当某个库在截止时间之后更改了其公共 API,训练数据早于该更改的模型就可能继续编写旧调用。其中一些代码会在导入或调用时失败。另一些代码仍能运行,因为旧形式只是被弃用,或者仍可接受但会发出警告。
以下是在示例项目中,since-cutoff scan 为 Claude Sonnet 4.5(训练截止时间为 2025 年 7 月)发现的一些变化。该项目的九个依赖中有六个固定到了当前版本(其他三个未固定,工具会使用最新版本):
| library | compared from | pinned | what changed |
|---|---|---|---|
| anthropic | 0.56.0 | 1.8.0 | 不再接受 messages.create(temperature=..., top_p=..., top_k=...) |
| huggingface-hub | 0.33.1 | 2.0.0 | hf_hub_download(resume_download=..., force_filename=..., local_dir_use_symlinks=...) 在 1.0 中已从签名移除(2.0.0 在运行时仍接受这些参数,但会忽略它们并发出警告) |
| langchain-core | 0.3.67 | 1.6.5 | retriever.get_relevant_documents() 和 llm.predict() 已移除 |
| openai | 1.93.0 | 3.19.2 | 24 项破坏性更改,6 项新增弃用(另有 5 项内部更改) |
每个库都与截止时间之前至少 30 天发布的最新版本进行对比(这是扫描的默认缓冲期),因此变化可能发生在截止时间前几周。在该项目中,9 个依赖里有 7 个自该版本发布以来更改了公共 API。静态差异分析标记出公共 API 中的 331 项破坏性更改和 24 项新增弃用,以及内部 API 中的 139 项更改;项目代码使用了其中 2 个已更改的 API。
这并非只涉及某个模型或某家厂商。在 36 个广泛使用的 Python AI 库和来自 OpenAI、Anthropic、Google、xAI、DeepSeek、Qwen、Moonshot 及 Mistral 的 21 个模型中,即使是测试过的最新模型 Claude Opus 5.5(截止时间为 2026 年 6 月),其训练数据也早于 36 个库中 20 个库的一项公共 API 破坏性更改(完整结果):
since-cutoff 会从以下三个方面解决这个问题:
scan会为每项依赖找出至少在模型训练截止时间前 30 天发布的最新版本(默认设置),将其公开 API 与你固定的版本进行比较,并指出你的代码使用了哪些已更改的 API、使用位置以及对应说明。不会调用模型,也不需要 API 密钥。sync会在显示差异后,将这些说明写入 AGENTS.md(或 CLAUDE.md)中的标记区块,并使其与锁定文件保持同步;sync --check和status会在这些内容过时的时候通知 CI、提交前检查和你的 Agent。每条说明都根据 API 差异撰写,并标注已检查的内容。不会调用模型。run是可选功能,会让模型完成需要使用已更改 API 的简短编码任务,不提供工具或文档,并使用类型检查器针对两个版本评估每个答案:过时、错误、已弃用或正确。没有任何 LLM 评判结果。对于每个失败,它都会写一条说明;只有当模型撰写的示例能通过你所用版本的类型检查时,才会保留该说明;之后还会在有无这些说明的情况下,使用保留任务重新测试模型。
Agent 也可以通过 MCP 服务获取相同的差异,CI 则可以通过 GitHub Action 和提交前钩子获取。下面的视频用 2 分半钟介绍了本节内容。
## 使用方法# the changed APIs your code uses, with a note for each (no model calls, no API key)
uvx since-cutoff scan
# write those notes into AGENTS.md (shows the diff and asks first); run it again after upgrades
uvx since-cutoff sync
# optional: measure which of the changes your model gets wrong, and test the notes (calls it;
# --with adds the basedpyright type checker, which only run needs)
uvx --with basedpyright since-cutoff run
以下是 scan 为示例项目输出的完整内容:
这两个 API 都属于“使用此 API”;传入 resume_download=True 或 temperature=0.2 的文件会被视为使用“旧形式”。scan -v 会列出使用某个 API 的所有文件(此处显示 3 个),而 scan --all 会按包列出公共 API 的所有其他变更(--internal 会添加内部 API 变更,每项计数都会将其单独列为“+N internal”)。scan --json 和 .since-cutoff/results.json 中的 used_apis 内容相同(每个 API、使用位置、变更及其说明,包括说明的标签、适用版本以及检查内容),.since-cutoff/report.md 则以“代码使用情况”开头。
保持说明为最新:sync 和 status
since-cutoff sync(0.4.0 及更高版本)会将 scan 显示的说明写入标记区块:写入 AGENTS.md;如果只有 CLAUDE.md,则写入 CLAUDE.md;如果 CLAUDE.md 未导入 AGENTS.md,则同时写入两者;如果已有区块,则写入该区块(--target 用于指定其他文件)。它会打印统一差异并询问
Write this to AGENTS.md? [y/N];--yes 会跳过询问直接写入,如果没有可用于询问的终端,则不会写入任何内容。区块外的文本会逐字节保留,包括 CRLF 换行符,而 since-cutoff unapply 会移除该区块。示例项目的区块见快速开始。Runtime 注意事项不会写入区块:无论如何,建议都是“不要传入这些参数”。
更改锁文件或代码后,请再次运行 sync。它会为代码开始使用的 API 添加说明、重新检查版本已升级的包,并在某个包不再是依赖项、版本不高于对比版本,或其变更的 API 已不再使用时,删除该包的说明并说明原因。它会保留写入区块时使用的模型和截止日期(这样使用其他模型的 Agent 的团队成员就不会来回改写区块),除非你传入 --model 或 --cutoff;--model a,b 会使用它们中最早的截止日期。如果没有变更,它不会写入任何内容,文件的字节内容和修改时间也会保持不变。
| 命令 | 作用 | 退出代码 |
|---|---|---|
since-cutoff sync | 显示差异、询问并写入 | 0 已写入或已是最新;3 未写入(你选择了否,或没有可用于询问的终端) |
since-cutoff sync --yes | 跳过询问直接写入 | 0 |
since-cutoff sync --dry-run | 显示差异,不写入 | 0 |
since-cutoff sync --check | 不写入(用于 CI 和提交前检查) | 0 已是最新;3 已过期 |
since-cutoff sync --json --yes | 跳过询问直接写入;打印 JSON 提案和写入结果 | 与 sync --yes 相同 |
since-cutoff sync --json --check | 打印 JSON 提案;不写入 | 0 已是最新;3 已过期 |
since-cutoff sync --json --dry-run | 打印 JSON 提案;不写入 | 0 |
since-cutoff status | 离线比较区块与锁文件 | 0 当前或没有区块;3 已过期;1 标记损坏 |
当代码块被手动编辑后,sync 和 sync --check 会显示差异、不写入任何内容,并以代码 4 退出;sync --force 会替换它。如果无法检查说明涉及的软件包(PyPI 无法访问,或当前运行的 Python 无法解析其源代码),sync 不会写入任何内容,并以代码 1 退出。 |
sync --json 必须与 --yes、--check 或 --dry-run 一起使用;否则会以代码 2 退出,且不会提示确认。标准输出仅包含 JSON;如果 sync 失败(使用错误,退出代码 2,或 PyPI 无法访问等错误,退出代码 1),标准输出将为空。进度、差异和错误会输出到标准错误。对象包含 exit_code、tip(sync 对 AGENTS.md 和 CLAUDE.md 的提示,或 null)以及 targets。每个目标会报告 target、action、changed、edited、written、model、cutoff、scope、notes、changes(每个软件包的 package、done 和 state)、retest 和 diff_lines。written 用于区分已应用的提案、预览结果和被拒绝的手动编辑。常规退出代码和 --force 行为仍然适用。
since-cutoff status 不读取网络或代码:它会逐个软件包报告说明针对的版本和锁文件中的版本、其他依赖项是否发生变化,以及编码 Agent 配置所用模型的训练截止日期是否早于说明中的日期(并提供对应的 sync --model 命令)。status --json 供脚本使用。status --hook 仅在说明已过时的情况下输出一行,并且始终以 0 退出,例如:
since-cutoff: the library notes in AGENTS.md are out of date: anthropic 1.8.0 in the notes, 0.60.0 in pyproject.toml. `since-cutoff sync` updates them.
更多选项:
sync --scope imported还会记录每个有变化的软件包中最可能相关的变更(每个软件包最多 5 个 API),适用于尚未使用任何已变更 API 的代码。符合这种情况时,scan会建议使用此选项。sync --profile evaluated是基准测试所测量的配置:--scope imported,每个软件包最多 5 个 API(等同于同时指定这两个标志)。默认范围used只记录代码已调用的变更 API,因此会在更少的项目中写入更短的代码块,但基准测试未对其进行测试。说明的措辞在测量所用的版本(0.4.1)发布后有所变化,而且该版本是从截止日期对应的版本开始比较的:添加--cutoff-margin 0,即可从同一天开始比较。sync --header-only只写入标头:列出自比较起点版本以来 API 已变更的软件包、各自锁定的版本和该起点版本,并附上跟进这些变更的指示;不包含变更行(--no-header-only会重新写入这些行)。这是基准测试中仅含标头的那组结果:篇幅更短(单个软件包时约 250 个 token,而完整代码块约为 285 到 505 个 token),并会告知 Agent 应检查哪些软件包,但不说明发生了什么变化或应改用什么。未指定--scope时,新标头会列出代码导入的软件包。sync --suggestions会添加锁定版本中看起来与已移除内容相似的名称,并标记为[not confirmed]。代码块会记录这些选项(范围、每个软件包的 API、是否仅含标头、建议),后续 sync 会保留它们。since-cutoff run --apply写入的说明会标记为[type-checked]。只要软件包版本不变且代码仍使用该 API,sync就会保留每条此类说明,用它替代差异中针对同一 API 的说明。 Claude Code 会读取 CLAUDE.md,只有在不存在 CLAUDE.md,或 CLAUDE.md 通过一行@AGENTS.md导入 AGENTS.md 时,才会读取 AGENTS.md (记忆文档);Codex、Cursor 和 Copilot 会读取 AGENTS.md; Gemini CLI 会读取 GEMINI.md,只有其 settings.json 中的context.fileName列出了 AGENTS.md 时才会读取该文件 ("context": {"fileName": ["AGENTS.md", "GEMINI.md"]};GEMINI.md 文档)。如果这两个文件都存在且尚无区块,当 CLAUDE.md 导入 AGENTS.md 时,备注只会写入 AGENTS.md;否则会写入两个文件 (#13); 此时,scan、sync和status会说明如何只保留一份:在 CLAUDE.md 中添加@AGENTS.md这一行,并运行since-cutoff unapply --target CLAUDE.md。如果区块只存在于 AGENTS.md 中,它会继续留在那里;当 CLAUDE.md 没有导入 AGENTS.md 时,这些命令也会说明这一点。如果两个文件都有区块,且 CLAUDE.md 随后导入了 AGENTS.md,它们会说明 CLAUDE.md 中有第二份副本,并提供相同的unapply命令。代码 span 或围栏代码块中的@AGENTS.md不会被视为导入。
测试你的模型
since-cutoff run 是可选步骤,会调用模型。它会向模型提出一些需要用到已更改 API 的简短任务,不提供工具、文档或你的任何代码;用类型检查器对照两个版本为答案评分;为每个错误写一条备注;并在留出的任务上测试这些备注(工作原理)。
它会使用你的 API 额度或 Claude Code 用量,耗时可能为 5-20 分钟。
since-cutoff run --quick # a smaller run: 12 probes, 1 held-out task, 3 regression checks
since-cutoff run --apply # write this run's notes into the block
如果不加 --apply,备注只会显示,不会写入。run --apply 会用本次运行的备注替换区块;之后运行 since-cutoff sync 会添加 diff 中与你的代码使用的其他已更改 API 相关的备注,并在对应软件包的版本保持不变时保留本次运行中标记为 [type-checked] 的备注。
在 Claude Code 中
/plugin marketplace add MohammadHijjawi97/since-cutoff
/plugin install since-cutoff@since-cutoff
然后让 Claude“检查我们的哪些依赖版本落后了”,或运行
/since-cutoff:since-cutoff。此技能会运行 since-cutoff scan 并提供备注:它会运行
since-cutoff sync --dry-run,向你展示 diff,并且只会在你同意后写入。你让它测试模型时,测试工作会交由模型的一个全新无工具副本完成,因此 Agent 无法给自己评分。该插件还会启动
MCP 服务,这样
Claude 就能在编写代码前查询库的变更;并且从 0.4.0 版起,会在会话开始时运行
since-cutoff status --hook,当备注过期时向会话添加一行提示(首次运行时,uvx 会下载 since-cutoff)。
在其他编码 Agent 中
npx skills add MohammadHijjawi97/since-cutoff
这会通过开源的 skills
CLI,为 Codex、Cursor、Gemini CLI、GitHub Copilot、OpenCode 和其他读取
SKILL.md 的 Agent 安装相同的技能。since-cutoff 也会从 Codex、Gemini CLI、OpenCode 和 Aider 的设置中读取模型;对于其他 Agent,请告知它要使用哪个模型,例如
since-cutoff scan --model openai:gpt-5.4。
按下方所示添加 MCP 服务。
效果不错的提示词:
- “我们的代码使用的哪些 API 在你的训练截止日期之后发生了变化?”Agent 会运行
since-cutoff scan,或调用 MCP 工具project_changes。 - “把这些 API 的备注添加到 AGENTS.md。”Agent 会运行
since-cutoff sync --dry-run,向你展示 diff,并在你同意后运行since-cutoff sync --yes。 - “测试一下这些变更中,哪些是你实际会弄错的。”Agent 会先征求你的同意,然后运行
since-cutoff run --quick。 - “编写 httpx 代码之前,先检查一下自你的截止日期以来 httpx 有哪些变更。”Agent 会调用 MCP 工具
api_changes。
选择模型
--model | 用途 | 需要 |
|---|---|---|
claude-code(未设置模型时的默认值) | 使用你的 Claude Code 登录信息(订阅或密钥)和当前模型 | claude CLI |
claude-code:sonnet、claude-code:claude-haiku-4-5 | 指定的 Claude 模型 | claude CLI |
anthropic:<model> | Anthropic API | ANTHROPIC_API_KEY |
openai:<model> | OpenAI API | OPENAI_API_KEY |
openrouter:<vendor/model> | OpenRouter | OPENROUTER_API_KEY |
deepseek:<model> | DeepSeek API | DEEPSEEK_API_KEY |
ollama:<model> | 本地 Ollama | Ollama 正在运行 |
openai-compatible:<model> | 任何兼容 OpenAI 的服务 | --base-url,OPENAI_API_KEY 可选 |
“需要”列适用于会调用模型的 run;scan 和 sync 只使用模型的训练截止日期。未指定 --model 时,since-cutoff 0.3.0 及更高版本会使用你的编码 Agent 所配置的模型,并在模型行中注明来源(“model from .claude/settings.json”):
SINCE_CUTOFF_MODEL(完整规格,例如openai:gpt-5.4)始终优先。- 在 Claude Code 内部(它会为所运行的命令设置
CLAUDECODE=1),只考虑 Claude Code 的设置:先是ANTHROPIC_MODEL,然后是项目的.claude/settings.local.json和.claude/settings.json,最后是~/.claude/settings.json。 - 其他情况下,最具体的设置优先:先是
ANTHROPIC_MODEL、GEMINI_MODEL或AIDER_MODEL,然后是项目设置(从最近的文件夹开始,从扫描文件夹到仓库根目录,不包括主目录),最后是用户设置。同一文件夹内的 Agent 按以下顺序处理:
| Agent | 项目设置 | 用户设置 |
|---|---|---|
| Claude Code | .claude/settings.local.json、.claude/settings.json | ~/.claude/settings.json |
| Codex | .codex/config.toml,使用其选定的配置文件 | $CODEX_HOME/config.toml 或 ~/.codex/config.toml |
| Gemini CLI | .gemini/settings.json | ~/.gemini/settings.json |
| OpenCode | opencode.json、opencode.jsonc | ~/.config/opencode/ |
| Aider | .aider.conf.yml,使用 Aider 的别名(4o、flash、r1 等) | ~/.aider.conf.yml |
未设置模型时,会使用 Claude Code 的默认模型,并注明这一点。程序只读取模型字段;如果无法识别模型名称,就会停止运行并指出相关设置项。Agent 通过其他服务(GitHub Copilot、Amazon Bedrock、Vertex AI)调用的模型会按其制造商命名,因此 run 会调用该制造商的 API(openai: 需要 OPENAI_API_KEY)。sync 会保留区块编写时所用的模型,无论你的 Agent 现在使用什么模型。
训练截止日期来自 models.dev(已附带快照以供离线使用)。since-cutoff models sonnet 会列出模型(--makers 会排除网关和转售商,--json 供脚本使用)。如果多个提供商都列出了某个模型,则使用所请求提供商的列表中的截止日期(opencode:glm-5.1);否则使用其制造商的列表中的截止日期;再否则使用网关或转售商所列出的最早日期,以确保更稳妥。模型行会注明非制造商的列表来源,而 since-cutoff models <id> 最后会显示 --model <id> 所采用的截止日期。--cutoff 2025-07 可覆盖日期;在不带 --model 的情况下使用 since-cutoff scan --cutoff 2025-07,则只针对该日期进行扫描。--cutoff-margin 30(默认值;也可通过 SINCE_CUTOFF_CUTOFF_MARGIN 设置)会以截止日期前 30 天或更早发布的最新版本为基准进行比较,因为模型对截止日期前几周的信息掌握得最少;--cutoff-margin 0 则会以截止日期当日或之前发布的最新版本为基准进行比较。模型行会显示这两个天数。scan 和 sync 只需要截止日期,因此也接受不带提供商的模型 ID(claude-haiku-4-5、sonnet),或 models.dev 列出的任意提供商模型(包括 google:gemini-2.5-pro、Amazon Bedrock 和 Vertex AI 的 ID);run 则需要使用上表中的提供商。
常见问题
**训练截止日期是否表示模型从该日期起就对某个库一无所知?**不是。
since-cutoff 仅使用截止日期来选择比较基准:对于每个依赖项,默认选择至少在该日期前 30 天发布的最新版本,并将其公共 API 与你固定的版本进行差异比较(--cutoff-margin 0 可恢复旧行为:选择截止日期当天或之前发布的最新版本)。模型对截止日期前几个月的内容掌握得不够好,也可能知道截止日期之后发布的版本,因此扫描可能会列出模型已经能处理的变更,也可能漏掉一些模型无法处理的变更。模型是否会编写旧版 API,是 run 衡量的内容。日期来自
models.dev;since-cutoff models <name> 会列出每个模型的截止日期和发布日期,两者相差数月(claude-sonnet-4-5:截止日期为 2025-07-31,发布日期为
2025-09-29)。更多信息请参阅
训练截止日期的用途。
会向哪里发送什么? scan、sync、status、MCP 服务、GitHub Action 和
pre-commit hooks 会读取 PyPI(元数据和 wheel)以及 models.dev,不会向任何模型发送内容。
run 会将提示词发送给你选择的模型提供商:包名、版本、变更 API 的公共签名和文档字符串、生成的任务,以及用于备注时模型自己的回答;绝不会发送你的源代码。哪些文件使用了变更的 API,会显示在终端和 .since-cutoff/ 中;只有在你主动发送时,相关内容才会进一步传出:例如通过 --markdown 生成 CI 摘要,或发送给调用 MCP 工具 project_changes 的 Agent。没有遥测。详情请参阅:
运行、发送和存储的内容
以及 PRIVACY.md。
**为什么有些备注会说“since-cutoff 未找到替代项”?**只有当库自身的弃用文本明确指出替代项,且该名称存在于你固定的版本中时,备注才会列出替代项(标签为 [diff + library])。否则,备注会说明变更,并指向软件包的变更日志,查看比较版本之后的变更(使用 PyPI 元数据中的 Changelog URL,否则使用其 GitHub releases 页面);如果 PyPI 中两者都没有,则说明未找到替代项:猜测一个替代项,再让 Agent 按此操作,比不提供替代项更糟。固定版本中看起来相似、但无法确认的名称会在终端显示为“未确认为替代项”;sync --suggestions 会将它们写入备注,并标记为 [not confirmed]。如果固定版本中的方法仍有 extra_body 参数(如 anthropic 的 Messages.create()),备注会指向该参数,因为即使请求参数已从签名中移除,API 仍可能接受它。
AGENTS.md 还是 CLAUDE.md?sync 会写入 AGENTS.md;如果只有 CLAUDE.md,则写入 CLAUDE.md;如果 CLAUDE.md 未导入 AGENTS.md,则会同时写入两者
(#13);如果其中一个文件已经有区块,则写入该文件(--target 可指定任意文件)。Claude Code 会读取 CLAUDE.md,且只有在 CLAUDE.md 通过一行 @AGENTS.md 导入 AGENTS.md 时才会读取 AGENTS.md;Codex、Cursor 和 Copilot 会读取 AGENTS.md;Gemini CLI 会读取 GEMINI.md,且只有当其 settings.json 中的 context.fileName 列出 AGENTS.md 时才会读取它("context": {"fileName": ["AGENTS.md", "GEMINI.md"]})。若要保留一份副本,请在 CLAUDE.md 中添加 @AGENTS.md,并运行 since-cutoff unapply --target CLAUDE.md;当两个文件都含有该区块时,scan、sync 和 status 会提示这一点;如果 AGENTS.md 中的区块未被导入,也会提示。从此以后,sync 会更新所有含有该区块的文件,而 status 会逐一报告。
这些说明会消耗多少 token? Agent 每轮都会读取整个区块。对于示例项目,约为 410 token,其中两条说明约占 140 token;其余部分是说明模型和截止日期、并解释标签含义的标题(和 since-cutoff 一样,按每 4 个字符计为 1 个 token)。sync 只会为代码使用的、已更改的 API 编写说明(--scope imported 会为每个导入的包增加最多 5 条;--header-only 不包含说明,只保留标题和每个包一行),模型编写的说明最多 60 个单词。在基准测试中,各区块为 285 到 505 token;包含说明的会话所用总 token 数是未包含说明的会话的 0.73 倍。run 会报告每个区块的 token 数。
可以离线使用吗? status 不会读取网络数据。scan 和 sync 需要从 PyPI 获取每个包的发行版本列表(缓存 12 小时;无法连接 PyPI 时会使用较旧的缓存副本,并在扫描结果中注明其日期),以及每个已更改包的源代码;源代码和差异都会缓存,因此后续运行只需几秒。训练截止日期来自 models.dev,并附带一份可离线使用的快照。
结果
目前已测量的结果及各自范围:
- 一个项目、一个模型、since-cutoff 0.1.0:下方的卡片,Claude Opus 4.6 在示例项目上的结果。后面的表格补充了 Claude Haiku 4.5 在同一项目上的结果。
- 基准测试:在 24 个带隐藏测试的 Python 任务上运行了 360 次无头 Claude Code 会话(
claude-opus-5-5),并在主运行前冻结了协议。在截止日期之后的 17 个任务中,使用 since-cutoff 0.4.1 说明的会话成本是单独使用 Claude Code 的 0.80 倍(95% CI 为 0.70-0.90),轮数为 0.83 倍,耗时为 0.87 倍。不声称通过率有所提升:单独使用 Claude Code 时,这些任务的通过率已达 94.1%,高于预先登记的 90% 上限。Context7 组在没有 API 密钥的情况下运行,从第 215 个会话开始(共 360 个)只收到“Monthly quota exceeded”,这不会影响说明与单独使用的比较。一个模型,一个 Agent: 结果, 任务、协议和会话记录。 - 独立开发者的报告将列于此处,并注明项目、模型和日期。欢迎在分享你的结果中发布。
在包含 9 个依赖项的示例项目
examples/agent-app
上测试的两个 Claude 模型,使用 since-cutoff 0.1.0 测量;任务和说明由 Claude Opus 4.6 编写:
| Claude Haiku 4.5 | Claude Opus 4.6 | |
|---|---|---|
| 训练截止日期 | 2025 年 2 月 | 2025 年 5 月 |
| 探测的 API 变更 | 20 | 16 |
| 过时 / 错误 / 已弃用 / 正确 | 5 / 1 / 2 / 12 | 7 / 0 / 3 / 6 |
| 使用过时 API 的库 | 探测的 5 个中有 3 个 | 探测的 4 个中有 2 个 |
| 编写的说明(含可通过类型检查的示例) | 8(7),约 391 token | 10(7),约 437 token |
| 留出任务正确率,不含说明 -> 含说明 | 14% -> 57%(14 组配对) | 5% -> 65%(20 组配对) |
| 添加说明后原本正确的 API | 6/6 仍然正确 | 6/6 仍然正确 |
| 留出任务是对每项失败变更所探测的任务进行释义改写;每项任务都会在不提供和提供备注的情况下各回答两次,并以相同方式评分。最后一行会重新检查模型已答对的 API,以发现备注是否会造成负面影响。 |
在这个样本中,能力更强的模型并不更安全:Opus 4.6 写出了在其知识截止时间后已移除的 API,包括搭配 client.completions 使用的 anthropic.HUMAN_PROMPT。两次运行中产生的过时代码,在对照版本中都有效,但会被针对指定版本的类型检查器拒绝:messages.create(temperature=...)(anthropic 1.8)、hf_hub_download(resume_download=...)、local_dir_use_symlinks=...、force_filename=... 和 proxies=...(huggingface-hub 2.0),以及 client.beta.vector_stores(openai 3.x)。运行时,anthropic 1.8.0 会因 temperature 引发 TypeError;huggingface-hub 2.0.0 仍接受这四个下载参数,但会忽略它们并发出警告。
Claude Haiku 4.5 运行中写下的备注(节选,原文照录;采用 since-cutoff 0.1.0 的格式):
## Library changes after the model's training cutoff
**anthropic 1.8.0**
- `temperature=...` was removed from `messages.create()` in anthropic 1.8.0. Omit the `temperature` parameter entirely; there is no replacement.
**huggingface-hub 2.0.0**
- `hf_hub_download(..., resume_download=True)`: The `resume_download` parameter was removed in huggingface-hub 2.0.0. Omit it; downloads resume automatically.
**openai 3.19.2**
- `client.beta.vector_stores` is removed in openai 3.19.2. Use `client.vector_stores` instead.
huggingface-hub 的备注不完全准确:resume_download 是在 1.0 版中从函数签名移除的,而不是 2.0.0;2.0.0 在运行时仍接受它,但会忽略它并发出警告
(源代码)。
不使用它仍然是正确建议。关于 0.4.0 及之后版本会根据 API 差异为相同参数生成备注的说明,见快速入门;运行时注意事项只出现在终端、报告和 JSON 中,不在代码块里。
Claude Opus 4.6 运行的终端摘要,记录于 0.1.0 版本。探测结果见上表。卡片上的差异计数来自 0.1.0(“标记了 725 项变更”);修正差异后,0.2.0 中的 scan 针对相同截止时间报告了 513 项破坏性变更和 48 项新增弃用。卡片上的“已修复变更”计数及其 95% CI 也沿用 0.1.0 的结果:该区间对应的是这个计数,而不是 5% -> 65% 的比率;在 0.2.0 及更早版本中,即使某个留出答案在不提供备注时就已正确,也会将相应变更计为已修复。
Claude Haiku 4.5 运行的相同摘要(同样使用 0.1.0;0.2.0 中的 scan 针对其截止时间报告了 491 项破坏性变更和 50 项新增弃用)
有关测量方法,以及这些数字能说明和不能说明的内容,请参阅更详细的说明。
研究:依赖项说明块节省的成本是否值得它占用的 Token?
一份技术报告(2026 年 10 月)对 1,607 次 Agent 会话中 sync 写入的说明块进行了测试,并将结果纳入盈亏平衡模型,所有长度均按模型调用次数计算。主要发现:
- 仅在一个模型上节省少量成本。 在主要研究中,Opus 5.5 使用该说明块的成本降至仅使用 Claude Code 时的 0.80 倍(95% CI:0.70-0.90);在八项新任务中,Token 数降至 0.79 倍(经 Holm 校正后未检测到显著差异,p = 0.094)。重复实验未达到预先承诺的判定标准;Sonnet 5.5、Haiku 5.5 和一个开放权重 Agent 均未显示出可检测的节省。所有任务均由 Opus 5.5 编写。
- 仅使用标题的效果也差不多。 没有任何预先承诺的测试能够区分完整说明块与仅列出变更包名称的标题,或与通用的“检查已安装签名”警告之间的效果。
- 成本低,但仅靠受影响的会话无法回本。 在作者的 Agent 上下文中,704 Token 的说明块约占上下文的 0.4%;假设每个受影响上下文都能固定节省一定成本,那么只有当需要使用变更 API 的上下文占所有上下文的 14-49% 时才能回本,而估计比例为 0.8-7%。
- 决定因素是对其他所有会话的影响。 对照任务显示节省了 6.6%,但无法确认这一结果与零有显著差异。默认关闭是一种规避损失的选择,并不能证明一定会造成损失;每组约 2,500-2,900 次会话的在线 A/B 测试可以给出结论。
在任何 Agent 中使用(MCP)
since-cutoff mcp 是一个 MCP 服务,可让编码 Agent 在编写代码前询问“自我的训练截止日期以来,这个库有哪些变化?”。它提供三个只读工具和两个提示词:
| 工具 | 解答的问题 |
|---|---|
api_changes(package, model, symbol=...) | 某个库在模型训练截止日期至少 30 天前发布的版本(默认)与最新版本(或指定版本)之间有哪些变化,优先列出重大破坏性变更 |
project_changes(project_dir, model) | 项目中每个依赖在其锁定版本上有哪些变化,从代码使用的已变更 API 开始:对每个 API,列出使用它的文件(最多 3 个)、说明、运行时注意事项,以及看起来相似但未经确认可作为替代项的名称;使用 header_only=true 时,仅返回 sync --header-only 写入的标头(代码导入的已变更包及其版本) |
model_cutoff(model) | 模型的训练截止日期,数据来自 models.dev |
| 提示词 | 要求 Agent 执行的操作 |
|---|---|
check_project(project_dir=".") | 使用自身的模型 ID 调用 project_changes,先列出旧格式用法,并建议使用 since-cutoff sync 更新 AGENTS.md 说明 |
before_upgrade(package, to_version="") | 读取锁定的包版本,针对计划升级到的版本调用 api_changes,并列出项目代码需要修改的内容 |
Agent 会传入自身的模型 ID,因此答案涵盖该模型训练截止日期之后的变化。这些工具以静态方式读取 PyPI 和包源代码:不调用模型、不需要 API 密钥,也不执行包代码。
Claude Code
claude mcp add --scope user since-cutoff -- uvx since-cutoff@latest mcp
Codex(~/.codex/config.toml)
[mcp_servers.since-cutoff]
command = "uvx"
args = ["since-cutoff@latest", "mcp"]
startup_timeout_sec = 60
tool_timeout_sec = 900
Cursor(~/.cursor/mcp.json)和 Claude Desktop(claude_desktop_config.json)
{
"mcpServers": {
"since-cutoff": { "command": "uvx", "args": ["since-cutoff@latest", "mcp"] }
}
}
VS Code(.vscode/mcp.json)
{
"servers": {
"since-cutoff": { "type": "stdio", "command": "uvx", "args": ["since-cutoff@latest", "mcp"] }
}
}
Gemini CLI
gemini mcp add --scope user since-cutoff uvx since-cutoff@latest mcp
# or as an extension, which starts the same server:
gemini extensions install https://github.com/MohammadHijjawi97/since-cutoff
@latest 会让 uvx 获取新版本,而不是重复使用它缓存的首个版本(插件自身的 .mcp.json 会固定到确切版本)。如果客户端找不到 uvx,请安装 uv 或提供完整路径(which uvx)。该服务已列入 MCP Registry,名称为 io.github.MohammadHijjawi97/since-cutoff。
首次在较大的项目上调用 project_changes 时,会下载每个已变更依赖的 wheel,可能需要几分钟(transformers 等超大型包耗时最长)。结果会缓存,因此后续调用只需几秒。要预热缓存,请在项目中运行一次 since-cutoff scan;它与服务共享缓存。默认工具超时较短的客户端可能需要延长超时时间,例如上面的 Codex 示例。
project_changes 会将答案控制在约 24,000 个字符以内:无法容纳的已变更依赖会各占一行;将它们传入 only 可查看其变更。
api_changes("huggingface-hub", model="claude-haiku-4-5", to_version="2.0.0") 的返回结果(实际输出,已删节):
# huggingface-hub 0.28.0 -> 2.0.0
- From 0.28.0 (2025-01-28): the newest release on or before 2025-01-29, 30 days before the cutoff 2025-02-28 (training cutoff of claude-haiku-4-5, from models.dev)
- To 2.0.0 (2026-09-24): as requested
- Warning: huggingface-hub 2.0.0: huggingface_hub.hf_file_system.HfFileSystemFile inherits from fsspec.spec.AbstractBufferedFile, which since-cutoff did not read; the attributes it sets are not compared
- 104 breaking changes, 0 new deprecations (dependencies switched 1, removed or moved 49, parameters removed 45, parameters now required 7, changed kind 1, now keyword-only or positional-only 1, +8 internal)
## Dependencies switched
- huggingface-hub requires `httpx2` instead of `requests`; its Requires-Dist lists `httpx2<3,>=2.0.0`, and `requests` only for its `gradio` extra; 6 places in its public API that named `requests` types name `httpx2` types: `get_session()` returns `httpx2.Client`, `HfHubHTTPError(response=...)` takes `httpx2.Response` and `HfFileSystemStreamFile.response` is `httpx2.Response`; `InferenceTimeoutError`, `HfHubHTTPError` and `TextGenerationError` derive from `httpx2.HTTPError` instead of `requests.HTTPError`
## Removed or moved
- `huggingface_hub.InferenceApi` was removed
...
## Parameters removed
- `huggingface_hub.InferenceClient.text_generation(stop_sequences=...)`: parameter `stop_sequences` was removed; the old docs said: Deprecated argument. Use `stop` instead; also changed under 1 other path, e.g. `huggingface_hub.AsyncInferenceClient.text_generation`
...
- `huggingface_hub.snapshot_download(proxies=...)`: parameter `proxies` was removed; 2.0.0's source still handles `proxies` (huggingface_hub/utils/_validators.py:178), so calls passing it may run with a warning; type checkers reject it
...
Not listed: 64 breaking changes, 0 new deprecations (removed or moved 34, parameters removed 30). Narrow with symbol="..." or raise limit.
使用 symbol="hf_hub_download" 时,只会列出该函数的 8 项变更(resume_download=、force_filename=、local_dir_use_symlinks= 和 proxies=,包括函数本身及 HfApi 上的相关变更)。
symbol 也接受代码中的调用形式:client.messages.create 会找到 Messages.create 的变更。差异会读取函数签名:这 4 个参数在 huggingface-hub 1.0 中已从签名移除;答案还会说明,2.0.0 的源代码仍会处理这些参数,因此传入它们的调用可能会在警告下运行。
在 CI 中使用
如果命令输出或 MCP 客户端有问题,请参阅故障排除指南。
GitHub Action
每次拉取请求时扫描项目,并在作业页面添加摘要:代码使用的已变更 API、使用这些 API 的文件、变更内容、是否仍在使用旧形式,以及替代形式;展开后还会显示说明和各个依赖项的变更。设置 check-notes: true 后,如果 AGENTS.md 中的说明已过期,作业也会失败。和 scan 一样,它只读取 PyPI 和 models.dev:不会调用模型,也不需要 API 密钥。
# .github/workflows/since-cutoff.yml
name: since-cutoff
on:
pull_request:
paths: ["**/*.lock", "**/pylock*.toml", "**/requirements*.txt", "**/environment.y*ml", "**/pyproject.toml", "**/AGENTS.md", "**/CLAUDE.md"]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: MohammadHijjawi97/since-cutoff@v0
with:
model: anthropic:claude-sonnet-4-5 # the model your team codes with
check-notes: true # fail when AGENTS.md needs `since-cutoff sync`
不设置 paths 时,代码变更开始使用已变更 API,也会触发作业。
| 输入 | 默认值 | |
|---|---|---|
model | 必填 | 与 --model 一样,使用 provider:model;只使用其训练截止日期 |
working-directory | . | 项目目录 |
only、exclude | 以逗号分隔的 PyPI 名称 | |
cutoff | 覆盖训练截止日期(YYYY-MM 或 YYYY-MM-DD) | |
cutoff-margin | 从截止日期前这么多天发布的最新版本开始比较(since-cutoff 默认为 30 天;0 表示从截止日期本身开始比较) | |
fail-on-changes | false | 如果某个依赖项的 API 相较于用于比较的版本发生变更,则使此步骤失败(默认使用截止日期前 30 天发布的最新版本) |
check-notes | false | 还会运行 since-cutoff sync --check,该命令不会写入任何内容;如果 AGENTS.md / CLAUDE.md 中的说明已过期或区块被手动编辑,则使作业失败;说明会保留编写时使用的模型;对于尚无区块的项目,则使用 model(和 cutoff) |
step-summary | true | 将 Markdown 摘要添加到作业摘要中 |
cache | true | 在运行之间保留 PyPI 元数据、软件包源代码和 API 差异(即使 fail-on-changes 导致作业失败也会保留) |
args | 更多 since-cutoff scan 参数,例如 --all-deps --limit 20 | |
since-cutoff-version | 0.5.0 | 要运行的 since-cutoff 版本,或 latest |
args: --fail-on old-form --annotate github 仅当代码以旧形式使用已变更 API 时才会使作业失败,并为每个使用该 API 的文件添加注释:旧形式显示警告,其他情况显示提示。
输出:changed-packages(以逗号分隔)、changes(破坏性变更)、deprecations、markdown(摘要文件路径,例如可用于发布为拉取请求评论)、report(完整报告的路径),以及使用 check-notes 时的 notes(up-to-date、out-of-date、edited-by-hand 或 error)。同一变更可通过多个导入路径访问时,只计为一次。
pre-commit
# .pre-commit-config.yaml
repos:
- repo: https://github.com/MohammadHijjawi97/since-cutoff
rev: v0.5.0
hooks:
- id: since-cutoff-scan
args: [--model=anthropic:claude-sonnet-4-5] # add --fail-on=old-form to block the commit
- id: since-cutoff-sync # keeps the notes in AGENTS.md current; args: [--check] only checks
当锁文件、requirements 文件、conda environment.yml 或 pyproject.toml 发生变更时,since-cutoff-scan 会运行并打印扫描结果;在 args 中为它提供 --model。当上述文件之一、AGENTS.md 或 CLAUDE.md 发生变更时,since-cutoff-sync 会运行并更新说明;如果它修改了文件,此 hook 会失败,与其他会修改文件的 pre-commit hook 一样:添加该文件并再次提交。使用 args: [--check] 时,它不会修改任何内容,并会在说明过期时失败。它会保留说明编写时使用的模型;对于第一个区块,请提供模型:args: [--model=anthropic:claude-sonnet-4-5]。这两个 hook 都需要 PyPI,因此在 pre-commit.ci 上跳过它们(ci: {skip: [since-cutoff-scan, since-cutoff-sync]})。since-cutoff-status 不需要网络:如果说明与锁文件不匹配,它会失败。
其他 CI
配置 CI 时,请参阅环境、文件和退出码参考。
# Markdown summary for any CI; exit code 3 if your code uses a changed API in the old form
since-cutoff scan --model anthropic:claude-sonnet-4-5 --markdown summary.md --fail-on old-form
# exit code 3 if the notes in AGENTS.md are out of date, 4 if the block was edited by hand
since-cutoff sync --check
# measure the model as well (needs its API key, or the claude CLI)
since-cutoff run --quick --fail-on-stale --json > since-cutoff.json
--markdown - 会将摘要打印到 stdout,并像 --json 一样,仅将进度和报告路径输出到
stderr。在文件、管道或 CI 日志中不会显示实时进度条,输出宽度为 160 列(COLUMNS 可设置其他宽度)。退出码:
0正常;1错误(例如,run无法探测任何 API 变更或为任何模型答案评分,或者sync无法检查其说明所涉及的包);2用法错误;3检查失败:scan --fail-on changes、used或old-form(依赖项的 API 自其对比基准版本以来发生了变化;默认基准为截止日期前 30 天发布的最新版本;你的代码使用了已变更的 API,或以旧形式使用了 API;--fail-on-changes等同于--fail-on changes;使用这些选项时,如果当前运行的 Python 无法解析某个依赖项的源代码,该依赖项也会触发失败),run --fail-on-stale(发现过时的 API 用法),或者说明已过期,且使用了sync --check或status(以及未写入说明时的sync);4sync:区块经手动编辑,未使用--force时不会写入任何内容;141输出被提前关闭(例如,管道传入head时)。
工作原理
scan 和 sync 无需模型:它们会对比公开 API,找出你的代码中使用了哪些已更改的 API,并根据差异说明每项更改,且标明检查过的内容
(详情)。
run 分为三个阶段。第一阶段是扫描,无需模型;另外两个阶段中,类型检查器会为每个答案评分,并检查模型撰写的每条说明:
| 结果 | 含义 |
|---|---|
| stale | 代码在对比版本(默认是模型截止日期前至少 30 天发布的最新版本)中有效,在你的版本中无效,且错误涉及已更改的 API |
| wrong | 在你的版本中无效,但无法用某项更改解释(虚构或误用 API) |
| deprecated | 有效,但使用了你的版本中标记为 @deprecated 的 API |
| correct | 在你的版本中有效,且确实使用了已更改的 API |
| untouched / off-task / invalid / error | 不计入任何比率,并始终会报告 |
从 0.3.0 起,留出测试结果会给出任务级比率:不提供说明 -> 提供说明 (并列出背后的配对任务数和 API 更改数)、两者之差及其 95% bootstrap 区间(按 API 更改重采样)、说明修复的问题(未提供说明时错误,提供后正确)及其 Wilson 95% 区间、说明导致的问题、修复与导致问题数量的精确符号检验结果,以及按原因分类的不计入留出配对项。回归检查会报告之前正确的 API 中,有多少在提供说明后仍然正确。
run --compare template,signatures(0.3.0 及更高版本)还会使用无需模型的基线说明来回答留出任务并进行回归检查:template 会根据 API 差异,用一句话说明每项导致错误的更改;signatures 会提供每个已更改 API 的新签名和文档字符串首段,或其库指定的替代项。所有区块都针对同一组配对项、使用相同的无说明答案进行评分,并显示各区块的 token 数量,因此一次运行即可看出 since-cutoff 自身的说明带来了哪些额外效果。
所有评分均由类型检查器根据确切的软件包版本完成,每个版本都在隔离环境中运行,并使用该软件包自身的运行时依赖项。没有 LLM 会充当评判者,且每个数字都可追溯到 results.json。详情:docs/how-it-works.md。
“verified”的含义
since-cutoff 不会单独使用“verified”一词。每条说明都会附带一个标签,指出检查过的内容,不会声称其他内容:
| tag | 检查内容 | 未检查内容 |
|---|---|---|
[diff] | 这是对两个版本的公共 API 进行静态比较(griffe)后发现的变更:默认情况下,一个是模型训练截止日期前至少 30 天发布的最新版本,另一个是项目锁定的版本。系统读取源代码,不会导入代码。仅有 [diff] 时,不会指出替代项:说明会引用库自身的弃用文本(“resume_download 没有替代项”),或指向软件包的变更日志(PyPI 元数据中的 Changelog URL,否则为其 GitHub releases 页面),以了解相较于作为比较基准的版本所做的变更;如果 PyPI 两者都未列出,则说明自截止日期起,在这些文本中未找到替代项。 | 行为,以及调用是否仍能运行:锁定的版本可能仍会接受已移除的参数并发出警告,例如 huggingface-hub 2.0.0 对 resume_download 的处理方式。若锁定版本的源代码仍会处理该参数,终端、report.md、MCP 工具和 JSON 会添加一行“Runtime:”,而区块中不会添加,因为建议相同。模型是否会给出错误信息。 |
[diff + library] | 与 [diff] 相同;此外,库自身的弃用文本(旧版本中的文档字符串、参数的文档字符串条目、@deprecated 消息或 warnings.warn 文本;对于弃用,则也包括锁定版本中的文本)指出了替代项(“改用 stop”),且该名称存在于锁定版本中。若文本只是将某名称作为建议提及,则会在 [diff] 下引用,而不会将其视为替代项。 | 替代项的行为是否相同。 |
[diff + move checked] | 与 [diff] 相同;此外,按可计数的标准,新路径上的对象与原对象相同:类或模块保留了旧对象至少一半的公共名称,函数保留其参数,值则保持相同。 | 行为。 |
[diff + metadata] | 较旧版本的 Requires-Dist(其 wheel 的 METADATA)列出了一个锁定版本未列出的库;而公共 API 中引用该库类型的位置(参数、返回类型、属性、基类、重新导出)改为引用锁定版本所需的另一个库的类型,或其附带的旧库副本的类型,且不再保留旧库:openai 3.x、anthropic 1.8、huggingface-hub 2.0 和 mcp 2.2 接受的是 httpx2 对象,而非原先的 httpx 对象。仅有 [metadata] 时,表示该项目代码导入的软件包是截止日期之后(或截止日期前的缓冲期内)首次发布的,或者其比较版本只是空占位版本(zensical 0.0.0):由于没有可供比较 API 的版本,说明会提供该软件包的首次发布日期(或占位版本号)、PyPI 简介和变更日志 URL。 | 行为:锁定版本是否仍接受旧库的对象(根据其源代码,openai 3 会转换其中一些对象,anthropic 1.8 会抛出 TypeError)。终端、report.md、MCP 工具和 JSON 会添加一行“Installed:”(说明项目是否仍安装了旧库,包括其虚拟环境),以及一行“Runtime:”,指出锁定版本的源代码中仍引用旧库的位置。 |
[diff; probable rename] | 同一位置上的参数具有相同注解,但名称已更改,且锁定版本的文档字符串中没有版本说明(.. versionadded::、.. versionchanged::)指出其中一个参数是新增的,或另一个参数已移除。这只是一个猜测,并会明确标注。 | 这是否确实是同一个参数。 |
[type-checked] | 由模型在 since-cutoff run 期间撰写,并因其示例通过了下方的类型检查而保留。 | 行为;该条目的说明是否在所示名称之外仍然成立。 |
[not confirmed] | 仅用于 sync --suggestions:锁定版本中看起来与已移除内容相似的名称(此时该条目的标签为 [diff; not confirmed])。 | 其中任何名称是否能替代它。 |
标签可以组合:证据之间用 + 连接([diff + library]),猜测标记放在 ; 后面([diff; probable rename]、[diff; not confirmed])。
默认情况下,名称只是看起来相似的内容绝不会写入说明中。终端、
report.md 和 MCP 工具会将其显示为“未确认是替代项”;如果库明确说明没有替代项,
则完全不会显示这些名称;sync --suggestions 会添加这些名称,并标记为
[not confirmed]。
[type-checked] 背后的类型检查。 只有满足以下所有条件时,才会保留模型编写的说明(模型有两次尝试机会):
- 完整示例可以解析并导入该包;
- 对照锁定文件中确切版本的源码及该版本的运行时依赖,在其他内容为空的环境中检查示例时,basedpyright(标准模式,将弃用报告为错误,并移除
# type: ignore和# pyright:注释)不会报告归因于该包的错误,也不会报告该包的弃用项; - 项目符号中所有使用反引号括起来的库名称,都出现在示例中,或出现在该说明所针对的 API 差异条目中;
- 项目符号最多 60 个词。
因此,[type-checked] 表示示例使用的导入、名称、参数和实参数量在你的版本中确实存在,且没有标记为 @deprecated(PEP 702)。包以外的问题(标准库、其他库)不会阻止说明保留。这并不表示代码在运行时行为正确、替代项是维护者推荐的选项,也不表示该说明对模型有帮助。如果说明未通过检查,since-cutoff 会改为根据 API 差异编写说明,并加上相应标签。
**“已测量”**是另一回事:since-cutoff run 会使用和不使用这些说明来回答留出任务,并报告数量。除此之外,since-cutoff 不会判断说明是否有帮助。
在 run 中,答案满足以下条件即计为正确:导入该包、使用变更后的 API,且在你的版本中没有归因于该包的“知识”错误(未知名称、导入项或参数;缺少必需实参;实参数量错误)。纯粹的类型严格性问题不计入。不会执行任何答案。
AGENTS.md 中的代码块包含带标签的项目符号;对于每个包,还会列出说明适用的版本以及用于比较的发布版本。其余信息见 scan --json 和 results.json:
used_apis[](代码使用的每个变更 API、使用位置、变更内容、替代项及其来源,以及带有 tags、applies_to 和 checks 的说明);运行 run 后还会生成
notes_detail[](每条说明及其模型示例和留出测试的测量结果;保留 verified,其含义与 checks.example_type_checks 相同)。
它运行、发送和存储的内容
参考文档列出了读取或写入的环境变量和文件。
- 不会运行任何软件包代码或模型编写的代码。 软件包以静态方式读取(使用 griffe,关闭检查;仅提取
.py/.pyi文件,并检查路径和大小)。模型的回答只会在本地使用 basedpyright 进行类型检查。 - 会获取 PyPI 上的公开软件包元数据和 wheel,以及 models.dev 上的模型截止日期(内置快照可供离线使用)。发布列表缓存 12 小时;无法连接 PyPI 时,会使用较旧的缓存副本,并在扫描结果中注明其日期。提取前,会根据 PyPI 列出的 sha256 校验每个下载的 wheel 或 sdist。Git、路径、工作区和私有索引依赖绝不会按名称在公开 PyPI 上查询。
status不会获取任何内容。 - 仅在
run中发送提示词,且只发送给你选择的模型提供商:软件包名称、版本、已更改 API 的公开签名和文档字符串、生成的任务,以及对于备注而言的模型自身回答。绝不会发送你的源代码。scan、sync、status、MCP 服务、GitHub Action 和 pre-commit 钩子不会向任何模型发送内容。 - 显示代码使用已更改 API 的位置(目前仅显示文件名),包括在终端和
.since-cutoff/中,且不会发送到任何地方。你可以自行决定如何传递这些信息:--markdown和--annotate github会将其写入 CI 作业的摘要和注释;MCP 工具project_changes会将其提供给发出请求的 Agent,由该 Agent 将工具结果传递给其模型。 - 将结果存储在项目的
.since-cutoff/中(它会在 git 中忽略自身)以及本地缓存中:since-cutoff cache info可显示缓存位置和各类型的大小;since-cutoff cache clear会清除缓存;使用--sources、--diffs或--pypi时,则只清除相应类型(模型对run的回答需要付费,因此会保留)。提取的软件包源代码占缓存的大部分,限制为 2 GB(SINCE_CUTOFF_CACHE_MAX_MB;设为 0 表示不限制):提取新版本后,会删除最近最少使用的版本,但不会删除正在运行的扫描所用版本;每次运行中首次删除时,都会输出一行说明删除了多少内容。sync(在你同意后,或使用--yes)和run --apply会在 AGENTS.md 或 CLAUDE.md 中写入一个带标记的区块,并保持文件其余部分逐字节不变;since-cutoff unapply会删除该区块。 - 不收集遥测数据,无需账户,也不收集个人数据。重复运行会使用缓存,因此免费且可复现(
--fresh会再次请求模型)。请参阅 PRIVACY.md。
局限性
- 目前仅支持 Python。接下来会支持 TypeScript(
.d.ts差异、tsc) (#1)。 - 类型检查器能识别错误的名称、错误的参数以及 PEP 702 弃用,但无法识别签名未变时背后的行为变化,也无法识别只在运行时发出警告的弃用。
scan还会列出使用库自身装饰器声明的弃用(名称中包含 "deprecat"),以及模块仍通过__getattr__发出警告来提供的已移除名称,但run不会探测这些情况。 - 差异涵盖公共 API:会跳过
_private名称,以及包内附带的测试套件、基准测试和示例。 - 源代码由运行 since-cutoff 的 Python 解析,因此无法读取更新版本 Python 的语法:在 Python 3.10 上,无法检查 numpy 2.5(其存根中使用了 PEP 695
type语句)。扫描会说明这一点以及可用于检查的 Python 版本(“使用 Python 3.12 或更高版本运行 since-cutoff 以进行检查”),而--fail-on会因此失败。请使用项目所用的 Python 版本或更新版本运行 since-cutoff。 - “你的代码使用了”是静态匹配,按文件进行:包括导入(含重新导出的名称)、调用、属性读取和关键字参数;方法调用的接收对象类型根据包自身的注解判定(
df = pd.read_csv(...)中的df是DataFrame;类会连同其基类一起计算,范围涵盖你固定版本的包)。它不会跟踪getattr等动态访问,目前也只会列出文件名,不会列出行号 (#8)。如果某个值的类无法从代码中确定,则对其进行的方法读取可能只按名称匹配;条件是该名称属于某个已更改的 API,且不是常见名称,也不是内置类型或标准库类型的成员。除非提供--include-name-matches(通过 MCP 使用include_name_matches),否则所有报告、说明、--fail-on和--annotate都会隐藏这类[name match],因为这类情况大多是其他库中名称相同的方法。scan、sync和project_changes会说明隐藏了多少处使用情况(结果中的name_matches_hidden)。目前,如果某个参数变为必需参数、仅限关键字参数或仅限位置参数,相关用法始终算作“使用了此 API”,而不算“旧形式”。 - 只有当库自身的弃用文本明确写出替代方案时,说明才会列出该方案。仅在迁移指南中提供的建议(anthropic 的
MIGRATION.md
建议对仍接受
temperature的旧版模型使用extra_body)不会出现在说明中。 - 探测只覆盖排名靠前的部分破坏性变更(优先探测你的代码已使用的符号),而非全部变更。
- “比较版本”是截止日期之前至少 30 天发布的最新版本(
--cutoff-margin;0表示从截止日期本身开始比较)。模型对截止日期前几周的情况了解得较少,因此实际过时情况可能更早出现;该间隔用于估算可能提前的时间。 - 留出任务是对同一变更的改述:它们只表明某条说明能修复该项变更,并不表明模型整体能力有所提升。
训练截止日期的用途
截止日期用于选定一个比较时间点,并不表示模型记住了什么。
since-cutoff 从 models.dev 获取日期(或使用 --cutoff);如果指定月份,则取该月最后一天
(2025-07 即 2025 年 7 月 31 日)。它会将该日期往前推一个边界余量(--cutoff-margin,默认
30 天),然后针对每个依赖项,选取截至目标日期已上传的最新正式且未撤回的版本(只有当该软件包在此之前没有正式版本时才选预发布版本,
绝不选开发版本),并将该版本的公开 API 与你锁定的版本进行差异比较。
差异列表列出候选项:模型的训练数据可能未包含的 API 变更。
比较日期决定了三件事:
- 哪些软件包会进行差异比较:锁定版本不晚于该版本的软件包无需比较,而在该日期之后首次发布的软件包会标记为新增;
- 在
run中,该版本自身依赖项所使用的版本(在旧版本端进行类型检查时,取该日期各项要求允许的最新版本); run中每个探测的“旧”版本端。因此,如果某个答案在旧版本端有效、在你的版本中无效,且报错涉及已变更的 API,则该答案会被标记为“过时”,而不是“错误”。
模型可能了解其声明截止日期之后发布的版本,也可能不了解截止日期前不久发布的版本,因此扫描可能会列出模型已经能正确处理的变更,也可能漏掉模型无法处理的变更。边界余量用于应对后一种情况;--cutoff-margin 0 表示从截止日期本身开始比较。模型实际上是否会写出旧版 API,只有 run 才会显示:它会在不提供任何工具、只告知项目锁定的版本时询问模型。
比较方式
since-cutoff 针对单个项目回答一个问题:你锁定的版本中,哪些公开 API 自模型训练截止日期所指向的版本发布以来发生了变更?并优先列出代码实际使用的 API。since-cutoff run 还会提出两个可选问题:该模型是否真的会答错,以及一条简短说明能否纠正它?下方的大多数工具回答的是另一个问题(“该库的文档现在怎么说?”),可与 since-cutoff 配合使用。
| 工具 | 功能 | since-cutoff 与之的关系 |
|---|---|---|
Context7(MCP 服务和 ctx7 CLI) | Agent 调用 resolve-library-id 和 query-docs,在工作时将文档片段提取到上下文中。如果库的维护者添加了特定版本(git 标签或分支,最多 20 个),它会提供该版本(/org/project/version);否则会提供已索引的分支。无需 API 密钥即可使用,但匿名速率限制较低。 | 互为补充。Context7 提供文档;它不会读取你锁定的版本,也不会检查 Agent 编写的代码。since-cutoff 会列出你固定的 API 中哪些在模型知识截止日期后发生了变化,并优先列出代码实际使用的 API,让你知道哪些地方需要查阅或添加备注。使用 Context7 时,请注明你固定的版本。 |
| 其他文档服务:Ref、docs-mcp-server | 为 Agent 提供文档搜索,可在回答时查询;docs-mcp-server 可以在本地索引文档 | 与 Context7 相同。 |
| library-skills | FastAPI 和 Streamlit 等库会在其软件包中附带 Agent 技能;uvx library-skills 会将你已安装版本的技能链接到 .agents/skills 或 .claude/skills,因此技能会随库更新 | 由维护者编写,并与已安装的版本保持同步:如果库附带了技能,就使用它。since-cutoff 覆盖未附带指导信息的软件包,并且只说明 API 表面的变化。 |
| 供应商技能插件,例如 pydantic/skills | 为 Pydantic、Pydantic AI 和 Logfire 提供 Claude Code、Codex 和 Cursor 插件及 SKILL.md 文件,从代码仓库安装 | 提供如何妥善使用库的维护者指导;随插件仓库发布,而非随你固定的版本发布。since-cutoff 的备注则针对你的锁定文件编写。 |
代码迁移工具:ast-grep 规则、OpenAI 的 openai migrate(Grit) | 使用手写的语法规则重写现有代码;ast-grep 的目录中有一项 OpenAI SDK 迁移(将 openai.Completion.create(...) 改为 client.completions.create(...)) | 对于迁移现有代码,代码迁移工具是合适的工具。since-cutoff 关注助手接下来编写的代码:它根据 API 差异而非他人编写的规则查找变化,并且只提供建议,不会重写任何内容。 |
| 依赖项机器人:Renovate、Dependabot | 创建用于更新固定版本的拉取请求 | GitHub Action 可以在这些拉取请求上运行,列出代码使用的已变更 API 及使用这些 API 的文件;使用 check-notes 时,还可以在备注同步完成前使检查失败。 |
| 基准测试:GitChameleon 2.0、VersiCode、CodeUpdateArena、LibEvolutionEval | 使用基于真实版本变更构建的固定任务集,或合成任务集(CodeUpdateArena),衡量模型表现;GitChameleon 2.0 会运行单元测试 | 它们通常会比较模型,有些会通过运行测试来检查行为。since-cutoff 会静态检查单个项目固定的版本:类型检查器能查看名称、参数和弃用项,但看不到行为。 |
since-cutoff run 中的 --compare signatures 会向模型提供新版本的签名和文档字符串的第一段。它是文档查询的本地替代方案,并非 Context7。
有两个较小的工具也在解决同一个问题:cutoff
通过运行由模型编写的程序来探测你维护的库,检查其当前版本;postcut 则将 Ruby Gemfile.lock 转换为一份简要说明,列出自截止点以来的变更。since-cutoff 基于 griffe、
basedpyright、models.dev 和 rich 构建。
将 since-cutoff 与 Context7 配合使用
since-cutoff scan 会告诉你需要查阅哪些 API;Context7 可以提供文档。提问时请注明你固定的版本(例如“anthropic 1.8.0”)。只有当库的维护者添加了该版本时,Context7 才会匹配该版本:截至 2026-09-27,/openai/openai-python 提供 v1.68.0、v1_105_0、v2.8.1 和 v2.11.0,而 /anthropics/anthropic-sdk-python 未提供任何版本,因此你可能会获得默认分支的文档。
相关研究
- 代码补全中的弃用 API。 Wang 等人,《LLM Meet Library Evolution: Evaluating Deprecated API Usage in LLM-based Code Completion》(ICSE 2025;arXiv:2406.09834,最初标题为《How and Why LLMs Use Deprecated APIs in Code Completion? An Empirical Study》)。研究了 7 个模型、8 个 Python 库中 145 组从弃用 API 到替代 API 的映射,以及 28,125 个补全提示词。大多数补全没有使用这两个 API 中的任何一个。在使用了其中一个(论文所称的“合理”补全)的结果中,整个数据集有 25-38% 使用了弃用 API:当提示词来自使用弃用 API 的代码时,这一比例为 70-90%;当提示词来自最新代码时,则为 9-18%。研究测试了两种基线修复方法,适用于模型使用了弃用 API 的最新代码提示词。ReplaceAPI 会在解码过程中将弃用 API 的词元替换为其替代 API,并让模型完成该行:在 6 个开源模型中,有 85.2-99.6% 的情况使用了替代 API(此方法需要控制解码,因此不适用于 GPT-3.5)。InsertPrompt 会添加注释
# {dep} is deprecated, use {rep} instead and revise the return value and arguments.并重新生成:不同模型的使用率为 25.7-97.2%,作者认为其效果和准确性仍不够理想。since-cutoff 的说明与 InsertPrompt 的做法相近,会将内容放入项目的指令文件;since-cutoff run会在留出的任务上评估这些说明,而不是假定它们有效。 - 仅在上下文中提供文档并不足够。 Ashik 等人,《When LLMs Lag Behind: Knowledge Conflicts from Evolving APIs in Code Generation》(arXiv:2604.09515,2026 年预印本)。研究涵盖 8 个 Python 库在 2023 年 12 月之后发布的版本中的 270 项真实 API 更新(45 项弃用或移除、128 项修改、97 项新增),以及来自 4 个系列、训练数据截止时间早于该日期的 11 个模型。仅提供更新说明时,模型在 74.64% 的回答中至少部分采用了更新(由 GPT-5 mini 评判);其中 42.55% 的回答可在引入该更新的库版本中运行。与此同时提供 API 文档后,采用率升至 92.87%,可运行率升至 66.36%。加入思维链和自我反思提示词后,可执行率又提高了 11.33%,这是相对增幅,而非百分点。在未采用更新的回答中,42.1% 完全忽略了更新,16.4% 使用了旧 API;在采用更新但在最佳设置下仍无法运行的回答中,与更新相关的最常见原因是参数错误(占这类失败的 26.6%)。这正是 since-cutoff 要根据你使用的确切版本检查代码的原因,也是
since-cutoff run会用这些说明重新测试模型,而不是假定模型会遵循说明的原因。 - 基准测试。 GitChameleon 2.0:328 个 Python 补全问题,每个问题都对应特定的库版本,并通过可执行单元测试进行检查;企业模型的基线成绩为 48-51%,检索文档可提升约 10 个百分点(GPT-4.1:从 48.5% 升至 58.5%),自我调试可提升约 10-20 个百分点。VersiCode:涵盖 300 多个 Python 库和 2,000 多个版本、跨越 9 年的特定版本代码补全和版本感知代码迁移。CodeUpdateArena:针对 7 个 Python 包中的 54 个函数进行知识编辑,包含合成更新、由 GPT-4 生成的更新以及 670 个程序合成示例;在提示词前附上更新文档,并不能让开源模型(DeepSeek、CodeLlama)使用这些更新。LibEvolutionEval(NAACL 2025):涵盖 8 个库的特定版本内联补全;检索特定版本的文档并提供提示词有助于提升效果。
这些研究会在固定任务集上评测许多模型;GitChameleon 2.0 和 Ashik 等人会运行生成的代码。since-cutoff 的范围更窄:针对一个项目,它会列出自对比版本以来的变更,优先列出你的代码使用到的变更,而
run会静态检查一个模型的回答。它无法发现签名未变但行为发生变化的情况,而运行代码的测试可以。
贡献
since-cutoff 还很新。目前最有帮助的方式是:
- 在你的项目上运行它,并在分享你的结果中发布发现的问题,包括误报。
- 认领一个适合新手的问题:规模较小、独立的任务,例如支持另一种锁文件格式或添加一个提供商预设。
- 更大的工作项会标记为需要帮助,例如 TypeScript 支持。
- 在问题列表中报告错误或异常结果。
CONTRIBUTING.md 介绍了代码布局和检查项;离线测试套件会使用一个玩具库和一个脚本化模型运行完整流程,因此无需 API 密钥。安全问题:SECURITY.md。
引用
如果你在研究中使用 since-cutoff,请引用它(参见 CITATION.cff)。
许可证
MIT © Mohammad Hijjawi
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。