@aidriftline/driftline 0.2.0 → 0.2.1
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.
- package/package.json +1 -1
- package/registry/models.json +1 -1
- package/src/index.js +3 -2
- package/src/mcp.js +4 -2
- package/src/registry.js +9 -0
- package/src/report.js +8 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aidriftline/driftline",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Finds OpenAI, Anthropic and Google model IDs in your code and warns before they are retired. CLI, GitHub Action and MCP server.",
|
|
5
5
|
"mcpName": "io.github.AI-Driftline/driftline",
|
|
6
6
|
"type": "module",
|
package/registry/models.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"checked": "2026-11-
|
|
2
|
+
"checked": "2026-11-13",
|
|
3
3
|
"note": "Hand-maintained from the official deprecation pages. Re-check before each release. Entries with a 'kind' other than text are scanned and reported but never drift-tested. gpt-4-1106-preview is listed on OpenAI's page under both 2026-03-26 and 2026-10-23; the later date is used so builds are not failed on a conflicting record. Entries with routes: true keep answering after the date, but the provider sends the requests to the replacement model (Google calls this automatic routing); the 'retires' date is the day routing starts. gemini-3.5-flash and gemini-3.7-flash already route to newer models with no date given, so they are left out. gemini-3.1-flash-live-preview was removed on 2026-11-14: Google now lists it with no shutdown date.",
|
|
4
4
|
"sources": {
|
|
5
5
|
"openai": "https://developers.openai.com/api/docs/deprecations",
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// GitHub Action entry point.
|
|
3
|
-
import { replacementText, replacementChange, loadBestRegistry } from "./registry.js";
|
|
3
|
+
import { replacementText, replacementChange, loadBestRegistry, reviewText } from "./registry.js";
|
|
4
4
|
import { migrationFor } from "./migration.js";
|
|
5
5
|
import { resolve } from "node:path";
|
|
6
6
|
import { statSync } from "node:fs";
|
|
@@ -65,7 +65,8 @@ async function main() {
|
|
|
65
65
|
? `${f.id} is past its earliest shutdown date (${f.entry.retires}) and may stop working at any time.`
|
|
66
66
|
: `${f.id} ${STATUS_TEXT[f.status]} (${f.entry.retires}).`;
|
|
67
67
|
const routedMore = f.entry.routes && migrationFor(f.entry)?.change === "params" ? ` Before then, apply the request changes the provider lists: ${migrationFor(f.entry).note}` : "";
|
|
68
|
-
|
|
68
|
+
const review = f.entry.under_review ? ` ${reviewText(f.entry)}` : "";
|
|
69
|
+
annotate(level, text + repl + more + routedMore + review, f.file, f.line);
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
// The report contains model answers; stop GitHub from reading any "::command::" lines inside it.
|
package/src/mcp.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { readFileSync, statSync } from "node:fs";
|
|
7
7
|
import { resolve } from "node:path";
|
|
8
8
|
import { createInterface } from "node:readline";
|
|
9
|
-
import { loadBestRegistry, classify, normalizeId, replacementChange, replacementText } from "./registry.js";
|
|
9
|
+
import { loadBestRegistry, classify, normalizeId, replacementChange, replacementText, reviewText } from "./registry.js";
|
|
10
10
|
import { migrationFor } from "./migration.js";
|
|
11
11
|
import { scanDirectory } from "./scan.js";
|
|
12
12
|
|
|
@@ -47,7 +47,8 @@ function describe(rawId, reg, today) {
|
|
|
47
47
|
...(m.previous_replacement ? { previous_replacement: m.previous_replacement, replacement_changed: m.replacement_changed } : {}), routes: !!m.routes, ...(m.aliasOf ? { alias_of: m.aliasOf } : {}), page: page(m.aliasOf || m.id),
|
|
48
48
|
} : {}),
|
|
49
49
|
...(m && migrationFor(m) ? { migration: migrationFor(m) } : {}),
|
|
50
|
-
|
|
50
|
+
...(m?.under_review ? { under_review: m.under_review } : {}),
|
|
51
|
+
advice: (m?.under_review ? reviewText(m) + " " : "") + advice(c, today),
|
|
51
52
|
};
|
|
52
53
|
}
|
|
53
54
|
|
|
@@ -62,6 +63,7 @@ const MODEL_RESULT = {
|
|
|
62
63
|
qualifier: { type: "string", enum: ["exact", "earliest", "not-before"], description: "exact date, earliest possible shutdown (Google), or not before this date (Anthropic)" },
|
|
63
64
|
days_left: { type: "number", description: "Days until the date; 0 or negative when it has passed" },
|
|
64
65
|
replacement: { type: ["string", "null"] },
|
|
66
|
+
under_review: { type: "object", description: "Present when the provider's page no longer matches this entry and Driftline is updating it: since (date), what (date, replacement or listing), page (source to check)", properties: { since: { type: "string" }, what: { type: "string" }, page: { type: "string" } } },
|
|
65
67
|
replacement_alt: { type: "string", description: "A second replacement the provider names (\"X or Y\"); which one fits depends on how the model is used" },
|
|
66
68
|
previous_replacement: { type: "string", description: "The replacement the provider recommended before it changed its advice" },
|
|
67
69
|
replacement_changed: { type: "string", description: "YYYY-MM-DD: when the change was seen on the provider's page" },
|
package/src/registry.js
CHANGED
|
@@ -103,6 +103,15 @@ export const isTextModel = (entry) => !entry?.kind || entry.kind === "text";
|
|
|
103
103
|
export const replacementText = (entry) => entry?.replacement
|
|
104
104
|
? entry.replacement + (entry.replacement_alt ? ` or ${entry.replacement_alt}` : "") + (entry.replacement_note ? ` (${entry.replacement_note})` : "") : "";
|
|
105
105
|
|
|
106
|
+
// The live list marks an entry under_review when the provider's page no longer matches it (found by the page check
|
|
107
|
+
// every 6 hours) and the registry hasn't been fixed yet. Say so instead of presenting the stored date as fact.
|
|
108
|
+
const WHO_NAME = { openai: "OpenAI", anthropic: "Anthropic", google: "Google" };
|
|
109
|
+
export function reviewText(entry) {
|
|
110
|
+
const r = entry?.under_review;
|
|
111
|
+
if (!r) return "";
|
|
112
|
+
return `Under review: ${WHO_NAME[entry.provider] || entry.provider}'s page no longer matches the ${r.what || "entry"} shown here (since ${r.since}); Driftline is updating it. Check ${r.page || "the provider's deprecation page"} before acting on this ${r.what === "replacement" ? "replacement" : "date"}.`;
|
|
113
|
+
}
|
|
114
|
+
|
|
106
115
|
// Providers sometimes change the recommended replacement after announcing a retirement. The registry keeps
|
|
107
116
|
// the old one and the date we saw the change, so reports can say so instead of silently switching targets.
|
|
108
117
|
export const RECENT_CHANGE_DAYS = 120;
|
package/src/report.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { replacementChange } from "./registry.js";
|
|
1
|
+
import { replacementChange, reviewText } from "./registry.js";
|
|
2
2
|
import { migrationFor, MIGRATION_LABEL } from "./migration.js";
|
|
3
3
|
|
|
4
4
|
const STATUS_LABEL = {
|
|
@@ -68,6 +68,7 @@ export function renderReport({ findings, drift, registry, notices = [], answers
|
|
|
68
68
|
: `Found ${byModel.size} model ID${byModel.size === 1 ? "" : "s"}. None are scheduled for retirement.`);
|
|
69
69
|
lines.push("", "| Model | Status | Retirement | Replacement | Change needed | Where |", "|---|---|---|---|---|---|");
|
|
70
70
|
const migNotes = new Map(); // note → { n, ids, guide } for the list under the table
|
|
71
|
+
const underReview = []; // rows whose provider page changed and the registry isn't fixed yet
|
|
71
72
|
const order = ["retired", "at-risk", "deprecated", "active", "unknown"];
|
|
72
73
|
const all = [...byModel.values()].sort((a, b) => order.indexOf(a.status) - order.indexOf(b.status));
|
|
73
74
|
const rows = all.slice(0, MAX_ROWS); // flagged models sort first, so only unknown/active rows get cut
|
|
@@ -83,9 +84,14 @@ export function renderReport({ findings, drift, registry, notices = [], answers
|
|
|
83
84
|
}
|
|
84
85
|
const places = r.places.slice(0, 3).join(", ") + (r.places.length > 3 ? ` +${r.places.length - 3} more` : "");
|
|
85
86
|
const docsOnly = r.places.length && byModel.get(r.id).docs === r.places.length ? " · docs only" : "";
|
|
86
|
-
|
|
87
|
+
if (r.entry?.under_review) underReview.push(r);
|
|
88
|
+
lines.push(`| ${code(r.id)}${r.entry?.aliasOf ? " · alias of " + code(r.entry.aliasOf) : ""}${r.entry?.kind && r.entry.kind !== "text" ? " · " + r.entry.kind : ""} | ${label(r)}${docsOnly}${r.entry?.under_review ? " · 🔍 under review" : ""} | ${esc(when(r))} | ${r.entry?.replacement ? code(r.entry.replacement) + (r.entry.replacement_alt ? " or " + code(r.entry.replacement_alt) : "") + (r.entry.replacement_note ? " (" + esc(r.entry.replacement_note) + ")" : "") + changed(r.entry, today) : ""} | ${changeCell} | ${places} |`);
|
|
87
89
|
}
|
|
88
90
|
if (all.length > rows.length) lines.push("", `_${all.length - rows.length} more model ID(s) with no retirement listed are not shown._`);
|
|
91
|
+
if (underReview.length) {
|
|
92
|
+
lines.push("", "**🔍 Under review:**", "");
|
|
93
|
+
for (const r of underReview) lines.push(`- ${code(r.id)}: ${esc(reviewText(r.entry))}`);
|
|
94
|
+
}
|
|
89
95
|
if (migNotes.size) {
|
|
90
96
|
lines.push("", "**More than a rename:**", "");
|
|
91
97
|
for (const [note, m] of migNotes) lines.push(`${m.n}. ${m.ids.map(code).join(", ")}: ${esc(note)}${m.guide ? ` ([provider guide](${m.guide}))` : ""}`);
|