项目介绍
MCP 服务 · 2026-11-09 更新 · 本站尚未实测
编码助手的训练数据往往早于项目依赖的最新版本,因此它可能写出当前固定版本已不再接受的调用。since-cutoff 针对 Python 项目解决这一问题:它读取锁文件,取每个依赖在模型训练截止日期之前至少 30 天(默认余量)发布的最新版本,把该版本的公开 API 与项目固定的版本做静态比对,并显示代码中哪些位置使用了已变更的 API。
工具的核心不调用模型。scan、sync、status、MCP 服务器、GitHub Action 与 pre-commit 钩子都不需要 API Key。只有可选的 run 会把提示发送给模型提供方,使用你的 API 额度或 Claude Code 用量。README 标注当前状态为 beta。
核心能力
点击能力名称查看输入、输出和原文依据。
- scan:读取
uv.lock、poetry.lock、pdm.lock、pylock.toml、Pipfile.lock、requirements*.txt、pyproject.toml、Pipfile、condaenvironment.yml的pip:段或.venv(不支持setup.py、setup.cfg),静态比对各依赖公开 API,列出代码用到的变更 API 及位置,并为每项生成说明。 - sync:把说明写入 AGENTS.md 或 CLAUDE.md 中带标记的区块,先展示 diff 再询问是否写入;锁文件或代码变化后再次运行即可保持同步,并通过
--check在 CI 与 pre-commit 中判断是否落后。 - status:离线比较提示块与锁文件,指出是否过期。
- MCP 服务器:让代理在写代码前查询某个库发生了哪些变更。
- run(可选):向模型提出需要使用变更 API 的短任务,用类型检查器对两个版本分别打分,生成说明并测试说明是否有效;使用基于 basedpyright 的额外依赖。
README 明确指出它不运行包代码、不重写你的代码、看不到签名不变的行为变化、只支持 Python(TypeScript 列在 issue #1)、定位到文件而非行号。
实际应用场景
适用场景
适合使用编码代理编写 Python、且固定了在模型训练截止后发布新版本的依赖的开发者。典型用法:升级依赖前先 scan 查看代码是否触及变更 API;把说明 sync 进 AGENTS.md/CLAUDE.md,减少助手生成旧写法;在 CI 中用 sync --check 或 status 提醒提示块过期;在 Claude Code 中通过插件让技能代跑这些命令。
安装与接入
要求 Python 3.10 或更高版本,README 声明支持 Linux、macOS 或 Windows。原文给出的安装方式:
pipx install since-cutoff # or: pip install since-cutoff
也可不安装直接运行,README 写作 uvx since-cutoff scan,并说明 uvx since-cutoff@latest scan 会获取新版本。run 需要额外安装 pip install "since-cutoff[run]" 或 pipx install "since-cutoff[run]",或用 uvx --with basedpyright since-cutoff run。
Claude Code 插件接入按原文:
/plugin marketplace add MohammadHijjawi97/since-cutoff
/plugin install since-cutoff@since-cutoff
其他读取 SKILL.md 的代理可用原文命令 npx skills add MohammadHijjawi97/since-cutoff。仓库 .mcp.json 给出的 MCP 启动配置为 uvx 加参数 since-cutoff==0.5.0、mcp,传输方式为 stdio。
使用条件与限制
- 是否需要部署
- 需自行准备运行环境,具体要求见正文
- 是否需要注册
- 账号和认证要求见正文;未明确说明的项目仍待核验
- 是否需要 API Key
- 待核验
- 费用与额度
- 费用边界见正文,项目许可不代表模型或托管服务免费
- 所需权限
- 待核验
- 许可证与服务条款
- 待核验
权限费用与限制
scan、sync、status、MCP 与钩子不调用模型、无需 API Key;run 会调用模型,消耗 API 额度或 Claude Code 用量,耗时可能 5 至 20 分钟。运行 run 时需按所选提供方准备对应密钥,例如 ANTHROPIC_API_KEY、OPENAI_API_KEY、OPENROUTER_API_KEY、DEEPSEEK_API_KEY,或本地 Ollama;也可使用 openai-compatible 加 --base-url。scan 与 sync 需要访问 PyPI(sync 在 PyPI 不可达时会退出且不写入),status 不读网络。训练截止日期来自 models.dev,离线使用内置快照。默认模型可能来自 Claude Code、Codex、Gemini CLI、OpenCode 或 Aider 的设置;未命名模型时使用 Claude Code 默认模型。各模型与代理组合的实际可用性、地区访问与国内网络兼容性待核验。
国内使用情况
网友核验 用户经验分享
与站长实测独立,不代表官方认证或本站已实测。
最近核验:暂无有效核验。参与人数按当前项目有效身份去重;不同核验项分别统计,不合并成用户总结果。仅统计审核通过且未过期的记录。
分项统计、有效期与样本量
| 核验项目 | 有效人数 | 成功 / 部分 / 失败 / 无法判断 | 成功率 | 最近核验 / 有效期 |
|---|---|---|---|---|
| 官方网站访问 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 7 天 |
| GitHub 仓库访问 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 7 天 |
| 安装与运行 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
| 官方服务国内连接 / MCP 连接 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
| 国内网络情况 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 7 天 |
| 国内模型兼容性 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
| 自定义 API | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
| 海外服务依赖 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
| 海外账号要求 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
| 中文界面 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
| 客户端兼容性 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
| 账号及费用信息 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
成功率 = 成功 ÷(成功 + 部分成功 + 失败);无法判断不计入分母。至少 5 份可判断结果才显示成功率。账号、费用、海外依赖与语言等事实项,成功表示核查结论与资料吻合,具体观察另行统计;不表示免费或没有海外依赖。不同网络、客户端和版本的结果不能直接等同。
最近有效核验
暂无审核通过且未过期的网友核验。欢迎提交真实使用记录。
查看全部有效核验 →国内实测状态:国内可用性待核验。协议兼容和官方资料说明不等于国内网络可用。
- 官方网站访问
- 尚未实测 待核验
- GitHub 仓库访问
- 尚未实测 待核验
- 官方服务国内连接
- 尚未实测 待核验
- 国内网络情况
- 尚未实测 待核验
- 国内模型兼容性
- 尚未实测 待核验
- 自定义 API
- 尚未实测 待核验
- 海外服务依赖
- 尚未实测 待核验
- 海外账号要求
- 尚未实测 待核验
- 中文界面
- 尚未实测 待核验
- 国内最后核验时间
- 尚未实测
许可与国内使用
仓库 LICENSE 为 MIT 许可证,版权声明为 2026 Mohammad Hijjawi。MIT 只覆盖仓库代码本身;通过 API 使用第三方模型或 Claude Code 产生的费用与条款由对应提供方决定,不属于本仓库授权范围。工具依赖 PyPI、models.dev 及所选模型服务,国内网络下的访问稳定性与是否存在镜像待核验。项目以英文文档为主,同时提供简体中文 README。是否存在商业模块、托管服务或付费层级,资料未说明,待向官方核验;公开仓库不等于相关模型服务免费。
项目资料与来源
字段依据与待核验事项
access:来源确认 · pipx install since-cutoff # or: pip install since-cutoff
project_type:来源确认 · since-cutoff is a command-line tool and MCP server for Python projects.
原始 README(折叠技术参考)
<p align="center"><picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/MohammadHijjawi97/since-cutoff/main/docs/img/logo-dark.svg">
<img src="https://raw.githubusercontent.com/MohammadHijjawi97/since-cutoff/main/docs/img/logo.svg" width="72" height="72" alt="since-cutoff logo">
</picture></p>
<h1 align="center">since-cutoff</h1>
<!-- mcp-name: io.github.MohammadHijjawi97/since-cutoff -->
**Your coding assistant's training data stops before your dependencies' latest releases, so it
can write calls your pinned versions no longer accept. since-cutoff shows where your code uses an
API that changed after the model's training cutoff, and writes the short notes your assistant
needs to avoid the old form.**
since-cutoff is a command-line tool and MCP server for Python projects. `scan` reads your
lockfile, takes each dependency's latest release published at least 30 days before your coding
model's training cutoff (by default), compares that release's public API with the version you pin, statically, and shows which
of the changed APIs your code uses, where, with a note for each. `sync` writes the notes into
AGENTS.md or CLAUDE.md and keeps them in step with the lockfile; `status`, pre-commit hooks and a
GitHub Action tell you when they fall behind. `run`, optional, measures which of the changes your
model actually gets wrong and whether the notes help. Only `run` calls a model.
[](https://pypi.org/project/since-cutoff/)

[](https://github.com/MohammadHijjawi97/since-cutoff/actions/workflows/ci.yml)
[](https://github.com/MohammadHijjawi97/since-cutoff/blob/main/LICENSE)

[](https://glama.ai/mcp/servers/MohammadHijjawi97/since-cutoff)
**English** | [简体中文](https://github.com/MohammadHijjawi97/since-cutoff/blob/main/README.zh-CN.md) | [Español](https://github.com/MohammadHijjawi97/since-cutoff/blob/main/README.es.md) | [Français](https://github.com/MohammadHijjawi97/since-cutoff/blob/main/README.fr.md)
## Install
Python 3.10 or later, on Linux, macOS or Windows.
```bash
pipx install since-cutoff # or: pip install since-cutoff
```
Or run it without installing: `uvx since-cutoff scan` downloads the current release and runs it
(`uvx since-cutoff@latest scan` picks up new releases instead of the first one uv cached).
`run` type-checks the model's answers with
[basedpyright](https://github.com/DetachHead/basedpyright), an extra of 180 MB that the other
commands never use: `pip install "since-cutoff[run]"` (or `pipx install "since-cutoff[run]"`)
installs it, and `uvx --with basedpyright since-cutoff run` adds it to a run without installing;
without it, `run` stops at once and says so.
Run it from your project root. It reads `uv.lock`, `poetry.lock`, `pdm.lock`, `pylock.toml`,
`Pipfile.lock`, `requirements*.txt`, `pyproject.toml`, `Pipfile`, the `pip:` block of a conda
`environment.yml` or a `.venv` (not `setup.py` or `setup.cfg`). Without `--model` it uses the
model your coding agent is set up with, from the Claude Code, Codex, Gemini CLI, OpenCode or
Aider settings; for any other model, pass `--model` (see
[Choosing the model](https://github.com/MohammadHijjawi97/since-cutoff#choosing-the-model)).
`scan`, `sync` and `status` call no model and need no API key; `run` sends prompts to the model
provider and uses your API credits or Claude Code usage.
Inside a coding agent, a skill runs these commands for you: see
[In Claude Code](https://github.com/MohammadHijjawi97/since-cutoff#in-claude-code) and
[In other coding agents](https://github.com/MohammadHijjawi97/since-cutoff#in-other-coding-agents).
The [MCP server](https://github.com/MohammadHijjawi97/since-cutoff#use-it-from-any-agent-mcp)
gives any agent the same diff.
## Quick start
The [sample project](https://github.com/MohammadHijjawi97/since-cutoff/tree/main/examples/agent-app)
calls `client.messages.create` and pins anthropic 1.8.0. The latest anthropic release at Claude
Sonnet 4.5's training cutoff was 0.60.0, whose `create` still took `temperature`; 1.8.0 raises
`TypeError` for it. What `scan` prints, trimmed to the anthropic part (it compares from
0.56.0, the latest release 30 days before the cutoff: models know the weeks before their
cutoff least):
```console
$ 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.
```
The call in `app/main.py` passes none of these parameters, so it is marked "uses this API", not
"old form": it works today, but an assistant writing for 0.60.0 could add `temperature=0.2` when
it edits the call. The note is what `since-cutoff sync` writes into AGENTS.md so that it does
not; its tag, `[diff]`, says what the note rests on: a static comparison of the two releases'
public APIs. The parameters left the signature, which is not the same as the API dropping the
field, so where the pinned method has an `extra_body` argument the note says so instead of
telling the assistant to drop the field.
`since-cutoff sync` then writes the two notes. It prints the block as a diff and asks
`Write this to AGENTS.md? [y/N]` (`--yes` writes without asking; `--dry-run` only shows the
diff). For the sample project, the block ends with:
```markdown
**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]
<!-- since-cutoff:end -->
```
Above the bullets are the start marker, a meta line (the model, its cutoff, where the versions
come from, a hash of the dependencies and a hash of the block's own text, so that a hand edit
shows) and a header that names the model, the cutoff and the file the versions come from, says
what the tags mean and that no library code was run. The whole block is about 410 tokens. Text
outside the block keeps its bytes, and `since-cutoff unapply` removes the block. After an
upgrade, run `sync` again; `sync --check` in CI and `status` offline tell you when the notes
fall behind ([Keep the notes current](https://github.com/MohammadHijjawi97/since-cutoff#keep-the-notes-current-sync-and-status)).
**Try it on your project.** No API key, no model call. In the project root:
```bash
uvx since-cutoff scan
```
It detects your coding model (or pass `--model`), reads your lockfile, takes each dependency's
latest release published at least 30 days before the model's training cutoff (by default), and
compares that release's public API
with the version you pin, statically: no package code runs. The cutoff only chooses which changes
to look at; it says nothing about what the model memorised. Whether your model really gets them
wrong, and whether the notes help, is what
[`since-cutoff run`](https://github.com/MohammadHijjawi97/since-cutoff#measure-your-model)
measures; it calls your model and is optional.
## When to use it, and what it does not do
Use it when you write Python with a coding agent and pin dependencies that have released since
the model's training cutoff; for the Python AI stack, that is most of them
([The problem](https://github.com/MohammadHijjawi97/since-cutoff#the-problem)):
- `scan` for a look: the changed APIs your code uses, where, and a note for each;
- `sync` to put the notes into AGENTS.md or CLAUDE.md and keep them current, and `sync --check`,
`status`, the pre-commit hooks or the GitHub Action to be told when they fall behind;
- the MCP server to let an agent ask what changed in a library before it writes code;
- `run` to find out whether your model actually writes the old API, and whether the notes fix it.
It does not:
- call a model, except `run`: `scan`, `sync`, `status`, the MCP server, the Action and the hooks
need no API key;
- run package code or model-written code: packages are read statically, and answers are only
type-checked;
- see behaviour changes behind an unchanged signature, or deprecations that only warn at run time;
- name a replacement unless the library's own deprecation text states it;
- say what the model memorised: the cutoff picks the comparison release, and only `run` asks the
model;
- rewrite your code: for migrating code you already have, a codemod is the right tool
([How it compares](https://github.com/MohammadHijjawi97/since-cutoff#how-it-compares));
- cover other languages yet: Python only, TypeScript is
[#1](https://github.com/MohammadHijjawi97/since-cutoff/issues/1); and it names files, not
lines ([#8](https://github.com/MohammadHijjawi97/since-cutoff/issues/8)).
The full list is in [Limitations](https://github.com/MohammadHijjawi97/since-cutoff#limitations).
## The problem
Every model has a training cutoff; your lockfile keeps moving. When a library changes its public
API after the cutoff, a model whose training data predates the change can keep writing the old
calls. Some of that code fails at import or call time. Some still runs, because the old form is
only deprecated, or still accepted with a warning.
A few of the changes `since-cutoff scan` finds for Claude Sonnet 4.5 (training cutoff July
2025) in the [sample project](https://github.com/MohammadHijjawi97/since-cutoff/tree/main/examples/agent-app),
which pins six of its nine dependencies to current releases (for the other three, which are
unpinned, the tool uses the latest release):
| library | compared from | pinned | what changed |
|---|---|---|---|
| anthropic | 0.56.0 | 1.8.0 | `messages.create(temperature=..., top_p=..., top_k=...)` is no longer accepted |
| huggingface-hub | 0.33.1 | 2.0.0 | `hf_hub_download(resume_download=..., force_filename=..., local_dir_use_symlinks=...)` left the signature in 1.0 (2.0.0 still accepts them at run time, ignores them and warns) |
| langchain-core | 0.3.67 | 1.6.5 | `retriever.get_relevant_documents()` and `llm.predict()` removed |
| openai | 1.93.0 | 3.19.2 | 24 breaking changes, 6 new deprecations (+5 internal) |
Each library is compared from its latest release published at least 30 days before the cutoff
(the scan's default margin), so a change can be from the last weeks before the cutoff. In that
project, 7 of 9 dependencies changed their public API since that release. The static diff flags
331 breaking changes and 24 new deprecations in public APIs, and 139 changes to internal ones;
the project's code uses 2 of the changed APIs.
It is not one model or one vendor. Across 36 widely used Python AI libraries and 21 models from
OpenAI, Anthropic, Google, xAI, DeepSeek, Qwen, Moonshot and Mistral, even the newest model
tested (Claude Opus 5.5, June 2026 cutoff) predates a public API break in 20 of the 36
([full results](https://mohammadhijjawi97.github.io/since-cutoff/ai-stack.html)):
<p align="center"><picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/MohammadHijjawi97/since-cutoff/main/docs/img/ai-stack-dark.svg">
<img src="https://raw.githubusercontent.com/MohammadHijjawi97/since-cutoff/main/docs/img/ai-stack.svg" width="860" alt="Bar chart: for each of 21 models from 8 vendors, how many of 36 Python AI libraries broke their public API and how many are on a new major version since the model's training cutoff. From 21 of 36 for GPT-4o (13 of the libraries did not exist yet) to 33 of 36 for models with early-2025 cutoffs, and 20 of 36 for Claude Opus 5.5 (June 2026).">
</picture></p>
since-cutoff does three things about it:
1. **`scan`** finds, for each dependency, the newest release published at least 30 days before
the model's training cutoff (by default),
diffs its public API against the version you pin, and shows which of the changed APIs your
code uses, where, and a note for each. No model calls, no API key.
2. **`sync`** writes those notes into a marked block in AGENTS.md (or CLAUDE.md) after showing
you the diff, and keeps them in step with your lockfile; `sync --check` and `status` tell CI,
pre-commit and your agent when they fall behind. Each note is stated from the API diff and
tagged with what was checked. No model calls.
3. **`run`**, optional, asks the model short coding tasks that need the changed APIs, with no
tools and no docs, and scores each answer with a type checker against *both* versions:
stale, wrong, deprecated or correct. No LLM judges anything. For each failure it writes a
note, keeps a model-written note only if its example type-checks against your version, and
re-tests the model on held-out tasks without and with the notes.
The same diff is available to agents through an [MCP server](https://github.com/MohammadHijjawi97/since-cutoff#use-it-from-any-agent-mcp)
and to CI through a [GitHub Action and pre-commit hooks](https://github.com/MohammadHijjawi97/since-cutoff#use-in-ci).
The explainer below covers this section in 2½ minutes.
<!-- The video is attached to the v0.5.0 release only (release.yml uploads dist/* alone), so its URL names that release. -->
<p align="center"><a href="https://github.com/MohammadHijjawi97/since-cutoff/releases/download/v0.5.0/since-cutoff-explainer.mp4"><img src="https://raw.githubusercontent.com/MohammadHijjawi97/since-cutoff/main/docs/img/explainer-thumbnail.png" width="560" alt="Watch the 2½-minute explainer (video with voice-over)"></a><br><a href="https://github.com/MohammadHijjawi97/since-cutoff/releases/download/v0.5.0/since-cutoff-explainer.mp4">▶ Watch the 2½-minute explainer (video with voice-over)</a></p>
## Using it
```bash
# 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
```
What `scan` prints for the sample project, in full:
<p align="center"><img src="https://raw.githubusercontent.com/MohammadHijjawi97/since-cutoff/main/docs/img/scan.svg" width="100%" alt="since-cutoff scan --model anthropic:claude-sonnet-4-5 on the sample project. 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: hf_hub_download, used in app/main.py, no longer has force_filename, local_dir_use_symlinks, resume_download and proxies in its signature; its note is tagged [diff], and a Runtime line says that 2.0.0's source still handles them, so calls passing them may run with a warning. anthropic 0.56.0 -> 1.8.0: Messages.create, called in app/main.py, no longer accepts temperature, top_k and top_p; its note is tagged [diff]. 2 notes ready for AGENTS.md; what uses this API and [diff] mean; 348 more changes in 7 packages that the code does not use, and 139 changes to internal APIs."></p>
Both APIs are "uses this API"; a file that passed `resume_download=True` or `temperature=0.2`
would make them "old form". `scan -v` lists every file that uses an API (3 are shown), and
`scan --all` adds every other change to a public API, package by package (`--internal` adds
the changes to internal APIs, which every count gives apart as "+N internal"). `scan --json` and
`.since-cutoff/results.json` have the same in `used_apis` (each API, where it is used, its
changes and its note, with the note's tags, the versions it applies to and what was checked),
and `.since-cutoff/report.md` starts with "Used by your code".
### Keep the notes current: sync and status
`since-cutoff sync` (0.4.0 and later) writes the notes that `scan` shows into a marked block: in
AGENTS.md, or in CLAUDE.md when only that file exists, or in both when CLAUDE.md does not import
AGENTS.md, and where a block already is, into that one (`--target` names another file). It
prints a unified diff and asks
`Write this to AGENTS.md? [y/N]`; `--yes` writes without asking, and with no terminal to ask in
it writes nothing. Text outside the block keeps its bytes, CRLF line breaks included, and
`since-cutoff unapply` removes the block. The block for the sample project is in
[Quick start](https://github.com/MohammadHijjawi97/since-cutoff#quick-start). The Runtime caveat
stays out of the block: the advice ("do not pass them") is the same either way.
Run `sync` again after you change the lockfile or the code. It adds notes for APIs your code
starts using, checks a bumped package again, and drops a package's notes when it is no longer a
dependency, is no newer than the comparison release, or its changed APIs are no longer used,
and says why. It keeps the model and cutoff the block was written for (so that teammates whose
agents use other models do not rewrite it back and forth) unless you pass `--model` or
`--cutoff`; `--model a,b` uses the earliest of their cutoffs. When nothing changed it writes
nothing, and the file keeps its bytes and modification time.
| command | what it does | exit code |
|---|---|---|
| `since-cutoff sync` | shows the diff, asks, writes | 0 written or up to date; 3 not written (you said no, or there is no terminal to ask in) |
| `since-cutoff sync --yes` | writes without asking | 0 |
| `since-cutoff sync --dry-run` | shows the diff, writes nothing | 0 |
| `since-cutoff sync --check` | writes nothing (for CI and pre-commit) | 0 up to date; 3 out of date |
| `since-cutoff sync --json --yes` | writes without asking; prints JSON proposals and write results | same as `sync --yes` |
| `since-cutoff sync --json --check` | prints JSON proposals; writes nothing | 0 up to date; 3 out of date |
| `since-cutoff sync --json --dry-run` | prints JSON proposals; writes nothing | 0 |
| `since-cutoff status` | compares the block with the lockfile, offline | 0 current, or no block; 3 out of date; 1 broken markers |
When the block was edited by hand, `sync` and `sync --check` show the diff, write nothing and
exit with code 4; `sync --force` replaces it. When a package the notes are about cannot be
checked (PyPI unreachable, or sources the running Python cannot parse), `sync` writes nothing
and exits with code 1.
`sync --json` requires `--yes`, `--check` or `--dry-run`; without one it exits with code 2
without prompting. Stdout contains only JSON, and stays empty when sync fails (a usage error,
exit code 2, or an error such as PyPI being unreachable, exit code 1); progress, diffs and
errors go to stderr. The object contains `exit_code`, `tip` (what sync says about
AGENTS.md and CLAUDE.md, or null) and `targets`. Each target
reports `target`, `action`, `changed`, `edited`, `written`, `model`, `cutoff`, `scope`,
`notes`, `changes` (each package's `package`, `done` and `state`), `retest` and `diff_lines`.
`written` distinguishes an applied proposal from a preview or a refused hand edit. The usual
exit codes and `--force` behavior still apply.
`since-cutoff status` reads no network and no code: per package, the version the notes are for
and the version the lockfile has, whether other dependencies changed, and whether the model
your coding agent is set up with has an earlier training cutoff than the notes (with the
`sync --model` command for it). `status --json` is for scripts. `status --hook` prints one line
only when the notes are out of date and always exits with 0, for example:
```text
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.
```
More options:
- `sync --scope imported` also notes the changes most likely to matter in each changed package
your code imports (up to 5 APIs per package), for code that uses none of the changed APIs yet.
`scan` suggests it when that is the case.
- `sync --profile evaluated` is the configuration the benchmark measured: `--scope imported`
with up to 5 APIs per package (the same as giving both flags). The default scope, `used`,
notes only the changed APIs your code already calls, so it writes a block in fewer projects
and a shorter one, but the benchmark did not test it. The notes' wording has changed since the
release that was measured (0.4.1), and that release compared from the release at the cutoff:
add `--cutoff-margin 0` to compare from the same day.
- `sync --header-only` writes only the header: the packages whose API changed since the release
they are compared from, each with its pinned version and that release, and the instruction to
follow them, with no change lines (`--no-header-only` writes them again). It is the block of the
benchmark's header-only arm: shorter (about 250 tokens with one package, against 285 to 505
for the full blocks), and it tells the agent which packages to check, but not what changed
or what to use instead. Without `--scope`, a new header lists the packages your code imports.
- `sync --suggestions` adds the names in the pinned version that merely look similar to what was
removed, tagged `[not confirmed]`. The block records these choices (the scope, the APIs per
package, the header only, the suggestions), and later syncs keep them.
- Notes that `since-cutoff run --apply` wrote are tagged `[type-checked]`. `sync` keeps each one,
in place of the note from the diff for the same API, while its package keeps the same version
and your code still uses that API.
Claude Code reads CLAUDE.md, and reads AGENTS.md only when there is no CLAUDE.md or when
CLAUDE.md imports it with a line `@AGENTS.md`
([memory docs](https://code.claude.com/docs/en/memory)); Codex, Cursor and Copilot read AGENTS.md;
Gemini CLI reads GEMINI.md, and AGENTS.md only when `context.fileName` in its settings.json lists
it (`"context": {"fileName": ["AGENTS.md", "GEMINI.md"]}`; [GEMINI.md
docs](https://github.com/google-gemini/gemini-cli/blob/main/docs/cli/gemini-md.md)). With both
files and no block yet, the notes go to AGENTS.md alone when CLAUDE.md imports it, and to both
files when it does not ([#13](https://github.com/MohammadHijjawi97/since-cutoff/issues/13));
`scan`, `sync` and `status` then say how to keep one copy: add the line `@AGENTS.md` to CLAUDE.md
and run `since-cutoff unapply --target CLAUDE.md`. A block already in AGENTS.md alone stays there,
and they say so when CLAUDE.md does not import it. When both files have a block and CLAUDE.md then
imports AGENTS.md, they say CLAUDE.md holds a second copy, with the same `unapply` command. An
`@AGENTS.md` in a code span or a fenced block is not an import.
### Measure your model
`since-cutoff run` is the optional step that calls a model. It asks the model short tasks that
need the changed APIs, with no tools, no docs and none of your code; scores the answers with a
type checker against both versions; writes a note for each failure; and tests the notes on
held-out tasks ([How it works](https://github.com/MohammadHijjawi97/since-cutoff#how-it-works)).
It uses your API credits or Claude Code usage and can take 5-20 minutes.
```bash
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
```
Without `--apply` the notes are shown, not written. `run --apply` replaces the block with that
run's notes; a later `since-cutoff sync` adds the notes from the diff for the other changed APIs
your code uses, and keeps the run's `[type-checked]` notes while their package's version stays
the same.
### In Claude Code
```text
/plugin marketplace add MohammadHijjawi97/since-cutoff
/plugin install since-cutoff@since-cutoff
```
Then ask Claude to "check which of our dependencies you are out of date on", or run
`/since-cutoff:since-cutoff`. The skill runs `since-cutoff scan` and offers the notes: it runs
`since-cutoff sync --dry-run`, shows you the diff and writes only when you agree. When you ask
it to measure the model, the measuring is done by a fresh, tool-less copy of the model, so the
agent cannot grade itself. The plugin also starts the
[MCP server](https://github.com/MohammadHijjawi97/since-cutoff#use-it-from-any-agent-mcp), so
Claude can look up a library's changes before it writes code, and (0.4.0 and later) runs
`since-cutoff status --hook` when a session starts, which adds one line to the session when the
notes are out of date (the first time, uvx downloads since-cutoff).
### In other coding agents
```bash
npx skills add MohammadHijjawi97/since-cutoff
```
This installs the same skill through the open [skills](https://github.com/vercel-labs/skills)
CLI for Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and other agents that read
`SKILL.md`. since-cutoff reads the model from the Codex, Gemini CLI, OpenCode and Aider settings
too; for other agents, tell it which model to use, for example
`since-cutoff scan --model openai:gpt-5.4`.
Add the MCP server as shown below.
Prompts that work well:
- "Which APIs that our code uses changed after your training cutoff?" The agent runs
`since-cutoff scan` or calls the MCP tool `project_changes`.
- "Add notes about them to AGENTS.md." The agent runs `since-cutoff sync --dry-run`, shows you
the diff, and runs `since-cutoff sync --yes` when you agree.
- "Measure which of those changes you actually get wrong." The agent asks you first, then runs
`since-cutoff run --quick`.
- "Before you write the httpx code, check what changed in httpx since your cutoff." The agent
calls the MCP tool `api_changes`.
### Choosing the model
| `--model` | uses | needs |
|---|---|---|
| `claude-code` (default when no setting names a model) | your Claude Code login (subscription or key), current model | the `claude` CLI |
| `claude-code:sonnet`, `claude-code:claude-haiku-4-5` | a specific Claude model | the `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>` | local Ollama | Ollama running |
| `openai-compatible:<model>` | any OpenAI-compatible server | `--base-url`, optional `OPENAI_API_KEY` |
The "needs" column is for `run`, which calls the model; `scan` and `sync` use only its training
cutoff. Without `--model`, since-cutoff 0.3.0 and later use the model your coding agent is set up
with, and the model line says where it came from ("model from .claude/settings.json"):
1. `SINCE_CUTOFF_MODEL` (a full spec such as `openai:gpt-5.4`) always wins.
2. Inside Claude Code (which sets `CLAUDECODE=1` for the commands it runs), only Claude Code's
settings count: `ANTHROPIC_MODEL`, then the project's `.claude/settings.local.json` and
`.claude/settings.json`, then `~/.claude/settings.json`.
3. Elsewhere the most specific setting wins: first `ANTHROPIC_MODEL`, `GEMINI_MODEL` or
`AIDER_MODEL`, then the project settings, nearest folder first, from the scanned folder up to
the repository root (never the home folder), then the user settings. In one folder the agents
count in this order:
| agent | project settings | user settings |
|---|---|---|
| Claude Code | `.claude/settings.local.json`, `.claude/settings.json` | `~/.claude/settings.json` |
| Codex | `.codex/config.toml`, with its selected profile | `$CODEX_HOME/config.toml` or `~/.codex/config.toml` |
| Gemini CLI | `.gemini/settings.json` | `~/.gemini/settings.json` |
| OpenCode | `opencode.json`, `opencode.jsonc` | `~/.config/opencode/` |
| Aider | `.aider.conf.yml`, with Aider's aliases (`4o`, `flash`, `r1`, ...) | `~/.aider.conf.yml` |
When no setting names a model, it uses Claude Code's default model and says so. Only the model
fields are read, and a model name it cannot place stops the run with a message naming the
setting. A model that an agent reaches through another service (GitHub Copilot, Amazon Bedrock,
Vertex AI) is named after its maker, so `run` calls the maker's API (`openai:` needs
`OPENAI_API_KEY`). `sync` keeps the model a block was written for, whatever your agent uses now.
Training cutoffs come from [models.dev](https://models.dev) (a snapshot is bundled for offline
use). `since-cutoff models sonnet` lists them (`--makers` leaves out gateways and resellers,
`--json` is for scripts). A model that several providers list takes the cutoff of the
requested provider's listing (`opencode:glm-5.1`), else of its maker's, else the earliest
that a gateway or reseller lists: more notes, the safe side. The model line names a listing
that is not the maker's, and `since-cutoff models <id>` ends with the one `--model <id>`
takes. `--cutoff 2025-07` overrides the date, and `since-cutoff scan --cutoff 2025-07` without `--model` scans against that
date alone. `--cutoff-margin 30` (the default; `SINCE_CUTOFF_CUTOFF_MARGIN` sets it too) compares from the
latest release published 30 days or more before the cutoff, since models know the weeks before
their cutoff least; `--cutoff-margin 0` compares from the latest release at the cutoff itself.
The model line shows both days. `scan` and
`sync` need only the cutoff, so they also take a model id without a provider
(`claude-haiku-4-5`, `sonnet`) or with any provider models.dev lists (`google:gemini-2.5-pro`,
Amazon Bedrock and Vertex AI ids included); `run` needs a provider from the table above.
## FAQ
**Is the training cutoff the date from which the model knows nothing about a library?** No.
since-cutoff uses the cutoff only to choose a comparison point: for each dependency, by default
the latest release published at least 30 days before that date, whose public API it diffs against
the version you pin (`--cutoff-margin 0` restores the old behaviour: the newest release on or
before the cutoff itself). Models know the months before their cutoff poorly and may know a
release that came after it, so the scan can list changes the model already handles and miss some
it does not. Whether the model writes the old API is what `run` measures. The dates come from
[models.dev](https://models.dev); `since-cutoff models <name>` lists each model's cutoff next to
its release date, which are months apart (claude-sonnet-4-5: cutoff 2025-07-31, released
2025-09-29). More in
[What the training cutoff is used for](https://github.com/MohammadHijjawi97/since-cutoff#what-the-training-cutoff-is-used-for).
**What is sent where?** `scan`, `sync`, `status`, the MCP server, the GitHub Action and the
pre-commit hooks read PyPI (metadata and wheels) and models.dev, and send nothing to any model.
`run` sends prompts to the model provider you choose: package names, versions, the public
signatures and docstrings of the changed APIs, the generated tasks and, for notes, the model's own
answers; never your source code. Which files use a changed API is shown in the terminal and in
`.since-cutoff/`, and goes further only where you send it: a CI summary with `--markdown`, or the
agent that called the MCP tool `project_changes`. No telemetry. Details:
[What it runs, sends and stores](https://github.com/MohammadHijjawi97/since-cutoff#what-it-runs-sends-and-stores)
and [PRIVACY.md](https://github.com/MohammadHijjawi97/since-cutoff/blob/main/PRIVACY.md).
**Why do some notes say "since-cutoff found no replacement"?** A note names a replacement only
when the library's own deprecation text states one and that name exists in your pinned version
(tag `[diff + library]`). Otherwise the note states the change and points at the package's
changelog for the changes since the comparison release (the Changelog URL in its PyPI
metadata, else its GitHub releases page), or, when PyPI lists neither, says that no replacement was found: a guessed
replacement that an agent then follows is worse than none. Names in the pinned
version that merely look similar are shown in the terminal as "not confirmed as replacements";
`sync --suggestions` writes them into the notes, tagged `[not confirmed]`. Where the pinned method
still has an `extra_body` argument, as anthropic's `Messages.create()` does, the
note points to it, since a request parameter that left the signature may still be accepted by the
API.
**AGENTS.md or CLAUDE.md?** `sync` writes to AGENTS.md; to CLAUDE.md when only that file
exists; to both when CLAUDE.md does not import AGENTS.md
([#13](https://github.com/MohammadHijjawi97/since-cutoff/issues/13)); and into whichever of the two
already has a block (`--target` names any file). Claude Code reads CLAUDE.md, and AGENTS.md
only when CLAUDE.md imports it with a line `@AGENTS.md`; Codex, Cursor and Copilot read AGENTS.md;
Gemini CLI reads GEMINI.md, and AGENTS.md only when `context.fileName` in its settings.json lists
it (`"context": {"fileName": ["AGENTS.md", "GEMINI.md"]}`). To keep one copy, add `@AGENTS.md` to
CLAUDE.md and run `since-cutoff unapply --target CLAUDE.md`; `scan`, `sync` and `status` say so
while both files have the block, and say when a block in AGENTS.md alone is not imported. From
then on `sync` updates every file that has a block, and `status` reports each.
**How much do the notes cost in tokens?** The agent reads the block on every turn. For the
sample project it is about 410 tokens, of which the two notes are about 140; the rest is the
header that names the model and the cutoff and says what the tags mean (counted at four
characters per token, as since-cutoff does). `sync` writes notes only for the changed APIs your
code uses (`--scope imported` adds up to 5 per imported package; `--header-only` leaves the
notes out and keeps the header and one line per package), and a model-written note has at most
60 words. In the benchmark, the blocks were 285 to 505 tokens, and sessions with the
notes used 0.73 times the total tokens of sessions without them. `run` reports each block's size
in tokens.
**Does it work offline?** `status` reads nothing from the network. `scan` and `sync` need PyPI
for each package's release list (cached for 12 hours; when PyPI cannot be reached, an older cached
copy is used and the scan says from which day it is) and for the sources of each changed package;
sources and diffs are cached, so later runs take seconds. Training cutoffs come from models.dev,
with a bundled snapshot for offline use.
## Results
What has been measured so far, each with its scope:
- **One project, one model, since-cutoff 0.1.0**: the card below, Claude Opus 4.6 on the sample
project. The table after it adds Claude Haiku 4.5 on the same project.
- **Benchmark**: 360 headless Claude Code sessions (`claude-opus-5-5`) on 24 Python tasks with
hidden tests, protocol frozen before the main run. On the 17 post-cutoff tasks, sessions with
since-cutoff 0.4.1's notes cost 0.80 times as much as Claude Code alone (95% CI 0.70-0.90),
with 0.83 times the turns and 0.87 times the wall time. No pass-rate claim: Claude Code alone
already passed 94.1% of them, above the pre-registered 90% ceiling. The Context7 arms ran
without an API key and got only "Monthly quota exceeded" from the 215th of 360 sessions on,
which does not touch the notes-vs-alone comparison. One model, one agent:
[results](https://mohammadhijjawi97.github.io/since-cutoff/benchmark.html),
[tasks, protocol and transcripts](https://github.com/MohammadHijjawi97/since-cutoff-benchmark).
- **Reports from independent developers** will be listed here, each with its project, model and
date. Post yours in [Share your results](https://github.com/MohammadHijjawi97/since-cutoff/discussions/6).
<p align="center"><picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/MohammadHijjawi97/since-cutoff/main/docs/img/hero-dark.svg">
<img src="https://raw.githubusercontent.com/MohammadHijjawi97/since-cutoff/main/docs/img/hero.svg" width="640" alt="Your coding model learned your libraries before they changed. Claude Opus 4.6 on one sample project, measured with since-cutoff 0.1.0: on 7 of 16 probed API changes it used a name or parameter that has since been removed; with the notes, 5% to 65% of 20 held-out tasks were correct. Try it: uvx since-cutoff scan (no model calls, no API key).">
</picture></p>
Two Claude models on the 9-dependency sample project in
[`examples/agent-app`](https://github.com/MohammadHijjawi97/since-cutoff/tree/main/examples/agent-app),
**measured with since-cutoff 0.1.0**, with Claude Opus 4.6 writing the tasks and notes:
| | Claude Haiku 4.5 | Claude Opus 4.6 |
|---|---|---|
| training cutoff | Feb 2025 | May 2025 |
| API changes probed | 20 | 16 |
| **stale** / wrong / deprecated / correct | **5** / 1 / 2 / 12 | **7** / 0 / 3 / 6 |
| libraries with stale use | 3 of 5 probed | 2 of 4 probed |
| notes written (with an example that type-checks) | 8 (7), about 391 tokens | 10 (7), about 437 tokens |
| **held-out correct, without -> with notes** | **14% -> 57%** (14 pairs) | **5% -> 65%** (20 pairs) |
| previously-correct APIs after notes | 6/6 still correct | 6/6 still correct |
*Held-out* tasks are paraphrases of the task each failing change was probed with; each one is
answered twice, without and with the notes, and scored the same way. The last row re-checks APIs
the model already got right, to catch notes that make things worse.
In this sample the stronger model was not safer: Opus 4.6 wrote APIs that were removed after its
cutoff, including `anthropic.HUMAN_PROMPT` with `client.completions`. Stale code from both runs,
each valid for the comparison release and rejected by the type checker for the pinned one:
`messages.create(temperature=...)` (anthropic 1.8), `hf_hub_download(resume_download=...)`,
`local_dir_use_symlinks=...`, `force_filename=...` and `proxies=...` (huggingface-hub 2.0), and
`client.beta.vector_stores` (openai 3.x). At run time, anthropic 1.8.0 raises `TypeError` for
`temperature`; huggingface-hub 2.0.0 still accepts those four download arguments, ignores them
and warns.
The notes written in the Claude Haiku 4.5 run (excerpt, verbatim; since-cutoff 0.1.0's format):
```markdown
<!-- since-cutoff:start -->
## 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.
<!-- since-cutoff:end -->
```
The huggingface-hub note is not quite right: `resume_download` left the signature in 1.0, not
2.0.0, and 2.0.0 still accepts it at run time, ignores it and warns
([source](https://github.com/huggingface/huggingface_hub/blob/v2.0.0/src/huggingface_hub/utils/_validators.py#L171-L191)).
Omitting it is still the right advice. The note that 0.4.0 and later write from the API diff for
the same arguments is in [Quick start](https://github.com/MohammadHijjawi97/since-cutoff#quick-start);
the runtime caveat is in the terminal, the report and the JSON, not in the block.
The terminal summary of the Claude Opus 4.6 run, recorded with 0.1.0. The probe results are the
ones in the table above. The diff counts on the card are 0.1.0's ("725 changes flagged"); after
fixes to the diff, `scan` in 0.2.0 reports 513 breaking changes and 48 new deprecations for the
same cutoff. The card's "changes fixed" count and its 95% CI also follow 0.1.0: the interval
belongs to that count, not to the 5% -> 65% rates, and versions up to 0.2.0 counted a change as
fixed even when a held-out answer was already correct without the notes.
<p align="center"><img src="https://raw.githubusercontent.com/MohammadHijjawi97/since-cutoff/main/docs/img/run-opus.svg" width="100%" alt="since-cutoff 0.1.0 run on Claude Opus 4.6: stale API use in 2 of 4 probed dependencies; 16 API changes probed: 7 stale, 0 wrong, 3 deprecated, 6 correct; 10 notes; held-out tasks correct without -> with notes: 5% -> 65% (20 paired tasks); a list of the stale calls"></p>
<details>
<summary>The same summary for the Claude Haiku 4.5 run (also 0.1.0; scan in 0.2.0 reports 491 breaking changes and 50 new deprecations for its cutoff)</summary>
<p align="center"><img src="https://raw.githubusercontent.com/MohammadHijjawi97/since-cutoff/main/docs/img/run.svg" width="100%" alt="since-cutoff 0.1.0 run on Claude Haiku 4.5: stale API use in 3 of 5 probed dependencies; 20 API changes probed: 5 stale, 1 wrong, 2 deprecated, 12 correct; 8 notes; held-out tasks correct without -> with notes: 14% -> 57% (14 paired tasks)"></p>
</details>
Small samples, two models, one project: treat this as a demonstration of the method, not a
benchmark. Every run writes its full report (each task, answer and type-checker error) to
`.since-cutoff/report.md`. To repeat the experiment with the current version (its diff and
ranking changed, so the probes will not be identical):
`cd examples/agent-app && since-cutoff run --model claude-code:claude-haiku-4-5 --task-model claude-code:claude-opus-4-6`.
Since 0.3.0, a run can also save its tasks: add `--tasks-out tasks.json`, and anyone
can repeat the run on exactly the same tasks with `--tasks-from tasks.json`, for another model or
another set of notes. The file names who wrote the tasks (model, prompt version, since-cutoff
version), and a run on reused tasks reports it
([details](https://github.com/MohammadHijjawi97/since-cutoff/blob/main/docs/how-it-works.md#2-probe)).
Results from your own projects are very welcome in
[Share your results](https://github.com/MohammadHijjawi97/since-cutoff/discussions/6).
How the measurement works and what these numbers do and do not show, in more detail:
[the write-up](https://mohammadhijjawi97.github.io/since-cutoff/).
## Research: does a dependency block pay for its tokens?
A technical report (October 2026) tests the block that `sync` writes in 1,607 agent sessions and puts the result into
a break-even model, with every length counted in model calls. The key findings:
- **A small saving, on one model.** On Opus 5.5 the block cut cost to 0.80 of Claude Code alone in the main study
(95% CI 0.70-0.90) and tokens to 0.79 on eight new tasks (not detected after Holm correction, p = 0.094); a
replication did not meet its pre-committed rule, and
Sonnet 5.5, Haiku 5.5 and an open-weight agent showed no detectable saving. All tasks were written by Opus 5.5.
- **The header alone did about as well.** No pre-committed test separated the full block from a header that only
names the changed packages, or from a generic "check the installed signatures" warning.
- **Cheap, but not repaid by the affected sessions alone.** In the author's agent contexts a 704-token block costs
about 0.4% of a context; assuming a fixed saving per affected context, the contexts that need a changed API repay it only once they make up 14-49% of all
contexts, against an estimated 0.8-7%.
- **The effect on all other sessions decides.** Control tasks put it at a 6.6% saving, but it cannot be told from
zero. Off by default is a loss-averse choice, not proof of a loss; a live A/B test of about 2,500-2,900 sessions per
arm would settle it.
Read the [report, data and scripts](https://github.com/MohammadHijjawi97/since-cutoff/tree/main/research/dependency-block-study) or the [summary on the docs site](https://mohammadhijjawi97.github.io/since-cutoff/research.html).
## Use it from any agent (MCP)
`since-cutoff mcp` is an MCP server that lets a coding agent ask "what changed in this library
since my training cutoff?" before it writes code. It has three read-only tools and two prompts:
| tool | answers |
|---|---|
| `api_changes(package, model, symbol=...)` | what changed in one library between the release published at least 30 days before the model's cutoff (by default) and the latest (or a given) version, hard breaks first |
| `project_changes(project_dir, model)` | the same for every dependency of a project at its pinned version, starting with the changed APIs your code uses: for each, the files that use it (at most 3), its note, the runtime caveat and names that look similar, not confirmed as replacements; with `header_only=true`, only the header `sync --header-only` writes (the changed packages the code imports and their versions) |
| `model_cutoff(model)` | a model's training cutoff, from [models.dev](https://models.dev) |
| prompt | asks the agent to |
|---|---|
| `check_project(project_dir=".")` | call `project_changes` with its own model id, start with old-form uses, and offer `since-cutoff sync` for AGENTS.md notes |
| `before_upgrade(package, to_version="")` | read the pinned package version, call `api_changes` for the intended upgrade, and list what project code needs to change |
The agent passes its own model id, so the answer covers what changed after that model's training
cutoff. The tools read PyPI and package sources statically: no model calls, no API key, no
package code executed.
**Claude Code**
```bash
claude mcp add --scope user since-cutoff -- uvx since-cutoff@latest mcp
```
**Codex** (`~/.codex/config.toml`)
```toml
[mcp_servers.since-cutoff]
command = "uvx"
args = ["since-cutoff@latest", "mcp"]
startup_timeout_sec = 60
tool_timeout_sec = 900
```
**Cursor** (`~/.cursor/mcp.json`) and **Claude Desktop** (`claude_desktop_config.json`)
```json
{
"mcpServers": {
"since-cutoff": { "command": "uvx", "args": ["since-cutoff@latest", "mcp"] }
}
}
```
**VS Code** (`.vscode/mcp.json`)
```json
{
"servers": {
"since-cutoff": { "type": "stdio", "command": "uvx", "args": ["since-cutoff@latest", "mcp"] }
}
}
```
**Gemini CLI**
```bash
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` makes `uvx` pick up new releases instead of reusing the first version it cached (the
plugin's own `.mcp.json` pins the exact release). If the client cannot find `uvx`, install
[uv](https://docs.astral.sh/uv/) or give the full path (`which uvx`). The server is listed in the
[MCP Registry](https://registry.modelcontextprotocol.io) as `io.github.MohammadHijjawi97/since-cutoff`.
The first `project_changes` call on a larger project downloads the wheels of every dependency
that changed and can take several minutes (very large packages such as transformers take the
longest). Results are cached, so later calls take seconds. To warm the cache, run
`since-cutoff scan` in the project once; it shares the cache with the server. Clients with a
short default tool timeout may need a longer one, as in the Codex example above.
`project_changes` keeps its answer under about 24,000 characters: changed dependencies that do
not fit get one line each, and passing them in `only` lists their changes.
What `api_changes("huggingface-hub", model="claude-haiku-4-5", to_version="2.0.0")` returns
(real output, trimmed):
```markdown
# 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.
```
With `symbol="hf_hub_download"` it lists only the 8 changes to that function (`resume_download=`,
`force_filename=`, `local_dir_use_symlinks=` and `proxies=`, on the function and on `HfApi`).
`symbol` also takes a call the way code writes it: `client.messages.create` finds the changes
to `Messages.create`. The diff reads signatures: these four parameters left the signature in
huggingface-hub 1.0, and the answer adds that 2.0.0's source still handles them, so calls
passing them may run with a warning.
## Use in CI
Problems with command output or an MCP client? See the
[troubleshooting guide](https://github.com/MohammadHijjawi97/since-cutoff/blob/main/docs/troubleshooting.md).
### GitHub Action
Scans the project on each pull request and adds a summary to the job page: the changed APIs your
code uses, with the files that use them, the change, whether in the old form and the
replacement, then, folded, the notes and each dependency's changes. With `check-notes: true` it
also fails the job when the notes in AGENTS.md are out of date. Like `scan`, it only reads PyPI
and models.dev: no model calls, no API key.
```yaml
# .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`
```
Without `paths`, the job also runs when a code change starts using a changed API.
| input | default | |
|---|---|---|
| `model` | required | `provider:model` as for `--model`; only its training cutoff is used |
| `working-directory` | `.` | the project directory |
| `only`, `exclude` | | comma-separated PyPI names |
| `cutoff` | | override the training cutoff (`YYYY-MM` or `YYYY-MM-DD`) |
| `cutoff-margin` | | compare from the latest release published this many days before the cutoff (since-cutoff's default is 30; `0` compares from the cutoff itself) |
| `fail-on-changes` | `false` | fail the step when a dependency changed its API since the release it is compared from (by default the latest one published 30 days before the cutoff) |
| `check-notes` | `false` | also run `since-cutoff sync --check`, which writes nothing, and fail the job when the notes in AGENTS.md / CLAUDE.md are out of date or the block was edited by hand; the notes keep the model they were written for, and `model` (and `cutoff`) serve a project with no block yet |
| `step-summary` | `true` | add the Markdown summary to the job summary |
| `cache` | `true` | keep PyPI metadata, package sources and API diffs between runs (also when `fail-on-changes` fails the job) |
| `args` | | more `since-cutoff scan` arguments, e.g. `--all-deps --limit 20` |
| `since-cutoff-version` | `0.5.0` | the since-cutoff release to run, or `latest` |
`args: --fail-on old-form --annotate github` fails the job only when your code uses a changed
API in the old form, and annotates each file that uses one: a warning for the old form, a notice
otherwise.
Outputs: `changed-packages` (comma-separated), `changes` (breaking changes), `deprecations`,
`markdown` (the summary's path, for example to post it as a pull request comment), `report`
(the full report's path) and, with `check-notes`, `notes` (`up-to-date`, `out-of-date`,
`edited-by-hand` or `error`). A change reachable under several import paths is counted once.
### pre-commit
```yaml
# .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
```
`since-cutoff-scan` runs when a lockfile, a requirements file, a conda `environment.yml` or
`pyproject.toml` changes, and prints the scan; give it `--model` in `args`. `since-cutoff-sync`
runs when one of those files, AGENTS.md or CLAUDE.md changes, and updates the notes; when it
changes the file, the hook fails, as pre-commit hooks that change files do: add the file and
commit again. With `args: [--check]` it changes nothing and fails while the notes are out of
date. It keeps the model the notes were written for; for a first block, give it one:
`args: [--model=anthropic:claude-sonnet-4-5]`. Both need PyPI, so skip them on pre-commit.ci
(`ci: {skip: [since-cutoff-scan, since-cutoff-sync]}`). `since-cutoff-status` needs no network:
it fails when the notes do not match the lockfile.
### Other CI
See the [environment, files and exit-code reference](https://github.com/MohammadHijjawi97/since-cutoff/blob/main/docs/reference.md) when configuring CI.
```bash
# 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 -` prints the summary to stdout, and only the progress and the report's path to
stderr, as `--json` does. In a file, a pipe or a CI log there is no live progress bar, and the
output is laid out 160 columns wide (`COLUMNS` sets another width). Exit codes:
- `0` ok;
- `1` error (for example, `run` could not probe any API change or score any model answer, or
`sync` could not check a package its notes are about);
- `2` usage error;
- `3` a check failed: `scan --fail-on changes`, `used` or `old-form` (a dependency changed its
API since the release it is compared from, by default the latest one published 30 days
before the cutoff; your code uses a changed API, or uses one in the old form;
`--fail-on-changes` is `--fail-on changes`; with any of them, also a dependency the running
Python cannot parse the sources of), `run --fail-on-stale` (stale API use found), or
notes out of date with `sync --check` or `status` (and `sync`, when it did not write them);
- `4` `sync`: the block was edited by hand, and nothing was written without `--force`;
- `141` the output was closed early (piped into `head`, for example).
## How it works
`scan` and `sync` need no model: they diff the public APIs, find where your code uses the changed
ones and state each change from the diff, tagged with what was checked
([details](https://github.com/MohammadHijjawi97/since-cutoff/blob/main/docs/how-it-works.md#notes-without-a-model-scan-and-sync)).
`run` goes through three stages. The first is the scan and needs no model; in the other two, a
type checker scores every answer and checks every note the model writes:
<p align="center"><picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/MohammadHijjawi97/since-cutoff/main/docs/img/how-it-works-dark.svg">
<img src="https://raw.githubusercontent.com/MohammadHijjawi97/since-cutoff/main/docs/img/how-it-works.svg" width="640" alt="Three stages. Scan, with no model calls: the lockfile gives your exact versions, then the latest release published at least 30 days before the model's cutoff, then a static API diff with griffe. Probe: short tasks that need the change, the model answers from memory, basedpyright checks the answer against both versions. Write and test notes: a model-written note is kept only if its example type-checks, otherwise the change is stated from the API diff; held-out tasks are answered without and with the notes, and --apply writes a block into AGENTS.md.">
</picture></p>
| outcome | meaning |
|---|---|
| **stale** | the code is valid for the comparison release (by default the latest one published at least 30 days before the model's cutoff) and invalid for yours, and the error involves an API that changed |
| **wrong** | invalid for your version, but not explained by a change (hallucinated or misused API) |
| **deprecated** | valid, but uses an API marked `@deprecated` in your version |
| **correct** | valid for your version and actually uses the changed API |
| untouched / off-task / invalid / error | not counted in any rate, and always reported |
Since 0.3.0, the held-out result gives the task-level rates without -> with notes
(with the number of paired tasks and API changes behind them), their difference with a 95%
bootstrap interval that resamples API changes, the changes the notes fixed (wrong without,
correct with) with a Wilson 95% interval, the changes they broke, an exact sign test of fixed
against broken, and the held-out pairs not counted, by reason. The regression check reports how
many previously-correct APIs are still correct with the notes.
`run --compare template,signatures` (0.3.0 and later) also answers the held-out tasks and
regression checks with baseline notes that need no model: `template` states each failing change in
one sentence from the API diff, and `signatures` gives the new signature and first docstring
paragraph of each changed API, or of the replacement its library n历史前置条件说明(补充参考)
Python 3.10+;建议 pipx 或 uvx;scan/sync 需访问 PyPI,status 离线;run 需 basedpyright 额外依赖及模型凭据(ANTHROPIC_API_KEY/OPENAI_API_KEY/OPENROUTER_API_KEY/DEEPSEEK_API_KEY 或本地 Ollama 等);费用与国内网络兼容性待核验
项目来源与清单版权许可
https://github.com/MohammadHijjawi97/since-cutoff
整理来源:https://github.com/slavakurilyak/awesome-ai-agents
清单版权与许可:
MIT License
Copyright (c) 2024 Slava Kurilyak
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.