since-cutoff 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- since_cutoff-0.1.0/.gitignore +15 -0
- since_cutoff-0.1.0/CHANGELOG.md +20 -0
- since_cutoff-0.1.0/LICENSE +21 -0
- since_cutoff-0.1.0/PKG-INFO +263 -0
- since_cutoff-0.1.0/README.md +233 -0
- since_cutoff-0.1.0/pyproject.toml +90 -0
- since_cutoff-0.1.0/src/since_cutoff/__init__.py +3 -0
- since_cutoff-0.1.0/src/since_cutoff/__main__.py +3 -0
- since_cutoff-0.1.0/src/since_cutoff/apidiff.py +875 -0
- since_cutoff-0.1.0/src/since_cutoff/cache.py +78 -0
- since_cutoff-0.1.0/src/since_cutoff/checker.py +562 -0
- since_cutoff-0.1.0/src/since_cutoff/cli.py +496 -0
- since_cutoff-0.1.0/src/since_cutoff/data/models_snapshot.json +3484 -0
- since_cutoff-0.1.0/src/since_cutoff/engine.py +1009 -0
- since_cutoff-0.1.0/src/since_cutoff/errors.py +27 -0
- since_cutoff-0.1.0/src/since_cutoff/models.py +206 -0
- since_cutoff-0.1.0/src/since_cutoff/net.py +97 -0
- since_cutoff-0.1.0/src/since_cutoff/notes.py +273 -0
- since_cutoff-0.1.0/src/since_cutoff/project.py +551 -0
- since_cutoff-0.1.0/src/since_cutoff/prompts.py +259 -0
- since_cutoff-0.1.0/src/since_cutoff/providers/__init__.py +87 -0
- since_cutoff-0.1.0/src/since_cutoff/providers/base.py +28 -0
- since_cutoff-0.1.0/src/since_cutoff/providers/claude_code.py +163 -0
- since_cutoff-0.1.0/src/since_cutoff/providers/http_api.py +111 -0
- since_cutoff-0.1.0/src/since_cutoff/pypi.py +451 -0
- since_cutoff-0.1.0/src/since_cutoff/report.py +446 -0
- since_cutoff-0.1.0/src/since_cutoff/selection.py +74 -0
- since_cutoff-0.1.0/src/since_cutoff/stats.py +25 -0
- since_cutoff-0.1.0/tests/__init__.py +0 -0
- since_cutoff-0.1.0/tests/conftest.py +271 -0
- since_cutoff-0.1.0/tests/test_apidiff.py +124 -0
- since_cutoff-0.1.0/tests/test_checker.py +111 -0
- since_cutoff-0.1.0/tests/test_engine.py +144 -0
- since_cutoff-0.1.0/tests/test_models.py +104 -0
- since_cutoff-0.1.0/tests/test_project.py +207 -0
- since_cutoff-0.1.0/tests/test_pypi_providers_cli.py +279 -0
- since_cutoff-0.1.0/tests/test_review_fixes.py +633 -0
- since_cutoff-0.1.0/tests/test_units.py +208 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2026-09-26)
|
|
4
|
+
|
|
5
|
+
First release.
|
|
6
|
+
|
|
7
|
+
- `since-cutoff scan`: find the dependency versions a model could have seen at its training
|
|
8
|
+
cutoff and statically diff their public API against the versions your project uses.
|
|
9
|
+
- `since-cutoff run`: probe the model on the most relevant breaking changes, score its code with
|
|
10
|
+
basedpyright against both versions (stale / wrong / deprecated / correct), write verified
|
|
11
|
+
AGENTS.md notes and measure their effect on held-out tasks.
|
|
12
|
+
- Lockfiles: uv.lock, poetry.lock, pdm.lock, pylock.toml, Pipfile.lock; requirements files;
|
|
13
|
+
`.venv` metadata; pyproject.toml.
|
|
14
|
+
- Providers: Claude Code CLI, Anthropic API, OpenAI, OpenRouter, DeepSeek, Ollama and any
|
|
15
|
+
OpenAI-compatible endpoint.
|
|
16
|
+
- Claude Code plugin with a `since-cutoff` skill.
|
|
17
|
+
- `--effort` (default `low`) for Claude Code calls, and only API-knowledge errors (unknown names,
|
|
18
|
+
imports and parameters, missing arguments, arity) count as stale or wrong; type-strictness
|
|
19
|
+
complaints do not.
|
|
20
|
+
- A reproducible example project in `examples/agent-app` and a first real run in the README.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mohammad Hijjawi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: since-cutoff
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Find which of your exact dependency versions your coding agent writes wrong, and fix it with a small, verified AGENTS.md note.
|
|
5
|
+
Project-URL: Homepage, https://github.com/MohammadHijjawi97/since-cutoff
|
|
6
|
+
Project-URL: Issues, https://github.com/MohammadHijjawi97/since-cutoff/issues
|
|
7
|
+
Project-URL: Changelog, https://github.com/MohammadHijjawi97/since-cutoff/blob/main/CHANGELOG.md
|
|
8
|
+
Author-email: Mohammad Hijjawi <mohammad.hijjawi1997@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agents-md,api-changes,claude-code,codex,coding-agents,knowledge-cutoff,llm,static-analysis
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: basedpyright>=1.20
|
|
25
|
+
Requires-Dist: griffe>=1.14
|
|
26
|
+
Requires-Dist: packaging>=23.0
|
|
27
|
+
Requires-Dist: rich>=13.7
|
|
28
|
+
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# since-cutoff
|
|
32
|
+
|
|
33
|
+
English | [简体中文](https://github.com/MohammadHijjawi97/since-cutoff/blob/main/README.zh-CN.md)
|
|
34
|
+
|
|
35
|
+
**Your coding agent learned your libraries before they changed.**
|
|
36
|
+
since-cutoff finds exactly which APIs of *your* dependency versions it gets wrong, and fixes
|
|
37
|
+
them with a small AGENTS.md note that is checked by a type checker, not by another LLM.
|
|
38
|
+
|
|
39
|
+
[](https://github.com/MohammadHijjawi97/since-cutoff/actions/workflows/ci.yml)
|
|
40
|
+
[](https://pypi.org/project/since-cutoff/)
|
|
41
|
+

|
|
42
|
+

|
|
43
|
+
[](https://github.com/MohammadHijjawi97/since-cutoff/blob/main/LICENSE)
|
|
44
|
+
|
|
45
|
+
<p align="center"><img src="https://raw.githubusercontent.com/MohammadHijjawi97/since-cutoff/main/docs/img/run.svg" alt="since-cutoff run: Claude Haiku 4.5 on a real project" width="860"></p>
|
|
46
|
+
|
|
47
|
+
## The problem
|
|
48
|
+
|
|
49
|
+
Every model has a training cutoff. Your lockfile does not. A few real examples for a model
|
|
50
|
+
with a July 2025 cutoff (Claude Sonnet 4.5) and current releases, found by `since-cutoff scan`:
|
|
51
|
+
|
|
52
|
+
| library | version the model saw | your version | what breaks |
|
|
53
|
+
|---|---|---|---|
|
|
54
|
+
| anthropic | 0.60.0 | 1.8.0 | `messages.create(temperature=..., top_p=..., top_k=...)` no longer accepted |
|
|
55
|
+
| huggingface-hub | 0.34.3 | 2.0.0 | `hf_hub_download(resume_download=..., force_filename=..., local_dir_use_symlinks=...)` removed |
|
|
56
|
+
| langchain-core | 0.3.72 | 1.6.5 | `retriever.get_relevant_documents()`, `llm.predict()` removed |
|
|
57
|
+
| openai | 1.98.0 | 3.19.2 | 26 breaking changes, 6 new deprecations |
|
|
58
|
+
|
|
59
|
+
For that sample project, 7 of 9 dependencies had changed their public API after the cutoff
|
|
60
|
+
(the static diff flags 483 changes; many are internals, which the task writer skips). An agent that learned the old API writes code that fails at
|
|
61
|
+
import or call time, or, worse, still runs because the old path is only deprecated.
|
|
62
|
+
|
|
63
|
+
<p align="center"><img src="https://raw.githubusercontent.com/MohammadHijjawi97/since-cutoff/main/docs/img/scan.svg" alt="since-cutoff scan output" width="820"></p>
|
|
64
|
+
|
|
65
|
+
Documentation tools paste whole docs into the context and hope. since-cutoff **measures**
|
|
66
|
+
which of those changes your model actually gets wrong, writes **only** the notes that are
|
|
67
|
+
needed, and **proves** on held-out tasks that the notes fix them.
|
|
68
|
+
|
|
69
|
+
## Features
|
|
70
|
+
|
|
71
|
+
- **`scan`**: for every dependency, the version your model saw at its training cutoff vs. the one
|
|
72
|
+
you pin, and a static diff of what broke in between (no model calls, no API key).
|
|
73
|
+
- **`run`**: probes the model with short tasks that need the changed APIs and scores its code with
|
|
74
|
+
a type checker against *both* versions: stale, wrong, deprecated or correct.
|
|
75
|
+
- **Verified fixes**: one-line AGENTS.md / CLAUDE.md notes, kept only if their example
|
|
76
|
+
type-checks against your exact version, and re-tested on held-out tasks.
|
|
77
|
+
- **Works where you are**: Claude Code plugin and skill, or any of Anthropic, OpenAI, OpenRouter,
|
|
78
|
+
DeepSeek, Ollama and OpenAI-compatible servers.
|
|
79
|
+
- **Every lockfile**: uv, Poetry, PDM, pylock, Pipenv, requirements files, or a `.venv`.
|
|
80
|
+
- **Safe and reproducible**: never runs package or model-written code; everything is cached;
|
|
81
|
+
full JSON and Markdown reports.
|
|
82
|
+
|
|
83
|
+
## Quick start
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# list API changes since your model's cutoff (fast, no model calls)
|
|
87
|
+
uvx since-cutoff scan
|
|
88
|
+
|
|
89
|
+
# probe the model, write verified notes, and apply them to AGENTS.md
|
|
90
|
+
uvx since-cutoff run --apply
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Or install it with `pipx install since-cutoff` (or `pip install since-cutoff`) and run
|
|
94
|
+
`since-cutoff`. Run it from your project root (anything with `uv.lock`, `poetry.lock`,
|
|
95
|
+
`pdm.lock`, `pylock.toml`, `Pipfile.lock`, `requirements*.txt`, `pyproject.toml` or a `.venv`).
|
|
96
|
+
|
|
97
|
+
### In Claude Code
|
|
98
|
+
|
|
99
|
+
```text
|
|
100
|
+
/plugin marketplace add MohammadHijjawi97/since-cutoff
|
|
101
|
+
/plugin install since-cutoff@since-cutoff
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Then ask Claude to "check which of our dependencies you are out of date on", or run
|
|
105
|
+
`/since-cutoff:since-cutoff`. The skill runs the CLI; the measuring itself is done by a fresh,
|
|
106
|
+
tool-less copy of the model, so the agent cannot grade itself.
|
|
107
|
+
|
|
108
|
+
## A real run
|
|
109
|
+
|
|
110
|
+
Two Claude models on the 9-dependency sample project in
|
|
111
|
+
[`examples/agent-app`](https://github.com/MohammadHijjawi97/since-cutoff/tree/main/examples/agent-app),
|
|
112
|
+
with Claude Opus 4.6 writing the tasks and notes:
|
|
113
|
+
|
|
114
|
+
| | Claude Haiku 4.5 | Claude Opus 4.6 |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| training cutoff | Feb 2025 | May 2025 |
|
|
117
|
+
| API changes probed | 20 | 16 |
|
|
118
|
+
| **stale** / wrong / deprecated / correct | **5** / 1 / 2 / 12 | **7** / 0 / 3 / 6 |
|
|
119
|
+
| libraries with stale use | 3 of 5 probed | 2 of 4 probed |
|
|
120
|
+
| notes written (type-checker verified) | 8 (7), about 391 tokens | 10 (7), about 437 tokens |
|
|
121
|
+
| **held-out correct, without -> with notes** | **14% -> 57%** (14 pairs) | **5% -> 65%** (20 pairs) |
|
|
122
|
+
| previously-correct APIs after notes | 6/6 still correct | 6/6 still correct |
|
|
123
|
+
|
|
124
|
+
The stronger model is not safer: Opus 4.6 confidently wrote APIs that were removed after its
|
|
125
|
+
cutoff, including `anthropic.HUMAN_PROMPT` with `client.completions`. Stale code from both runs,
|
|
126
|
+
each valid for the version the model learned and broken for the pinned one:
|
|
127
|
+
`messages.create(temperature=...)` (anthropic 1.8), `hf_hub_download(resume_download=...)`,
|
|
128
|
+
`local_dir_use_symlinks=...`, `force_filename=...` and `proxies=...` (huggingface-hub 2.0), and
|
|
129
|
+
`client.beta.vector_stores` (openai 3.x).
|
|
130
|
+
|
|
131
|
+
<p align="center"><img src="https://raw.githubusercontent.com/MohammadHijjawi97/since-cutoff/main/docs/img/run-opus.svg" alt="since-cutoff run: Claude Opus 4.6" width="860"></p>
|
|
132
|
+
|
|
133
|
+
The notes it wrote (excerpt, verbatim):
|
|
134
|
+
|
|
135
|
+
```markdown
|
|
136
|
+
<!-- since-cutoff:start -->
|
|
137
|
+
## Library changes after the model's training cutoff
|
|
138
|
+
|
|
139
|
+
**anthropic 1.8.0**
|
|
140
|
+
- `temperature=...` was removed from `messages.create()` in anthropic 1.8.0. Omit the `temperature` parameter entirely; there is no replacement.
|
|
141
|
+
|
|
142
|
+
**huggingface-hub 2.0.0**
|
|
143
|
+
- `hf_hub_download(..., resume_download=True)`: The `resume_download` parameter was removed in huggingface-hub 2.0.0. Omit it; downloads resume automatically.
|
|
144
|
+
|
|
145
|
+
**openai 3.19.2**
|
|
146
|
+
- `client.beta.vector_stores` is removed in openai 3.19.2. Use `client.vector_stores` instead.
|
|
147
|
+
<!-- since-cutoff:end -->
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Small samples, two models, one project: treat it as a demonstration, not a benchmark. The full
|
|
151
|
+
report (every task, answer and type-checker error) is what `since-cutoff run` writes to
|
|
152
|
+
`.since-cutoff/report.md`. To reproduce: `cd examples/agent-app && since-cutoff run --model
|
|
153
|
+
claude-code:claude-haiku-4-5 --task-model claude-code:claude-opus-4-6`.
|
|
154
|
+
|
|
155
|
+
## Models
|
|
156
|
+
|
|
157
|
+
| `--model` | uses | needs |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| `claude-code` (default) | your Claude Code login (subscription or key), current model | the `claude` CLI |
|
|
160
|
+
| `claude-code:sonnet`, `claude-code:claude-haiku-4-5` | a specific Claude model | the `claude` CLI |
|
|
161
|
+
| `anthropic:<model>` | Anthropic API | `ANTHROPIC_API_KEY` |
|
|
162
|
+
| `openai:<model>` | OpenAI API | `OPENAI_API_KEY` |
|
|
163
|
+
| `openrouter:<vendor/model>` | OpenRouter | `OPENROUTER_API_KEY` |
|
|
164
|
+
| `deepseek:<model>` | DeepSeek API | `DEEPSEEK_API_KEY` |
|
|
165
|
+
| `ollama:<model>` | local Ollama | Ollama running |
|
|
166
|
+
| `openai-compatible:<model>` | any OpenAI-compatible server | `--base-url`, optional `OPENAI_API_KEY` |
|
|
167
|
+
|
|
168
|
+
Training cutoffs come from [models.dev](https://models.dev) (a snapshot is bundled for
|
|
169
|
+
offline use). `since-cutoff models sonnet` lists them; `--cutoff 2025-07` overrides.
|
|
170
|
+
|
|
171
|
+
## How it works
|
|
172
|
+
|
|
173
|
+
```mermaid
|
|
174
|
+
flowchart LR
|
|
175
|
+
L[lockfile] --> V[version at the model's cutoff<br/>vs your version]
|
|
176
|
+
V --> D[static API diff<br/>griffe]
|
|
177
|
+
D --> T[short tasks that need<br/>the changed API]
|
|
178
|
+
T --> M[model answers<br/>no tools, no docs]
|
|
179
|
+
M --> C[basedpyright against<br/>BOTH versions]
|
|
180
|
+
C --> N[notes, verified<br/>by the type checker]
|
|
181
|
+
N --> H[held-out tasks<br/>with vs without notes]
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
| outcome | meaning |
|
|
185
|
+
|---|---|
|
|
186
|
+
| **stale** | the code is valid for the version the model knew and invalid for yours, and the error involves an API that changed |
|
|
187
|
+
| **wrong** | invalid for your version, but not explained by a change (hallucinated or misused API) |
|
|
188
|
+
| **deprecated** | valid, but uses an API marked `@deprecated` in your version |
|
|
189
|
+
| **correct** | valid for your version and actually uses the changed API |
|
|
190
|
+
| untouched / off-task / invalid / error | not counted in any rate, and always reported |
|
|
191
|
+
|
|
192
|
+
Everything is scored by a type checker against the exact package versions, each in an isolated
|
|
193
|
+
environment with that package's own runtime dependencies. No LLM judges anything, and every
|
|
194
|
+
number traces back to `results.json`. Details: [docs/how-it-works.md](https://github.com/MohammadHijjawi97/since-cutoff/blob/main/docs/how-it-works.md).
|
|
195
|
+
|
|
196
|
+
## What it runs, sends and fetches
|
|
197
|
+
|
|
198
|
+
- **Fetches** package metadata and wheels from PyPI and model cutoffs from models.dev (a snapshot
|
|
199
|
+
is bundled for offline use).
|
|
200
|
+
- **Sends** prompts only to the model provider you choose (`run` only; `scan` sends nothing).
|
|
201
|
+
Prompts contain package names, versions, public signatures and docstrings of the changed APIs,
|
|
202
|
+
the generated tasks and, for notes, the model's own answer. Never your source code.
|
|
203
|
+
- **Runs** basedpyright locally on the model's answers. It never executes them.
|
|
204
|
+
- **Writes** `.since-cutoff/` in your project, its cache (`since-cutoff cache path`) and, with
|
|
205
|
+
`--apply`, one marked block in `AGENTS.md`/`CLAUDE.md`. No telemetry.
|
|
206
|
+
|
|
207
|
+
## Safe by design
|
|
208
|
+
|
|
209
|
+
- **Never executes code.** Package code is read statically (griffe with inspection off; only
|
|
210
|
+
`.py`/`.pyi` files are extracted, with path and size checks). Model-written code is only
|
|
211
|
+
type-checked.
|
|
212
|
+
- **Writes almost nothing.** Only `.since-cutoff/` (which ignores itself in git) and, with
|
|
213
|
+
`--apply`, one marked block in `AGENTS.md`/`CLAUDE.md`. Everything else in that file is
|
|
214
|
+
left byte-for-byte unchanged.
|
|
215
|
+
- **Stays on PyPI.** Git, path, workspace and private-index dependencies are never looked up on
|
|
216
|
+
public PyPI by name.
|
|
217
|
+
- **Local and cached.** No telemetry. PyPI data, diffs, tasks and answers are cached, so
|
|
218
|
+
re-runs are free and reproducible (`--fresh` asks the model again).
|
|
219
|
+
|
|
220
|
+
## Use in CI
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
since-cutoff run --quick --fail-on-stale --json > since-cutoff.json
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Exit codes: `0` ok, `1` error (including "no model answer could be scored"), `2` usage error,
|
|
227
|
+
`3` stale API use found with `--fail-on-stale`.
|
|
228
|
+
|
|
229
|
+
## Limitations
|
|
230
|
+
|
|
231
|
+
- Python only for now. TypeScript (`.d.ts` diffs, `tsc`) is next.
|
|
232
|
+
- A type checker sees wrong names, wrong parameters and PEP 702 deprecations. It cannot see
|
|
233
|
+
behaviour changes behind an unchanged signature, or deprecations that only warn at run time.
|
|
234
|
+
- Probes cover a ranked **sample** of the breaking changes (symbols your code already uses
|
|
235
|
+
first), not all of them.
|
|
236
|
+
- "The version the model saw" is the newest release on or before the cutoff date. Models know
|
|
237
|
+
recent releases less well, so real staleness can start earlier.
|
|
238
|
+
- Held-out tasks are paraphrases of the same change: they show that a note fixes *that* change,
|
|
239
|
+
not that the model got better in general.
|
|
240
|
+
|
|
241
|
+
## Related work
|
|
242
|
+
|
|
243
|
+
- [Context7](https://github.com/upstash/context7) and similar tools retrieve current docs at
|
|
244
|
+
answer time. since-cutoff is complementary: it measures what is actually wrong and keeps a
|
|
245
|
+
small, verified note in the repo.
|
|
246
|
+
- [cutoff](https://github.com/sandeepsirodia/cutoff) probes a library you maintain;
|
|
247
|
+
[postcut](https://github.com/justi/postcut) pastes changelogs since the cutoff.
|
|
248
|
+
- Built on [griffe](https://mkdocstrings.github.io/griffe/),
|
|
249
|
+
[basedpyright](https://github.com/DetachHead/basedpyright), [models.dev](https://models.dev)
|
|
250
|
+
and [rich](https://github.com/Textualize/rich).
|
|
251
|
+
|
|
252
|
+
## Contributing
|
|
253
|
+
|
|
254
|
+
Issues and pull requests are welcome; see [CONTRIBUTING.md](https://github.com/MohammadHijjawi97/since-cutoff/blob/main/CONTRIBUTING.md). The offline test
|
|
255
|
+
suite runs the whole pipeline with a toy library and a scripted model, so no API key is needed.
|
|
256
|
+
|
|
257
|
+
## Citation
|
|
258
|
+
|
|
259
|
+
If you use since-cutoff in research, please cite it (see [`CITATION.cff`](https://github.com/MohammadHijjawi97/since-cutoff/blob/main/CITATION.cff)).
|
|
260
|
+
|
|
261
|
+
## License
|
|
262
|
+
|
|
263
|
+
MIT © [Mohammad Hijjawi](https://github.com/MohammadHijjawi97)
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# since-cutoff
|
|
2
|
+
|
|
3
|
+
English | [简体中文](https://github.com/MohammadHijjawi97/since-cutoff/blob/main/README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
**Your coding agent learned your libraries before they changed.**
|
|
6
|
+
since-cutoff finds exactly which APIs of *your* dependency versions it gets wrong, and fixes
|
|
7
|
+
them with a small AGENTS.md note that is checked by a type checker, not by another LLM.
|
|
8
|
+
|
|
9
|
+
[](https://github.com/MohammadHijjawi97/since-cutoff/actions/workflows/ci.yml)
|
|
10
|
+
[](https://pypi.org/project/since-cutoff/)
|
|
11
|
+

|
|
12
|
+

|
|
13
|
+
[](https://github.com/MohammadHijjawi97/since-cutoff/blob/main/LICENSE)
|
|
14
|
+
|
|
15
|
+
<p align="center"><img src="https://raw.githubusercontent.com/MohammadHijjawi97/since-cutoff/main/docs/img/run.svg" alt="since-cutoff run: Claude Haiku 4.5 on a real project" width="860"></p>
|
|
16
|
+
|
|
17
|
+
## The problem
|
|
18
|
+
|
|
19
|
+
Every model has a training cutoff. Your lockfile does not. A few real examples for a model
|
|
20
|
+
with a July 2025 cutoff (Claude Sonnet 4.5) and current releases, found by `since-cutoff scan`:
|
|
21
|
+
|
|
22
|
+
| library | version the model saw | your version | what breaks |
|
|
23
|
+
|---|---|---|---|
|
|
24
|
+
| anthropic | 0.60.0 | 1.8.0 | `messages.create(temperature=..., top_p=..., top_k=...)` no longer accepted |
|
|
25
|
+
| huggingface-hub | 0.34.3 | 2.0.0 | `hf_hub_download(resume_download=..., force_filename=..., local_dir_use_symlinks=...)` removed |
|
|
26
|
+
| langchain-core | 0.3.72 | 1.6.5 | `retriever.get_relevant_documents()`, `llm.predict()` removed |
|
|
27
|
+
| openai | 1.98.0 | 3.19.2 | 26 breaking changes, 6 new deprecations |
|
|
28
|
+
|
|
29
|
+
For that sample project, 7 of 9 dependencies had changed their public API after the cutoff
|
|
30
|
+
(the static diff flags 483 changes; many are internals, which the task writer skips). An agent that learned the old API writes code that fails at
|
|
31
|
+
import or call time, or, worse, still runs because the old path is only deprecated.
|
|
32
|
+
|
|
33
|
+
<p align="center"><img src="https://raw.githubusercontent.com/MohammadHijjawi97/since-cutoff/main/docs/img/scan.svg" alt="since-cutoff scan output" width="820"></p>
|
|
34
|
+
|
|
35
|
+
Documentation tools paste whole docs into the context and hope. since-cutoff **measures**
|
|
36
|
+
which of those changes your model actually gets wrong, writes **only** the notes that are
|
|
37
|
+
needed, and **proves** on held-out tasks that the notes fix them.
|
|
38
|
+
|
|
39
|
+
## Features
|
|
40
|
+
|
|
41
|
+
- **`scan`**: for every dependency, the version your model saw at its training cutoff vs. the one
|
|
42
|
+
you pin, and a static diff of what broke in between (no model calls, no API key).
|
|
43
|
+
- **`run`**: probes the model with short tasks that need the changed APIs and scores its code with
|
|
44
|
+
a type checker against *both* versions: stale, wrong, deprecated or correct.
|
|
45
|
+
- **Verified fixes**: one-line AGENTS.md / CLAUDE.md notes, kept only if their example
|
|
46
|
+
type-checks against your exact version, and re-tested on held-out tasks.
|
|
47
|
+
- **Works where you are**: Claude Code plugin and skill, or any of Anthropic, OpenAI, OpenRouter,
|
|
48
|
+
DeepSeek, Ollama and OpenAI-compatible servers.
|
|
49
|
+
- **Every lockfile**: uv, Poetry, PDM, pylock, Pipenv, requirements files, or a `.venv`.
|
|
50
|
+
- **Safe and reproducible**: never runs package or model-written code; everything is cached;
|
|
51
|
+
full JSON and Markdown reports.
|
|
52
|
+
|
|
53
|
+
## Quick start
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# list API changes since your model's cutoff (fast, no model calls)
|
|
57
|
+
uvx since-cutoff scan
|
|
58
|
+
|
|
59
|
+
# probe the model, write verified notes, and apply them to AGENTS.md
|
|
60
|
+
uvx since-cutoff run --apply
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Or install it with `pipx install since-cutoff` (or `pip install since-cutoff`) and run
|
|
64
|
+
`since-cutoff`. Run it from your project root (anything with `uv.lock`, `poetry.lock`,
|
|
65
|
+
`pdm.lock`, `pylock.toml`, `Pipfile.lock`, `requirements*.txt`, `pyproject.toml` or a `.venv`).
|
|
66
|
+
|
|
67
|
+
### In Claude Code
|
|
68
|
+
|
|
69
|
+
```text
|
|
70
|
+
/plugin marketplace add MohammadHijjawi97/since-cutoff
|
|
71
|
+
/plugin install since-cutoff@since-cutoff
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Then ask Claude to "check which of our dependencies you are out of date on", or run
|
|
75
|
+
`/since-cutoff:since-cutoff`. The skill runs the CLI; the measuring itself is done by a fresh,
|
|
76
|
+
tool-less copy of the model, so the agent cannot grade itself.
|
|
77
|
+
|
|
78
|
+
## A real run
|
|
79
|
+
|
|
80
|
+
Two Claude models on the 9-dependency sample project in
|
|
81
|
+
[`examples/agent-app`](https://github.com/MohammadHijjawi97/since-cutoff/tree/main/examples/agent-app),
|
|
82
|
+
with Claude Opus 4.6 writing the tasks and notes:
|
|
83
|
+
|
|
84
|
+
| | Claude Haiku 4.5 | Claude Opus 4.6 |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| training cutoff | Feb 2025 | May 2025 |
|
|
87
|
+
| API changes probed | 20 | 16 |
|
|
88
|
+
| **stale** / wrong / deprecated / correct | **5** / 1 / 2 / 12 | **7** / 0 / 3 / 6 |
|
|
89
|
+
| libraries with stale use | 3 of 5 probed | 2 of 4 probed |
|
|
90
|
+
| notes written (type-checker verified) | 8 (7), about 391 tokens | 10 (7), about 437 tokens |
|
|
91
|
+
| **held-out correct, without -> with notes** | **14% -> 57%** (14 pairs) | **5% -> 65%** (20 pairs) |
|
|
92
|
+
| previously-correct APIs after notes | 6/6 still correct | 6/6 still correct |
|
|
93
|
+
|
|
94
|
+
The stronger model is not safer: Opus 4.6 confidently wrote APIs that were removed after its
|
|
95
|
+
cutoff, including `anthropic.HUMAN_PROMPT` with `client.completions`. Stale code from both runs,
|
|
96
|
+
each valid for the version the model learned and broken for the pinned one:
|
|
97
|
+
`messages.create(temperature=...)` (anthropic 1.8), `hf_hub_download(resume_download=...)`,
|
|
98
|
+
`local_dir_use_symlinks=...`, `force_filename=...` and `proxies=...` (huggingface-hub 2.0), and
|
|
99
|
+
`client.beta.vector_stores` (openai 3.x).
|
|
100
|
+
|
|
101
|
+
<p align="center"><img src="https://raw.githubusercontent.com/MohammadHijjawi97/since-cutoff/main/docs/img/run-opus.svg" alt="since-cutoff run: Claude Opus 4.6" width="860"></p>
|
|
102
|
+
|
|
103
|
+
The notes it wrote (excerpt, verbatim):
|
|
104
|
+
|
|
105
|
+
```markdown
|
|
106
|
+
<!-- since-cutoff:start -->
|
|
107
|
+
## Library changes after the model's training cutoff
|
|
108
|
+
|
|
109
|
+
**anthropic 1.8.0**
|
|
110
|
+
- `temperature=...` was removed from `messages.create()` in anthropic 1.8.0. Omit the `temperature` parameter entirely; there is no replacement.
|
|
111
|
+
|
|
112
|
+
**huggingface-hub 2.0.0**
|
|
113
|
+
- `hf_hub_download(..., resume_download=True)`: The `resume_download` parameter was removed in huggingface-hub 2.0.0. Omit it; downloads resume automatically.
|
|
114
|
+
|
|
115
|
+
**openai 3.19.2**
|
|
116
|
+
- `client.beta.vector_stores` is removed in openai 3.19.2. Use `client.vector_stores` instead.
|
|
117
|
+
<!-- since-cutoff:end -->
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Small samples, two models, one project: treat it as a demonstration, not a benchmark. The full
|
|
121
|
+
report (every task, answer and type-checker error) is what `since-cutoff run` writes to
|
|
122
|
+
`.since-cutoff/report.md`. To reproduce: `cd examples/agent-app && since-cutoff run --model
|
|
123
|
+
claude-code:claude-haiku-4-5 --task-model claude-code:claude-opus-4-6`.
|
|
124
|
+
|
|
125
|
+
## Models
|
|
126
|
+
|
|
127
|
+
| `--model` | uses | needs |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| `claude-code` (default) | your Claude Code login (subscription or key), current model | the `claude` CLI |
|
|
130
|
+
| `claude-code:sonnet`, `claude-code:claude-haiku-4-5` | a specific Claude model | the `claude` CLI |
|
|
131
|
+
| `anthropic:<model>` | Anthropic API | `ANTHROPIC_API_KEY` |
|
|
132
|
+
| `openai:<model>` | OpenAI API | `OPENAI_API_KEY` |
|
|
133
|
+
| `openrouter:<vendor/model>` | OpenRouter | `OPENROUTER_API_KEY` |
|
|
134
|
+
| `deepseek:<model>` | DeepSeek API | `DEEPSEEK_API_KEY` |
|
|
135
|
+
| `ollama:<model>` | local Ollama | Ollama running |
|
|
136
|
+
| `openai-compatible:<model>` | any OpenAI-compatible server | `--base-url`, optional `OPENAI_API_KEY` |
|
|
137
|
+
|
|
138
|
+
Training cutoffs come from [models.dev](https://models.dev) (a snapshot is bundled for
|
|
139
|
+
offline use). `since-cutoff models sonnet` lists them; `--cutoff 2025-07` overrides.
|
|
140
|
+
|
|
141
|
+
## How it works
|
|
142
|
+
|
|
143
|
+
```mermaid
|
|
144
|
+
flowchart LR
|
|
145
|
+
L[lockfile] --> V[version at the model's cutoff<br/>vs your version]
|
|
146
|
+
V --> D[static API diff<br/>griffe]
|
|
147
|
+
D --> T[short tasks that need<br/>the changed API]
|
|
148
|
+
T --> M[model answers<br/>no tools, no docs]
|
|
149
|
+
M --> C[basedpyright against<br/>BOTH versions]
|
|
150
|
+
C --> N[notes, verified<br/>by the type checker]
|
|
151
|
+
N --> H[held-out tasks<br/>with vs without notes]
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
| outcome | meaning |
|
|
155
|
+
|---|---|
|
|
156
|
+
| **stale** | the code is valid for the version the model knew and invalid for yours, and the error involves an API that changed |
|
|
157
|
+
| **wrong** | invalid for your version, but not explained by a change (hallucinated or misused API) |
|
|
158
|
+
| **deprecated** | valid, but uses an API marked `@deprecated` in your version |
|
|
159
|
+
| **correct** | valid for your version and actually uses the changed API |
|
|
160
|
+
| untouched / off-task / invalid / error | not counted in any rate, and always reported |
|
|
161
|
+
|
|
162
|
+
Everything is scored by a type checker against the exact package versions, each in an isolated
|
|
163
|
+
environment with that package's own runtime dependencies. No LLM judges anything, and every
|
|
164
|
+
number traces back to `results.json`. Details: [docs/how-it-works.md](https://github.com/MohammadHijjawi97/since-cutoff/blob/main/docs/how-it-works.md).
|
|
165
|
+
|
|
166
|
+
## What it runs, sends and fetches
|
|
167
|
+
|
|
168
|
+
- **Fetches** package metadata and wheels from PyPI and model cutoffs from models.dev (a snapshot
|
|
169
|
+
is bundled for offline use).
|
|
170
|
+
- **Sends** prompts only to the model provider you choose (`run` only; `scan` sends nothing).
|
|
171
|
+
Prompts contain package names, versions, public signatures and docstrings of the changed APIs,
|
|
172
|
+
the generated tasks and, for notes, the model's own answer. Never your source code.
|
|
173
|
+
- **Runs** basedpyright locally on the model's answers. It never executes them.
|
|
174
|
+
- **Writes** `.since-cutoff/` in your project, its cache (`since-cutoff cache path`) and, with
|
|
175
|
+
`--apply`, one marked block in `AGENTS.md`/`CLAUDE.md`. No telemetry.
|
|
176
|
+
|
|
177
|
+
## Safe by design
|
|
178
|
+
|
|
179
|
+
- **Never executes code.** Package code is read statically (griffe with inspection off; only
|
|
180
|
+
`.py`/`.pyi` files are extracted, with path and size checks). Model-written code is only
|
|
181
|
+
type-checked.
|
|
182
|
+
- **Writes almost nothing.** Only `.since-cutoff/` (which ignores itself in git) and, with
|
|
183
|
+
`--apply`, one marked block in `AGENTS.md`/`CLAUDE.md`. Everything else in that file is
|
|
184
|
+
left byte-for-byte unchanged.
|
|
185
|
+
- **Stays on PyPI.** Git, path, workspace and private-index dependencies are never looked up on
|
|
186
|
+
public PyPI by name.
|
|
187
|
+
- **Local and cached.** No telemetry. PyPI data, diffs, tasks and answers are cached, so
|
|
188
|
+
re-runs are free and reproducible (`--fresh` asks the model again).
|
|
189
|
+
|
|
190
|
+
## Use in CI
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
since-cutoff run --quick --fail-on-stale --json > since-cutoff.json
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Exit codes: `0` ok, `1` error (including "no model answer could be scored"), `2` usage error,
|
|
197
|
+
`3` stale API use found with `--fail-on-stale`.
|
|
198
|
+
|
|
199
|
+
## Limitations
|
|
200
|
+
|
|
201
|
+
- Python only for now. TypeScript (`.d.ts` diffs, `tsc`) is next.
|
|
202
|
+
- A type checker sees wrong names, wrong parameters and PEP 702 deprecations. It cannot see
|
|
203
|
+
behaviour changes behind an unchanged signature, or deprecations that only warn at run time.
|
|
204
|
+
- Probes cover a ranked **sample** of the breaking changes (symbols your code already uses
|
|
205
|
+
first), not all of them.
|
|
206
|
+
- "The version the model saw" is the newest release on or before the cutoff date. Models know
|
|
207
|
+
recent releases less well, so real staleness can start earlier.
|
|
208
|
+
- Held-out tasks are paraphrases of the same change: they show that a note fixes *that* change,
|
|
209
|
+
not that the model got better in general.
|
|
210
|
+
|
|
211
|
+
## Related work
|
|
212
|
+
|
|
213
|
+
- [Context7](https://github.com/upstash/context7) and similar tools retrieve current docs at
|
|
214
|
+
answer time. since-cutoff is complementary: it measures what is actually wrong and keeps a
|
|
215
|
+
small, verified note in the repo.
|
|
216
|
+
- [cutoff](https://github.com/sandeepsirodia/cutoff) probes a library you maintain;
|
|
217
|
+
[postcut](https://github.com/justi/postcut) pastes changelogs since the cutoff.
|
|
218
|
+
- Built on [griffe](https://mkdocstrings.github.io/griffe/),
|
|
219
|
+
[basedpyright](https://github.com/DetachHead/basedpyright), [models.dev](https://models.dev)
|
|
220
|
+
and [rich](https://github.com/Textualize/rich).
|
|
221
|
+
|
|
222
|
+
## Contributing
|
|
223
|
+
|
|
224
|
+
Issues and pull requests are welcome; see [CONTRIBUTING.md](https://github.com/MohammadHijjawi97/since-cutoff/blob/main/CONTRIBUTING.md). The offline test
|
|
225
|
+
suite runs the whole pipeline with a toy library and a scripted model, so no API key is needed.
|
|
226
|
+
|
|
227
|
+
## Citation
|
|
228
|
+
|
|
229
|
+
If you use since-cutoff in research, please cite it (see [`CITATION.cff`](https://github.com/MohammadHijjawi97/since-cutoff/blob/main/CITATION.cff)).
|
|
230
|
+
|
|
231
|
+
## License
|
|
232
|
+
|
|
233
|
+
MIT © [Mohammad Hijjawi](https://github.com/MohammadHijjawi97)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.25"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "since-cutoff"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Find which of your exact dependency versions your coding agent writes wrong, and fix it with a small, verified AGENTS.md note."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
authors = [{ name = "Mohammad Hijjawi", email = "mohammad.hijjawi1997@gmail.com" }]
|
|
14
|
+
keywords = [
|
|
15
|
+
"llm",
|
|
16
|
+
"coding-agents",
|
|
17
|
+
"claude-code",
|
|
18
|
+
"codex",
|
|
19
|
+
"agents-md",
|
|
20
|
+
"knowledge-cutoff",
|
|
21
|
+
"api-changes",
|
|
22
|
+
"static-analysis",
|
|
23
|
+
]
|
|
24
|
+
classifiers = [
|
|
25
|
+
"Development Status :: 4 - Beta",
|
|
26
|
+
"Environment :: Console",
|
|
27
|
+
"Intended Audience :: Developers",
|
|
28
|
+
"Operating System :: OS Independent",
|
|
29
|
+
"Programming Language :: Python :: 3",
|
|
30
|
+
"Programming Language :: Python :: 3.10",
|
|
31
|
+
"Programming Language :: Python :: 3.11",
|
|
32
|
+
"Programming Language :: Python :: 3.12",
|
|
33
|
+
"Programming Language :: Python :: 3.13",
|
|
34
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
35
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
36
|
+
]
|
|
37
|
+
dependencies = [
|
|
38
|
+
"basedpyright>=1.20",
|
|
39
|
+
"griffe>=1.14",
|
|
40
|
+
"packaging>=23.0",
|
|
41
|
+
"rich>=13.7",
|
|
42
|
+
"tomli>=2.0; python_version < '3.11'",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[project.urls]
|
|
46
|
+
Homepage = "https://github.com/MohammadHijjawi97/since-cutoff"
|
|
47
|
+
Issues = "https://github.com/MohammadHijjawi97/since-cutoff/issues"
|
|
48
|
+
Changelog = "https://github.com/MohammadHijjawi97/since-cutoff/blob/main/CHANGELOG.md"
|
|
49
|
+
|
|
50
|
+
[project.scripts]
|
|
51
|
+
since-cutoff = "since_cutoff.cli:main"
|
|
52
|
+
|
|
53
|
+
[dependency-groups]
|
|
54
|
+
dev = ["pytest>=8", "pytest-cov>=5", "ruff>=0.6", "mypy>=1.11"]
|
|
55
|
+
|
|
56
|
+
[tool.hatch.version]
|
|
57
|
+
path = "src/since_cutoff/__init__.py"
|
|
58
|
+
|
|
59
|
+
[tool.hatch.build.targets.wheel]
|
|
60
|
+
packages = ["src/since_cutoff"]
|
|
61
|
+
|
|
62
|
+
[tool.hatch.build.targets.sdist]
|
|
63
|
+
include = ["src", "tests", "README.md", "LICENSE", "CHANGELOG.md"]
|
|
64
|
+
|
|
65
|
+
[tool.pytest.ini_options]
|
|
66
|
+
testpaths = ["tests"]
|
|
67
|
+
addopts = "-ra --strict-markers"
|
|
68
|
+
markers = [
|
|
69
|
+
"network: needs internet access (PyPI, models.dev)",
|
|
70
|
+
"pyright: runs the basedpyright type checker",
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
[tool.ruff]
|
|
74
|
+
line-length = 100
|
|
75
|
+
target-version = "py310"
|
|
76
|
+
src = ["src", "tests"]
|
|
77
|
+
|
|
78
|
+
[tool.ruff.lint]
|
|
79
|
+
select = ["E", "F", "W", "I", "B", "UP", "SIM", "RUF", "PTH", "C4"]
|
|
80
|
+
ignore = ["E501", "RUF001", "RUF002", "RUF003"]
|
|
81
|
+
|
|
82
|
+
[tool.mypy]
|
|
83
|
+
python_version = "3.10"
|
|
84
|
+
strict = true
|
|
85
|
+
warn_unused_ignores = true
|
|
86
|
+
files = ["src/since_cutoff"]
|
|
87
|
+
|
|
88
|
+
[[tool.mypy.overrides]]
|
|
89
|
+
module = ["griffe.*", "tomli", "basedpyright"]
|
|
90
|
+
ignore_missing_imports = true
|