@aidriftline/driftline 0.1.16 → 0.1.17
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 +12 -0
- package/src/cli.js +1 -1
- package/src/mcp.js +4 -3
- package/src/registry.js +9 -3
- package/src/report.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aidriftline/driftline",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
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
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"retires": "2027-02-10",
|
|
14
14
|
"qualifier": "exact",
|
|
15
15
|
"replacement": "gpt-image-2.6-sunburst",
|
|
16
|
+
"replacement_alt": "gpt-image-2.6-flare",
|
|
16
17
|
"kind": "image",
|
|
17
18
|
"announced": "2026-11-10"
|
|
18
19
|
},
|
|
@@ -22,6 +23,7 @@
|
|
|
22
23
|
"retires": "2027-02-10",
|
|
23
24
|
"qualifier": "exact",
|
|
24
25
|
"replacement": "gpt-image-2.6-sunburst",
|
|
26
|
+
"replacement_alt": "gpt-image-2.6-flare",
|
|
25
27
|
"kind": "image",
|
|
26
28
|
"announced": "2026-11-10"
|
|
27
29
|
},
|
|
@@ -1072,6 +1074,7 @@
|
|
|
1072
1074
|
"retires": "2027-02-26",
|
|
1073
1075
|
"qualifier": "exact",
|
|
1074
1076
|
"replacement": "gpt-live-transcribe",
|
|
1077
|
+
"replacement_alt": "gpt-transcribe",
|
|
1075
1078
|
"kind": "audio"
|
|
1076
1079
|
},
|
|
1077
1080
|
{
|
|
@@ -1080,6 +1083,7 @@
|
|
|
1080
1083
|
"retires": "2027-02-26",
|
|
1081
1084
|
"qualifier": "exact",
|
|
1082
1085
|
"replacement": "gpt-live-transcribe",
|
|
1086
|
+
"replacement_alt": "gpt-transcribe",
|
|
1083
1087
|
"kind": "audio"
|
|
1084
1088
|
},
|
|
1085
1089
|
{
|
|
@@ -1088,6 +1092,7 @@
|
|
|
1088
1092
|
"retires": "2027-02-26",
|
|
1089
1093
|
"qualifier": "exact",
|
|
1090
1094
|
"replacement": "gpt-live-transcribe",
|
|
1095
|
+
"replacement_alt": "gpt-transcribe",
|
|
1091
1096
|
"kind": "audio"
|
|
1092
1097
|
},
|
|
1093
1098
|
{
|
|
@@ -1096,6 +1101,7 @@
|
|
|
1096
1101
|
"retires": "2027-02-26",
|
|
1097
1102
|
"qualifier": "exact",
|
|
1098
1103
|
"replacement": "gpt-live-transcribe",
|
|
1104
|
+
"replacement_alt": "gpt-transcribe",
|
|
1099
1105
|
"kind": "audio"
|
|
1100
1106
|
},
|
|
1101
1107
|
{
|
|
@@ -1104,6 +1110,7 @@
|
|
|
1104
1110
|
"retires": "2027-02-26",
|
|
1105
1111
|
"qualifier": "exact",
|
|
1106
1112
|
"replacement": "gpt-live-transcribe",
|
|
1113
|
+
"replacement_alt": "gpt-transcribe",
|
|
1107
1114
|
"kind": "audio"
|
|
1108
1115
|
},
|
|
1109
1116
|
{
|
|
@@ -1312,6 +1319,7 @@
|
|
|
1312
1319
|
"retires": "2026-10-23",
|
|
1313
1320
|
"qualifier": "exact",
|
|
1314
1321
|
"replacement": "gpt-image-2.5-sunburst",
|
|
1322
|
+
"replacement_alt": "gpt-image-2.5-flare",
|
|
1315
1323
|
"kind": "image"
|
|
1316
1324
|
},
|
|
1317
1325
|
{
|
|
@@ -1320,6 +1328,7 @@
|
|
|
1320
1328
|
"retires": "2026-12-01",
|
|
1321
1329
|
"qualifier": "exact",
|
|
1322
1330
|
"replacement": "gpt-image-2.6-sunburst",
|
|
1331
|
+
"replacement_alt": "gpt-image-2.6-flare",
|
|
1323
1332
|
"kind": "image"
|
|
1324
1333
|
},
|
|
1325
1334
|
{
|
|
@@ -1328,6 +1337,7 @@
|
|
|
1328
1337
|
"retires": "2026-12-01",
|
|
1329
1338
|
"qualifier": "exact",
|
|
1330
1339
|
"replacement": "gpt-image-2.6-sunburst",
|
|
1340
|
+
"replacement_alt": "gpt-image-2.6-flare",
|
|
1331
1341
|
"kind": "image"
|
|
1332
1342
|
},
|
|
1333
1343
|
{
|
|
@@ -1336,6 +1346,7 @@
|
|
|
1336
1346
|
"retires": "2026-12-01",
|
|
1337
1347
|
"qualifier": "exact",
|
|
1338
1348
|
"replacement": "gpt-image-2.6-sunburst",
|
|
1349
|
+
"replacement_alt": "gpt-image-2.6-flare",
|
|
1339
1350
|
"kind": "image"
|
|
1340
1351
|
},
|
|
1341
1352
|
{
|
|
@@ -1344,6 +1355,7 @@
|
|
|
1344
1355
|
"retires": "2026-12-01",
|
|
1345
1356
|
"qualifier": "exact",
|
|
1346
1357
|
"replacement": "gpt-image-2.6-sunburst",
|
|
1358
|
+
"replacement_alt": "gpt-image-2.6-flare",
|
|
1347
1359
|
"kind": "image"
|
|
1348
1360
|
},
|
|
1349
1361
|
{
|
package/src/cli.js
CHANGED
|
@@ -81,7 +81,7 @@ if (cmd === "-h" || cmd === "--help" || cmd === "help") {
|
|
|
81
81
|
const pick = (f) => ({
|
|
82
82
|
id: f.id, file: f.file, line: f.line, status: f.status, docOnly: !!f.doc,
|
|
83
83
|
provider: f.entry?.provider, retires: f.entry?.retires, qualifier: f.entry?.qualifier,
|
|
84
|
-
replacement: f.entry?.replacement, migration: migrationFor(f.entry) || undefined, routes: !!f.entry?.routes, aliasOf: f.entry?.aliasOf,
|
|
84
|
+
replacement: f.entry?.replacement, replacement_alt: f.entry?.replacement_alt, migration: migrationFor(f.entry) || undefined, routes: !!f.entry?.routes, aliasOf: f.entry?.aliasOf,
|
|
85
85
|
});
|
|
86
86
|
console.log(JSON.stringify({ findings: result.findings.map(pick), drift: result.drift || [] }, null, 2));
|
|
87
87
|
} else console.log(result.markdown);
|
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 } from "./registry.js";
|
|
9
|
+
import { loadBestRegistry, classify, normalizeId, replacementChange, replacementText } from "./registry.js";
|
|
10
10
|
import { migrationFor } from "./migration.js";
|
|
11
11
|
import { scanDirectory } from "./scan.js";
|
|
12
12
|
|
|
@@ -24,7 +24,7 @@ function advice(c, today = new Date()) {
|
|
|
24
24
|
const chg = replacementChange(m, today);
|
|
25
25
|
const mig = migrationFor(m);
|
|
26
26
|
const migText = mig && !mig.drop_in && mig.change !== "settings" ? ` More than a rename: ${mig.note}` : mig?.change === "choose" ? ` ${mig.note}` : "";
|
|
27
|
-
const repl = m.replacement ? ` Replacement: ${
|
|
27
|
+
const repl = m.replacement ? ` Replacement: ${replacementText(m)}.${chg ? ` ${who} changed this recommendation on ${chg.on}; before it was ${chg.was}.` : ""}` : "";
|
|
28
28
|
if (m.qualifier === "not-before") return `Active. ${who} says it will not be retired before ${m.retires}.`;
|
|
29
29
|
if (m.routes) return c.days <= 0
|
|
30
30
|
? `Since ${m.retires}, ${who} answers requests for it with ${m.replacement}. No error, but answers come from a different model.${migText}`
|
|
@@ -43,7 +43,7 @@ function describe(rawId, reg, today) {
|
|
|
43
43
|
id: rawId, ...(id !== rawId ? { normalized: id } : {}),
|
|
44
44
|
status: c.status, ...(m ? {
|
|
45
45
|
provider: m.provider, retires: m.retires, qualifier: m.qualifier, days_left: c.days,
|
|
46
|
-
replacement: m.replacement || null, ...(m.replacement_note ? { replacement_note: m.replacement_note } : {}),
|
|
46
|
+
replacement: m.replacement || null, ...(m.replacement_alt ? { replacement_alt: m.replacement_alt } : {}), ...(m.replacement_note ? { replacement_note: m.replacement_note } : {}),
|
|
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) } : {}),
|
|
@@ -62,6 +62,7 @@ const MODEL_RESULT = {
|
|
|
62
62
|
qualifier: { type: "string", enum: ["exact", "earliest", "not-before"], description: "exact date, earliest possible shutdown (Google), or not before this date (Anthropic)" },
|
|
63
63
|
days_left: { type: "number", description: "Days until the date; 0 or negative when it has passed" },
|
|
64
64
|
replacement: { type: ["string", "null"] },
|
|
65
|
+
replacement_alt: { type: "string", description: "A second replacement the provider names (\"X or Y\"); which one fits depends on how the model is used" },
|
|
65
66
|
previous_replacement: { type: "string", description: "The replacement the provider recommended before it changed its advice" },
|
|
66
67
|
replacement_changed: { type: "string", description: "YYYY-MM-DD: when the change was seen on the provider's page" },
|
|
67
68
|
migration: { type: "object", description: "Whether moving to the replacement is just a new model name (drop_in true) or needs more: change = rename | settings | params | api | re-embed | choose, with a note and the provider's guide", properties: { change: { type: "string" }, drop_in: { type: "boolean" }, note: { type: "string" }, guide: { type: ["string", "null"] } } },
|
package/src/registry.js
CHANGED
|
@@ -40,12 +40,18 @@ export function buildRegistry(data) {
|
|
|
40
40
|
if (!m.replacement) continue;
|
|
41
41
|
const seen = new Set([m.id]);
|
|
42
42
|
let next = m.replacement;
|
|
43
|
+
let hop = m; // the entry whose replacement is `next`: its second option and note belong to that target
|
|
43
44
|
seen.add(next);
|
|
44
45
|
while (byId.get(next)?.replacement && byId.get(next).qualifier !== "not-before" && !seen.has(byId.get(next).replacement)) {
|
|
45
|
-
|
|
46
|
+
hop = byId.get(next);
|
|
47
|
+
next = hop.replacement;
|
|
46
48
|
seen.add(next);
|
|
47
49
|
}
|
|
48
|
-
if (next !== m.replacement && next !== m.id) {
|
|
50
|
+
if (next !== m.replacement && next !== m.id) {
|
|
51
|
+
m.via = m.replacement; m.replacement = next;
|
|
52
|
+
if (hop.replacement_alt) m.replacement_alt = hop.replacement_alt; else delete m.replacement_alt;
|
|
53
|
+
if (hop.replacement_note) m.replacement_note = hop.replacement_note; else delete m.replacement_note;
|
|
54
|
+
}
|
|
49
55
|
}
|
|
50
56
|
// Anthropic short names point at a dated snapshot: "claude-sonnet-4-5" and "-latest" -> claude-sonnet-4-5-20250929,
|
|
51
57
|
// "claude-sonnet-4-0" -> claude-sonnet-4-20250514. The deprecation page lists only snapshots, so a short name
|
|
@@ -95,7 +101,7 @@ export const isTextModel = (entry) => !entry?.kind || entry.kind === "text";
|
|
|
95
101
|
// The replacement as people should read it: the model, plus the provider's extra instruction when there is one,
|
|
96
102
|
// e.g. "gpt-6.1-sol (reasoning.mode: pro, in the Responses API)". Without the note, a rename alone is not the move.
|
|
97
103
|
export const replacementText = (entry) => entry?.replacement
|
|
98
|
-
? entry.replacement + (entry.replacement_note ? ` (${entry.replacement_note})` : "") : "";
|
|
104
|
+
? entry.replacement + (entry.replacement_alt ? ` or ${entry.replacement_alt}` : "") + (entry.replacement_note ? ` (${entry.replacement_note})` : "") : "";
|
|
99
105
|
|
|
100
106
|
// Providers sometimes change the recommended replacement after announcing a retirement. The registry keeps
|
|
101
107
|
// the old one and the date we saw the change, so reports can say so instead of silently switching targets.
|
package/src/report.js
CHANGED
|
@@ -83,7 +83,7 @@ export function renderReport({ findings, drift, registry, notices = [], answers
|
|
|
83
83
|
}
|
|
84
84
|
const places = r.places.slice(0, 3).join(", ") + (r.places.length > 3 ? ` +${r.places.length - 3} more` : "");
|
|
85
85
|
const docsOnly = r.places.length && byModel.get(r.id).docs === r.places.length ? " · docs only" : "";
|
|
86
|
-
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} | ${esc(when(r))} | ${r.entry?.replacement ? code(r.entry.replacement) + (r.entry.replacement_note ? " (" + esc(r.entry.replacement_note) + ")" : "") + changed(r.entry, today) : ""} | ${changeCell} | ${places} |`);
|
|
86
|
+
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} | ${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
87
|
}
|
|
88
88
|
if (all.length > rows.length) lines.push("", `_${all.length - rows.length} more model ID(s) with no retirement listed are not shown._`);
|
|
89
89
|
if (migNotes.size) {
|