
since-cutoff
io.github.MohammadHijjawi97由 GitHub 账号 MohammadHijjawi97 发布v0.5.0更新于 Oct 24, 2026
Which Python library APIs changed after your model's training cutoff (static diff, no model calls)
概览
让编程助手检查哪些 Python 库 API 在其训练截止时间之后发生了变化,以及项目代码在何处使用了这些 API。
- 可以做什么
- 该服务器提供与 since-cutoff 命令行工具相同的静态差异分析:读取项目的锁文件,将每个固定依赖的公共 API 与模型训练截止时间之前发布的最新版本进行比较,并报告代码使用了哪些已变更的 API,并为每个变更附上简短说明。README 中提到的工具包括 project_changes 和 api_changes,因此助手可以在编写代码前查询某个库发生了哪些变化。扫描是静态的,不调用模型。
- 适用场景
- 当你使用编程助手编写 Python 代码,并且固定的依赖在模型训练截止时间之后有新版本发布时使用,尤其是 Python AI 技术栈。它可以帮助助手避免写出固定版本已不再接受的调用。
- 运行要求
- 作为 PyPI 包通过 stdio 在本地运行,可用 uvx 启动,或用 pipx、pip 安装。需要 Linux、macOS 或 Windows 上的 Python 3.10 或更高版本,以及包含 uv.lock、poetry.lock、pyproject.toml 或 requirements.txt 等锁文件或依赖文件的项目根目录。scan、sync 和 status 路径不需要 API 密钥;只有可选的 run 命令会调用模型,可能需要 ANTHROPIC_API_KEY 或 OPENAI_API_KEY 等提供商密钥。
安装
在 SourceWeft 中
- 打开 控制台中的 since-cutoff,将其添加到工作区。
- 为需要使用其工具的对话启用该服务。
Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。
其他 MCP 客户端
参照 仓库 中的启动说明。
README
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.
[PyPI] [Python 3.10+] [CI] [License: MIT] [Status: beta] [since-cutoff MCP server on Glama]
English | 简体中文 | Español | Français
Install
Python 3.10 or later, on Linux, macOS or Windows.
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, 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).
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 and In other coding agents. The MCP server gives any agent the same diff.
Quick start
The sample project
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):
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:
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).
Try it on your project. No API key, no model call. In the project root:
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
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):
scanfor a look: the changed APIs your code uses, where, and a note for each;syncto put the notes into AGENTS.md or CLAUDE.md and keep them current, andsync --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;
runto 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
runasks the model; - rewrite your code: for migrating code you already have, a codemod is the right tool (How it compares);
- cover other languages yet: Python only, TypeScript is #1; and it names files, not lines (#8).
The full list is in 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,
which pins six of its nine dependencies to current releases (for the other three, which are
unpinned, the tool uses the latest release):
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):
since-cutoff does three things about it:
scanfinds, 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.syncwrites 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 --checkandstatustell 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.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 and to CI through a GitHub Action and pre-commit hooks. The explainer below covers this section in 2½ minutes.
[Watch the 2½-minute explainer (video with voice-over)]
▶ Watch the 2½-minute explainer (video with voice-over)
Using it
What scan prints for the sample project, in full:
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. 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.
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:
More options:
sync --scope importedalso 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.scansuggests it when that is the case.sync --profile evaluatedis the configuration the benchmark measured:--scope importedwith 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 0to compare from the same day.sync --header-onlywrites 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-onlywrites 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 --suggestionsadds 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 --applywrote are tagged[type-checked].synckeeps 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); 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). 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);
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).
It uses your API credits or Claude Code usage and can take 5-20 minutes.
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
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, 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
This installs the same skill through the open 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 scanor calls the MCP toolproject_changes. - "Add notes about them to AGENTS.md." The agent runs
since-cutoff sync --dry-run, shows you the diff, and runssince-cutoff sync --yeswhen 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
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"):
SINCE_CUTOFF_MODEL(a full spec such asopenai:gpt-5.4) always wins.- Inside Claude Code (which sets
CLAUDECODE=1for the commands it runs), only Claude Code's settings count:ANTHROPIC_MODEL, then the project's.claude/settings.local.jsonand.claude/settings.json, then~/.claude/settings.json. - Elsewhere the most specific setting wins: first
ANTHROPIC_MODEL,GEMINI_MODELorAIDER_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:
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 (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; 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.
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
and 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); 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, tasks, protocol and transcripts. - Reports from independent developers will be listed here, each with its project, model and date. Post yours in Share your results.
Two Claude models on the 9-dependency sample project in
examples/agent-app,
measured with since-cutoff 0.1.0, with Claude Opus 4.6 writing the tasks and notes:
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):
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).
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;
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.
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)
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).
Results from your own projects are very welcome in
Share your results.
How the measurement works and what these numbers do and do not show, in more detail: the write-up.
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 or the summary on the docs site.
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:
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
Codex (~/.codex/config.toml)
Cursor (~/.cursor/mcp.json) and Claude Desktop (claude_desktop_config.json)
VS Code (.vscode/mcp.json)
Gemini CLI
@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 or give the full path (which uvx). The server is listed in the
MCP Registry 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):
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.
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.
Without paths, the job also runs when a code change starts using a changed API.
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
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 when configuring CI.
--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:
0ok;1error (for example,runcould not probe any API change or score any model answer, orsynccould not check a package its notes are about);2usage error;3a check failed:scan --fail-on changes,usedorold-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-changesis--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 withsync --checkorstatus(andsync, when it did not write them);4sync: the block was edited by hand, and nothing was written without--force;141the output was closed early (piped intohead, 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).
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:
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 names. All blocks are scored on
the same pairs, against the same answers without notes, and each block's size is shown in tokens,
so a run shows what since-cutoff's own notes add over them.
Everything is scored by a type checker against the exact package versions, each in an isolated
environment with that package's own runtime dependencies. No LLM judges anything, and every
number traces back to results.json. Details: docs/how-it-works.md.
What "verified" means
since-cutoff does not use the word "verified" on its own. Each note carries a tag that says what was checked, and nothing else is claimed:
Tags combine: evidence is joined with + ([diff + library]), and a guess comes after ;
([diff; probable rename], [diff; not confirmed]).
Names that merely look similar are never written into the notes by default. The terminal,
report.md and the MCP tools show them as "not confirmed as replacements", and not at all where
the library says there is no replacement; sync --suggestions adds them, tagged
[not confirmed].
The type check behind [type-checked]. A model-written note is kept only when all of these
hold (the model gets two tries):
- its complete example parses and imports the package;
- basedpyright (standard mode, deprecations reported as errors,
# type: ignoreand# pyright:comments removed) reports no error attributed to that package, and no deprecation of it, when the example is checked against the sources of the exact version in your lockfile plus that version's runtime dependencies, in an otherwise empty environment; - every library name the bullet puts in backticks appears in that example or in the API-diff entry the note is about;
- the bullet has at most 60 words.
So [type-checked] means the imports, names, parameters and argument counts the example uses exist
in your version and are not marked @deprecated (PEP 702). Errors outside the package (the
standard library, other libraries) do not block a note. It does not mean that the code behaves
correctly at run time, that the replacement is the one the maintainers recommend, or that the note
helps the model. When a note fails the check, since-cutoff writes the note from the API diff
instead, with its tag.
Measured is separate: since-cutoff run answers held-out tasks without and with the notes and
reports the counts. Nothing else in since-cutoff says whether a note helps.
In run, an answer counts as correct when it imports the package, uses the changed API, and
has no "knowledge" error attributed to the package on your version (unknown name, import or
parameter; missing required argument; wrong number of arguments). Pure type-strictness complaints
are ignored. No answer is executed.
The block in AGENTS.md holds the bullets with their tags, and for each package the version its
notes apply to and the release they are compared from. The rest is in scan --json and results.json:
used_apis[] (each changed API your code uses, where, its changes, its replacements with their
source, and its note with tags, applies_to and checks) and, after run, notes_detail[]
(each note with the model's example and what the held-out test measured; verified is kept,
meaning the same as checks.example_type_checks).
What it runs, sends and stores
The reference lists environment variables and files read or written.
- Runs no package code and no model-written code. Packages are read statically (griffe with
inspection off; only
.py/.pyifiles are extracted, with path and size checks). The model's answers are only type-checked, locally, with basedpyright. - Fetches public package metadata and wheels from PyPI, and model cutoffs from models.dev (a
snapshot is bundled for offline use). Release lists are cached for 12 hours; when PyPI cannot
be reached, an older cached copy is used and the scan says from which day it is. Each
downloaded wheel or sdist is checked against the
sha256 that PyPI lists before extraction. Git, path, workspace and private-index dependencies
are never looked up on public PyPI by name.
statusfetches nothing. - Sends prompts only in
run, and only to the model provider you choose: package names, versions, public signatures and docstrings of the changed APIs, the generated tasks and, for notes, the model's own answers. Never your source code.scan,sync,status, the MCP server, the GitHub Action and the pre-commit hooks send nothing to any model. - Shows where your code uses a changed API (file names for now) in the terminal and in
.since-cutoff/, and sends it nowhere. What you pass on is up to you:--markdownand--annotate githubput it in a CI job's summary and annotations, and the MCP toolproject_changesgives it to the agent that asked, which passes tool results to its model. - Stores results in
.since-cutoff/in your project (it ignores itself in git) and a local cache:since-cutoff cache infoshows where it is and its size by kind,since-cutoff cache clearremoves it, or with--sources,--diffsor--pypionly that kind (the model's answers torun, which cost money, are kept). The extracted package sources, the bulk of it, are capped at 2 GB (SINCE_CUTOFF_CACHE_MAX_MB; 0 for no cap): after a new version is extracted, the least recently used ones go, never one a running scan uses, and the first removal in a run prints one line saying how much went.sync(after you agree, or with--yes) andrun --applywrite one marked block into AGENTS.md or CLAUDE.md and leave the rest of the file byte-for-byte unchanged;since-cutoff unapplyremoves the block. - No telemetry, no account, no personal data. Re-runs come from the cache, so they are free
and reproducible (
--freshasks the model again). See PRIVACY.md.
Limitations
- Python only for now. TypeScript (
.d.tsdiffs,tsc) is next (#1). - A type checker sees wrong names, wrong parameters and PEP 702 deprecations. It cannot see
behaviour changes behind an unchanged signature, or deprecations that only warn at run time.
scanalso lists deprecations declared with a library's own decorator (name containing "deprecat") and removed names that a module still serves with a warning through__getattr__, butrundoes not probe them. - The diff covers the public API:
_privatenames, and test suites, benchmarks and examples shipped inside a package, are skipped. - The sources are parsed by the Python that runs since-cutoff, which cannot read syntax of a
newer one: on Python 3.10, numpy 2.5 (PEP 695
typestatements in its stub) cannot be checked. The scan says so and which Python can ("run since-cutoff with Python 3.12 or later to check it"), and--fail-onfails on it. Run since-cutoff with the Python your project uses, or a newer one. - "Your code uses" is a static match, file by file: imports (re-exported names included),
calls, attribute reads and keyword arguments, with the receiver of a method call typed from
the package's own annotations (
df = pd.read_csv(...)is aDataFrame; a class counts with its base classes, across your pinned packages). It does not follow dynamic access such asgetattr, and it names files, not lines, for now (#8). A method read on a value whose class the code does not show can count by its name alone, when that name is one changed API's and neither a common one nor a member of a built-in or standard-library type: such a[name match]is hidden from every report, the notes,--fail-onand--annotateunless--include-name-matchesis given (include_name_matchesover MCP), as most are another library's method of the same name;scan,syncandproject_changessay how many uses they hid (name_matches_hiddenin results.json). A parameter that became required, keyword-only or positional-only is always "uses this API", never "old form", for now. - A note names a replacement only when the library's own deprecation text states it. Advice that
is only in a migration guide (anthropic's
MIGRATION.md
suggests
extra_bodyfor older models that still taketemperature) is not in the notes. - Probes cover a ranked sample of the breaking changes (symbols your code already uses first), not all of them.
- "The comparison release" is the newest release published at least 30 days before the cutoff
date (
--cutoff-margin;0compares from the cutoff itself). Models know the weeks before their cutoff less well, so real staleness can start earlier still; the margin is an estimate of how much earlier. - Held-out tasks are paraphrases of the same change: they show that a note fixes that change, not that the model got better in general.
What the training cutoff is used for
The cutoff date picks a comparison point. It is not a claim about what a model memorised.
since-cutoff takes the date from models.dev (or --cutoff); a month means its last day
(2025-07 is 31 July 2025). It moves that date back by the margin (--cutoff-margin, 30 days
by default), and for each dependency takes the newest final, non-yanked release uploaded on or
before the day it lands on (a pre-release only if the package had no final release by then,
never a development release), and diffs that release's public API against your locked version.
That diff is a list of candidates: API changes that the model's training data probably does not
include.
The comparison date decides three things:
- which packages are diffed at all: a package whose locked version is no newer than that release has nothing to diff, and a package first released after the date is listed as new;
- in
run, which versions of that release's own dependencies the old side is type-checked with (the newest each requirement allowed on that date); - the "old" side of every probe in
run, so an answer that is valid there and invalid for your version, with the error on a changed API, is "stale" rather than "wrong".
A model can know a release after its stated cutoff or not know releases shortly before it, so the
scan can list changes the model already handles and miss some it does not. The margin is for the
second case; --cutoff-margin 0 compares from the cutoff itself. Whether the model actually
writes the old API is shown only by run, which asks it: with no tools, told which version the
project pins.
How it compares
since-cutoff answers one question for one project: which public APIs of the versions you pin
changed since the release a model's training cutoff points to, starting with the ones your code
uses? since-cutoff run adds two optional questions: does this model actually get them wrong,
and does a short note fix it? Most tools below answer a different question ("what do the
library's docs say now?") and work well alongside it.
--compare signatures in since-cutoff run gives the model the new version's signature and the
first paragraph of its docstring. It is a local stand-in for a documentation lookup, not Context7.
Two smaller tools work on the same problem: cutoff
probes a library you maintain by running model-written programs against its current version, and
postcut turns a Ruby Gemfile.lock into a brief of changes
since the cutoff. since-cutoff is built on griffe,
basedpyright, models.dev and rich.
Using since-cutoff with Context7
since-cutoff scan tells you which APIs to look up; Context7 can supply the docs. Name the
version you pin when you ask ("anthropic 1.8.0"). Context7 matches it only when the library's
owners added that version:
on 2026-09-27, /openai/openai-python offered v1.68.0, v1_105_0, v2.8.1 and v2.11.0, and
/anthropics/anthropic-sdk-python offered none, so you may get the default branch's docs.
Related research
- Deprecated APIs in code completion. Wang et al., LLMs Meet Library Evolution: Evaluating
Deprecated API Usage in LLM-based Code Completion (ICSE 2025;
arXiv:2406.09834, first titled How and Why LLMs Use
Deprecated APIs in Code Completion? An Empirical Study). 7 models, 145 mappings from a
deprecated API to its replacement in 8 Python libraries, 28,125 completion prompts. Most
completions used neither API. Of those that used one of the two (the paper's "plausible"
completions), 25-38% used the deprecated one over the whole dataset: 70-90% when the prompt came
from code that used the deprecated API, 9-18% when it came from up-to-date code. Two baseline
fixes were tested on up-to-date prompts where a model had used the deprecated API. ReplaceAPI
swaps the deprecated API's tokens for the replacement during decoding and lets the model finish
the line: the replacement was then used in 85.2-99.6% of cases on the six open models (it needs
control of decoding, so not GPT-3.5). InsertPrompt adds the comment
# {dep} is deprecated, use {rep} instead and revise the return value and arguments.and regenerates: 25.7-97.2%, depending on the model, which the authors judge not yet effective or accurate enough. since-cutoff's notes are close to InsertPrompt, moved into the project's instructions file;since-cutoff runmeasures them on held-out tasks instead of assuming they work. - Documentation in context is not enough on its own. Ashik et al., When LLMs Lag Behind:
Knowledge Conflicts from Evolving APIs in Code Generation
(arXiv:2604.09515, 2026 preprint). 270 real API updates (45
deprecated or removed, 128 modified, 97 new) from releases of 8 Python libraries after December
2023, and 11 models from 4 families with training cutoffs before that date. Given only a
description of the update, the models at least partly adopted it in 74.64% of answers (judged
by GPT-5 mini), and 42.55% of those answers ran in the library version that introduced the
update; with the API documentation as well, 92.87% adopted it and 66.36% ran. Adding
chain-of-thought and self-reflection prompts raised the executable rate by a further 11.33%, a
relative gain rather than percentage points. Of the answers that did not adopt the update, 42.1%
ignored it entirely and 16.4% used the old API; of the adopting answers that still failed to run
in the best setup, the most common update-related cause was wrong parameters (26.6% of those
failures). This is why since-cutoff checks code against your exact version, and why
since-cutoff runre-tests the model with the notes rather than assuming they are followed. - Benchmarks. GitChameleon 2.0: 328 Python completion problems, each tied to specific library versions and checked by executable unit tests; enterprise models reach 48-51% at baseline, retrieved documentation adds up to about 10 points (GPT-4.1: 48.5% to 58.5%) and self-debugging about 10-20. VersiCode: version-specific code completion and version-aware code migration over more than 300 Python libraries and more than 2,000 versions across 9 years. CodeUpdateArena: knowledge editing for 54 functions from 7 Python packages, with synthetic, GPT-4-generated updates and 670 program-synthesis examples; prepending the update's documentation did not let open models (DeepSeek, CodeLlama) use it. LibEvolutionEval (NAACL 2025): version-specific inline completion across 8 libraries; retrieved version-specific documentation and prompting help.
These studies measure many models on fixed task sets; GitChameleon 2.0 and Ashik et al. run the
generated code. since-cutoff does something narrower: for one project it lists the changes since a
comparison release, the ones your code uses first, and run checks one model's answers
statically. It cannot see behaviour changes behind an unchanged signature, which tests that run
the code can.
Contributing
since-cutoff is young. The most useful help right now:
- Run it on your project and post what it found, including false positives, in Share your results.
- Pick up a good first issue: small, self-contained tasks such as another lockfile format or a provider preset.
- Bigger pieces are labelled help wanted, for example TypeScript support.
- Report a bug or an odd result in the issues.
CONTRIBUTING.md explains the code layout and the checks; the offline test suite runs the whole pipeline with a toy library and a scripted model, so no API key is needed. Security issues: SECURITY.md.
Citation
If you use since-cutoff in research, please cite it (see CITATION.cff).
License
MIT © Mohammad Hijjawi
来源:README.md,提交 e13cf02
工具
0版本历史
6- v0.5.0最新Sep 28, 2026
- v0.4.1Sep 28, 2026
- v0.3.2Sep 27, 2026
- v0.3.1Sep 27, 2026
- v0.3.0Sep 27, 2026
- v0.2.0Sep 27, 2026
评分
已满足适用检查项的 50%
该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。
- 近 30 天内可连通25 分
请保持端点能够响应 MCP 握手;SourceWeft 每 30 天探测一次。
- 已声明认证方式10 分
请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。
- 已列出工具15 分
请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。
- 描述必需15 分
- 图标10 分
- 文档15 分
- 许可证10 分