model-eol 0.2.4 → 0.4.0
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/README.md +117 -22
- package/action.yml +124 -9
- package/bot/bot.mjs +1371 -0
- package/bot/lib/common.mjs +122 -0
- package/bot/lib/config.mjs +1 -0
- package/bot/lib/eval.mjs +103 -0
- package/bot/lib/feeds.mjs +48 -0
- package/bot/lib/git.mjs +131 -0
- package/bot/lib/github.mjs +187 -0
- package/bot.yml.example +145 -0
- package/check.mjs +151 -17
- package/feeds/amazon.json +1 -1
- package/feeds/anthropic.json +1 -1
- package/feeds/google.json +88 -5
- package/feeds/openai.json +11 -26
- package/lib/config.mjs +258 -0
- package/lib/feeds.mjs +38 -4
- package/lib/glob.mjs +35 -0
- package/lib/plan.mjs +27 -8
- package/lib/reports.mjs +24 -7
- package/lib/scanner.mjs +247 -70
- package/package.json +8 -4
- package/schema/model-eol.bot-config.schema.json +73 -8
- package/schema/model-eol.inventory.schema.json +21 -0
- package/schema/model-eol.plan.schema.json +22 -0
package/bot.yml.example
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
name: model-eol bot
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
schedule:
|
|
5
|
+
- cron: '17 4 * * 1'
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: model-eol-bot
|
|
10
|
+
cancel-in-progress: false
|
|
11
|
+
|
|
12
|
+
env:
|
|
13
|
+
# Major-line pin: receives compatible fixes and refreshed bundled feeds.
|
|
14
|
+
MODEL_EOL_PACKAGE: model-eol@0
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
plan:
|
|
18
|
+
name: build trusted plan
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
permissions:
|
|
21
|
+
contents: read
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
with:
|
|
25
|
+
persist-credentials: false
|
|
26
|
+
- uses: actions/setup-node@v4
|
|
27
|
+
with:
|
|
28
|
+
node-version: 20
|
|
29
|
+
- name: Build the versioned plan
|
|
30
|
+
run: |
|
|
31
|
+
mkdir -p "$RUNNER_TEMP/model-eol-plan"
|
|
32
|
+
MODEL_EOL_VERSION="$(npm view "$MODEL_EOL_PACKAGE" version --json | node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{const v=JSON.parse(s);const r=Array.isArray(v)?v.at(-1):v;if(typeof r!=="string")process.exit(1);process.stdout.write(r)})')"
|
|
33
|
+
if [[ ! "$MODEL_EOL_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
34
|
+
echo "refusing invalid resolved model-eol version: $MODEL_EOL_VERSION" >&2
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
printf '%s\n' "$MODEL_EOL_VERSION" > "$RUNNER_TEMP/model-eol-plan/model-eol-version"
|
|
38
|
+
PLAN_ARGS=(plan .)
|
|
39
|
+
if [ ! -f .model-eol.json ]; then PLAN_ARGS+=(--days 90 --scope direct); fi
|
|
40
|
+
npx --yes --package="model-eol@$MODEL_EOL_VERSION" model-eol "${PLAN_ARGS[@]}" > "$RUNNER_TEMP/model-eol-plan/model-eol-plan.json"
|
|
41
|
+
- name: Upload plan
|
|
42
|
+
uses: actions/upload-artifact@v4
|
|
43
|
+
with:
|
|
44
|
+
name: model-eol-plan
|
|
45
|
+
path: |
|
|
46
|
+
${{ runner.temp }}/model-eol-plan/model-eol-plan.json
|
|
47
|
+
${{ runner.temp }}/model-eol-plan/model-eol-version
|
|
48
|
+
if-no-files-found: error
|
|
49
|
+
|
|
50
|
+
evaluate:
|
|
51
|
+
name: evaluate isolated migrations
|
|
52
|
+
needs: plan
|
|
53
|
+
runs-on: ubuntu-latest
|
|
54
|
+
permissions:
|
|
55
|
+
contents: read
|
|
56
|
+
steps:
|
|
57
|
+
# Provider credentials exist only in this read-only job. The evaluator
|
|
58
|
+
# forwards only names explicitly allowed by eval.pass_env.
|
|
59
|
+
- uses: actions/checkout@v4
|
|
60
|
+
with:
|
|
61
|
+
persist-credentials: false
|
|
62
|
+
- uses: actions/setup-node@v4
|
|
63
|
+
with:
|
|
64
|
+
node-version: 20
|
|
65
|
+
- name: Download trusted plan
|
|
66
|
+
uses: actions/download-artifact@v4
|
|
67
|
+
with:
|
|
68
|
+
name: model-eol-plan
|
|
69
|
+
path: ${{ runner.temp }}/model-eol-plan
|
|
70
|
+
- name: Apply and evaluate each migration independently
|
|
71
|
+
env:
|
|
72
|
+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
73
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
74
|
+
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
|
|
75
|
+
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
|
76
|
+
# A non-empty repository variable overrides eval.command. Otherwise
|
|
77
|
+
# the strictly validated command in .model-eol.json is used.
|
|
78
|
+
MODEL_EOL_EVAL_COMMAND: ${{ vars.MODEL_EOL_EVAL_COMMAND }}
|
|
79
|
+
run: |
|
|
80
|
+
mkdir -p "$RUNNER_TEMP/model-eol-eval"
|
|
81
|
+
MODEL_EOL_VERSION="$(cat "$RUNNER_TEMP/model-eol-plan/model-eol-version")"
|
|
82
|
+
if [[ ! "$MODEL_EOL_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
83
|
+
echo "refusing invalid resolved model-eol version: $MODEL_EOL_VERSION" >&2
|
|
84
|
+
exit 1
|
|
85
|
+
fi
|
|
86
|
+
npx --yes --package="model-eol@$MODEL_EOL_VERSION" model-eol-bot evaluate \
|
|
87
|
+
--target-dir . \
|
|
88
|
+
--plan-file "$RUNNER_TEMP/model-eol-plan/model-eol-plan.json" \
|
|
89
|
+
--output-file "$RUNNER_TEMP/model-eol-eval/model-eol-eval-results.json"
|
|
90
|
+
- name: Upload bounded eval results
|
|
91
|
+
uses: actions/upload-artifact@v4
|
|
92
|
+
with:
|
|
93
|
+
name: model-eol-eval-results
|
|
94
|
+
path: ${{ runner.temp }}/model-eol-eval/model-eol-eval-results.json
|
|
95
|
+
if-no-files-found: error
|
|
96
|
+
|
|
97
|
+
publish:
|
|
98
|
+
name: reconcile migration work
|
|
99
|
+
needs: [plan, evaluate]
|
|
100
|
+
runs-on: ubuntu-latest
|
|
101
|
+
permissions:
|
|
102
|
+
contents: write
|
|
103
|
+
pull-requests: write
|
|
104
|
+
issues: write
|
|
105
|
+
steps:
|
|
106
|
+
- uses: actions/checkout@v4
|
|
107
|
+
with:
|
|
108
|
+
# model-eol-bot authenticates its own temporary HTTPS clones and
|
|
109
|
+
# pushes with GITHUB_TOKEN; no checkout credential is persisted.
|
|
110
|
+
persist-credentials: false
|
|
111
|
+
- uses: actions/setup-node@v4
|
|
112
|
+
with:
|
|
113
|
+
node-version: 20
|
|
114
|
+
- name: Download trusted plan
|
|
115
|
+
uses: actions/download-artifact@v4
|
|
116
|
+
with:
|
|
117
|
+
name: model-eol-plan
|
|
118
|
+
path: ${{ runner.temp }}/model-eol-plan
|
|
119
|
+
- name: Download eval results
|
|
120
|
+
uses: actions/download-artifact@v4
|
|
121
|
+
with:
|
|
122
|
+
name: model-eol-eval-results
|
|
123
|
+
path: ${{ runner.temp }}/model-eol-eval
|
|
124
|
+
- name: Publish and reconcile model-eol work
|
|
125
|
+
env:
|
|
126
|
+
GITHUB_TOKEN: ${{ secrets.MODEL_EOL_BOT_TOKEN || secrets.GITHUB_TOKEN }}
|
|
127
|
+
MODEL_EOL_PLAN_FILE: ${{ runner.temp }}/model-eol-plan/model-eol-plan.json
|
|
128
|
+
MODEL_EOL_EVAL_RESULTS_FILE: ${{ runner.temp }}/model-eol-eval/model-eol-eval-results.json
|
|
129
|
+
# The write job receives the non-secret command text only so it can
|
|
130
|
+
# verify the eval configuration digest. It receives no provider keys.
|
|
131
|
+
MODEL_EOL_EVAL_COMMAND: ${{ vars.MODEL_EOL_EVAL_COMMAND }}
|
|
132
|
+
MODEL_EOL_TOKEN_KIND: ${{ secrets.MODEL_EOL_BOT_TOKEN && 'external-token' || 'github-token' }}
|
|
133
|
+
run: |
|
|
134
|
+
MODEL_EOL_VERSION="$(cat "$RUNNER_TEMP/model-eol-plan/model-eol-version")"
|
|
135
|
+
if [[ ! "$MODEL_EOL_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
136
|
+
echo "refusing invalid resolved model-eol version: $MODEL_EOL_VERSION" >&2
|
|
137
|
+
exit 1
|
|
138
|
+
fi
|
|
139
|
+
npx --yes --package="model-eol@$MODEL_EOL_VERSION" model-eol-bot --repo "${{ github.repository }}" --target-dir .
|
|
140
|
+
|
|
141
|
+
# A PR made with GITHUB_TOKEN does not trigger pull_request workflows. For
|
|
142
|
+
# required checks, store a narrowly scoped fine-grained PAT or GitHub App token
|
|
143
|
+
# as MODEL_EOL_BOT_TOKEN. Keep provider keys out of the publish job.
|
|
144
|
+
# Set feeds.allow_vendored_fallback: true only for degraded report-only runs.
|
|
145
|
+
# Independent concurrent invocations need external mutual exclusion.
|
package/check.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// model-eol reference CLI - zero dependencies.
|
|
3
3
|
//
|
|
4
4
|
// Default mode preserves the original CI gate:
|
|
5
|
-
// node check.mjs [paths...] [--days N] [--feeds DIR] [--via DISTRIBUTOR] [--scope all|direct] [--json] [--include-docs] [--allow-incomplete]
|
|
5
|
+
// node check.mjs [paths...] [--config FILE] [--days N] [--feeds DIR] [--via DISTRIBUTOR] [--scope all|direct] [--json] [--include-docs] [--allow-incomplete]
|
|
6
6
|
//
|
|
7
7
|
// New inventory modes:
|
|
8
8
|
// node check.mjs inventory [paths...] [--json|--format json|cyclonedx|text]
|
|
@@ -14,11 +14,23 @@
|
|
|
14
14
|
// exit codes: check/alert 0 = clear, 1 = finding, 2 = usage error;
|
|
15
15
|
// apply 0 = all items applied, 1 = item refused, 2 = usage or plan error.
|
|
16
16
|
|
|
17
|
+
import fs from 'node:fs'
|
|
17
18
|
import path from 'node:path'
|
|
18
19
|
|
|
19
20
|
import { applyPlan } from './lib/apply.mjs'
|
|
20
21
|
import { parseCliArgs } from './lib/cli.mjs'
|
|
21
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
CLI_DEFAULT_CONFIG,
|
|
24
|
+
DEFAULT_CONFIG,
|
|
25
|
+
configForRepoPath,
|
|
26
|
+
configuredVias,
|
|
27
|
+
loadConfig,
|
|
28
|
+
mappedRoutesForRepoPath,
|
|
29
|
+
normalizeConfig,
|
|
30
|
+
routeForRepoReference,
|
|
31
|
+
} from './lib/config.mjs'
|
|
32
|
+
import { BUILTIN_CHANNELS, findingFromRef, isBad, loadFeeds } from './lib/feeds.mjs'
|
|
33
|
+
import { normalizeRepoPath } from './lib/glob.mjs'
|
|
22
34
|
import { buildPlan } from './lib/plan.mjs'
|
|
23
35
|
import {
|
|
24
36
|
buildAlert,
|
|
@@ -32,7 +44,53 @@ import {
|
|
|
32
44
|
formatInventoryCycloneDX,
|
|
33
45
|
formatSchedule,
|
|
34
46
|
} from './lib/reports.mjs'
|
|
35
|
-
import { addedLinesForTargets, filterFindingsToChanged, incompleteScanNotes, scanTargets } from './lib/scanner.mjs'
|
|
47
|
+
import { addedLinesForTargets, filterFindingsToChanged, gitRootFor, incompleteScanNotes, scanTargets } from './lib/scanner.mjs'
|
|
48
|
+
|
|
49
|
+
const rootForTarget = target => {
|
|
50
|
+
const absolute = path.resolve(target)
|
|
51
|
+
let stat
|
|
52
|
+
try {
|
|
53
|
+
stat = fs.lstatSync(absolute)
|
|
54
|
+
} catch {
|
|
55
|
+
return null
|
|
56
|
+
}
|
|
57
|
+
let resolved = absolute
|
|
58
|
+
try {
|
|
59
|
+
resolved = fs.realpathSync(absolute)
|
|
60
|
+
} catch {
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const gitRoot = gitRootFor(resolved)
|
|
64
|
+
if (gitRoot) return gitRoot
|
|
65
|
+
} catch {
|
|
66
|
+
}
|
|
67
|
+
return stat.isDirectory() ? resolved : path.dirname(resolved)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const configForTargets = ({ targets, explicit }) => {
|
|
71
|
+
const roots = [...new Set(targets.map(rootForTarget).filter(Boolean))]
|
|
72
|
+
if (explicit !== null) {
|
|
73
|
+
if (!explicit) throw new Error('--config must be a non-empty path')
|
|
74
|
+
return { file: path.resolve(explicit), roots }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const files = [...new Set(roots
|
|
78
|
+
.map(root => path.join(root, '.model-eol.json'))
|
|
79
|
+
.filter(file => fs.existsSync(file)))]
|
|
80
|
+
if (files.length > 1) {
|
|
81
|
+
throw new Error(`multiple .model-eol.json files found (${files.join(', ')}); use --config FILE to choose one`)
|
|
82
|
+
}
|
|
83
|
+
return { file: files[0] ?? null, roots }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const repoPathForFile = (file, roots) => {
|
|
87
|
+
const absolute = path.resolve(file)
|
|
88
|
+
const candidates = roots
|
|
89
|
+
.map(root => ({ root, relative: path.relative(root, absolute) }))
|
|
90
|
+
.filter(({ relative }) => relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative))
|
|
91
|
+
.sort((a, b) => b.root.length - a.root.length)
|
|
92
|
+
return normalizeRepoPath(candidates[0]?.relative ?? absolute)
|
|
93
|
+
}
|
|
36
94
|
|
|
37
95
|
const main = () => {
|
|
38
96
|
const COMMANDS = new Set(['check', 'inventory', 'schedule', 'alert', 'plan', 'apply', 'help'])
|
|
@@ -46,6 +104,7 @@ try {
|
|
|
46
104
|
args,
|
|
47
105
|
options: {
|
|
48
106
|
days: { type: 'string' },
|
|
107
|
+
config: { type: 'string' },
|
|
49
108
|
feeds: { type: 'string' },
|
|
50
109
|
via: { type: 'string' },
|
|
51
110
|
scope: { type: 'string' },
|
|
@@ -69,10 +128,6 @@ try {
|
|
|
69
128
|
const { values, positionals } = parsed
|
|
70
129
|
if (values.help) command = 'help'
|
|
71
130
|
|
|
72
|
-
const DAYS = Number(values.days ?? '90')
|
|
73
|
-
const FEEDS_DIR = values.feeds ?? path.join(import.meta.dirname, 'feeds')
|
|
74
|
-
const VIA = values.via ?? null // e.g. azure-ai-foundry, aws-bedrock
|
|
75
|
-
const SCOPE = values.scope ?? 'all'
|
|
76
131
|
const FORMAT = values.format ?? null
|
|
77
132
|
const PLAN_FILE = values.plan ?? null
|
|
78
133
|
const AS_JSON = values.json ?? false
|
|
@@ -86,12 +141,12 @@ if (command === 'help') {
|
|
|
86
141
|
console.log(`model-eol
|
|
87
142
|
|
|
88
143
|
Usage:
|
|
89
|
-
node check.mjs [paths...] [--days N] [--feeds DIR] [--via DISTRIBUTOR] [--scope all|direct] [--json] [--include-docs] [--allow-incomplete]
|
|
90
|
-
node check.mjs check [paths...] [--days N] [--scope all|direct] [--json] [--changed BASE_REF] [--allow-incomplete]
|
|
91
|
-
node check.mjs inventory [paths...] [--json] [--format json|cyclonedx|text]
|
|
92
|
-
node check.mjs schedule [paths...] [--days N] [--json]
|
|
93
|
-
node check.mjs alert [paths...] [--days N] [--scope all|direct] [--format github|markdown|badge] [--json]
|
|
94
|
-
node check.mjs plan [paths...] [--days N] [--scope all|direct] [--via DISTRIBUTOR] [--feeds DIR] [--allow-incomplete]
|
|
144
|
+
node check.mjs [paths...] [--config FILE] [--days N] [--feeds DIR] [--via DISTRIBUTOR] [--scope all|direct] [--json] [--include-docs] [--allow-incomplete]
|
|
145
|
+
node check.mjs check [paths...] [--config FILE] [--days N] [--scope all|direct] [--json] [--changed BASE_REF] [--allow-incomplete]
|
|
146
|
+
node check.mjs inventory [paths...] [--config FILE] [--json] [--format json|cyclonedx|text]
|
|
147
|
+
node check.mjs schedule [paths...] [--config FILE] [--days N] [--json]
|
|
148
|
+
node check.mjs alert [paths...] [--config FILE] [--days N] [--scope all|direct] [--format github|markdown|badge] [--json]
|
|
149
|
+
node check.mjs plan [paths...] [--config FILE] [--days N] [--scope all|direct] [--via DISTRIBUTOR] [--feeds DIR] [--allow-incomplete]
|
|
95
150
|
node check.mjs apply --plan plan.json [--dry-run]
|
|
96
151
|
|
|
97
152
|
Commands:
|
|
@@ -109,6 +164,10 @@ Scopes:
|
|
|
109
164
|
all Check every tracked model ID found. This preserves the original behavior.
|
|
110
165
|
direct Check direct API and generic model references; leave cloud/gateway refs in inventory.
|
|
111
166
|
|
|
167
|
+
Configuration:
|
|
168
|
+
.model-eol.json is loaded automatically from the target repository root.
|
|
169
|
+
--config FILE selects an explicit config. Explicit CLI flags override config values.
|
|
170
|
+
|
|
112
171
|
Exit codes:
|
|
113
172
|
0 Clean, or report generated successfully.
|
|
114
173
|
1 Findings, alert errors, or refused apply items.
|
|
@@ -132,6 +191,23 @@ if (command === 'apply') {
|
|
|
132
191
|
}
|
|
133
192
|
}
|
|
134
193
|
|
|
194
|
+
let configLocation
|
|
195
|
+
let repositoryConfig
|
|
196
|
+
try {
|
|
197
|
+
configLocation = configForTargets({ targets, explicit: values.config ?? null })
|
|
198
|
+
repositoryConfig = configLocation.file
|
|
199
|
+
? loadConfig(configLocation.file, { defaults: DEFAULT_CONFIG, allowMissing: false })
|
|
200
|
+
: normalizeConfig({}, { defaults: CLI_DEFAULT_CONFIG })
|
|
201
|
+
} catch (e) {
|
|
202
|
+
console.error(`failed to load repository config: ${e.message}`)
|
|
203
|
+
return 2
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const DAYS = Number(values.days ?? repositoryConfig.days)
|
|
207
|
+
const FEEDS_DIR = values.feeds ?? path.join(import.meta.dirname, 'feeds')
|
|
208
|
+
const VIA = values.via ?? repositoryConfig.via // e.g. azure-ai-foundry, aws-bedrock
|
|
209
|
+
const SCOPE = values.scope ?? repositoryConfig.scope
|
|
210
|
+
|
|
135
211
|
if (!Number.isFinite(DAYS) || !Number.isInteger(DAYS) || DAYS < 0) {
|
|
136
212
|
console.error('--days must be a finite non-negative integer')
|
|
137
213
|
return 2
|
|
@@ -165,6 +241,37 @@ if (feedData.entries.size === 0) {
|
|
|
165
241
|
return 2
|
|
166
242
|
}
|
|
167
243
|
|
|
244
|
+
const allowedVias = new Set([...BUILTIN_CHANNELS, ...feedData.vias])
|
|
245
|
+
const unknownVia = [values.via, ...configuredVias(repositoryConfig)]
|
|
246
|
+
.find(via => via && !allowedVias.has(via))
|
|
247
|
+
if (unknownVia) {
|
|
248
|
+
console.error(`unknown lifecycle channel "${unknownVia}"; known values: ${[...allowedVias].sort().join(', ')}`)
|
|
249
|
+
return 2
|
|
250
|
+
}
|
|
251
|
+
const unresolvedRoute = repositoryConfig.routes.find(route => route.model && !feedData.entries.has(route.model))
|
|
252
|
+
if (unresolvedRoute) {
|
|
253
|
+
console.error(`configured route model "${unresolvedRoute.model}" is not present in loaded feeds`)
|
|
254
|
+
return 2
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const policyCache = new Map()
|
|
258
|
+
const policyForFile = file => {
|
|
259
|
+
const absolute = path.resolve(file)
|
|
260
|
+
if (!policyCache.has(absolute)) {
|
|
261
|
+
const repoPath = repoPathForFile(absolute, configLocation.roots)
|
|
262
|
+
policyCache.set(absolute, { ...configForRepoPath(repositoryConfig, repoPath), repoPath })
|
|
263
|
+
}
|
|
264
|
+
return policyCache.get(absolute)
|
|
265
|
+
}
|
|
266
|
+
const routeForReference = ({ file, matched, id }) => {
|
|
267
|
+
const policy = policyForFile(file)
|
|
268
|
+
return routeForRepoReference(repositoryConfig, policy.repoPath, { matched, id })
|
|
269
|
+
}
|
|
270
|
+
const mappedRoutesForFile = file => {
|
|
271
|
+
const policy = policyForFile(file)
|
|
272
|
+
return mappedRoutesForRepoPath(repositoryConfig, policy.repoPath)
|
|
273
|
+
}
|
|
274
|
+
|
|
168
275
|
let scan
|
|
169
276
|
try {
|
|
170
277
|
scan = scanTargets({
|
|
@@ -172,6 +279,11 @@ try {
|
|
|
172
279
|
entries: feedData.entries,
|
|
173
280
|
keys: feedData.keys,
|
|
174
281
|
includeDocs: INCLUDE_DOCS,
|
|
282
|
+
ignoreRoots: configLocation.roots,
|
|
283
|
+
ignoredFiles: configLocation.file ? [configLocation.file] : [],
|
|
284
|
+
policyForFile,
|
|
285
|
+
routeForReference,
|
|
286
|
+
mappedRoutesForFile,
|
|
175
287
|
})
|
|
176
288
|
} catch (e) {
|
|
177
289
|
console.error(`scan failed: ${e.message}`)
|
|
@@ -189,10 +301,32 @@ if (incompleteNotes.length && (command === 'check' || command === 'plan')) {
|
|
|
189
301
|
if (!ALLOW_INCOMPLETE) return 2
|
|
190
302
|
}
|
|
191
303
|
|
|
192
|
-
const findings = scan.modelRefs.map(ref =>
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
:
|
|
304
|
+
const findings = scan.modelRefs.map(ref => {
|
|
305
|
+
const policy = policyForFile(ref.file)
|
|
306
|
+
const route = routeForReference({ file: ref.file, matched: ref.matched, id: ref.id })
|
|
307
|
+
const days = values.days === undefined ? policy.days : DAYS
|
|
308
|
+
const scope = values.scope === undefined ? policy.scope : SCOPE
|
|
309
|
+
const via = values.via === undefined ? (route?.via ?? policy.via) : VIA
|
|
310
|
+
const finding = findingFromRef(ref, { days, via })
|
|
311
|
+
finding.effective_scope = scope
|
|
312
|
+
finding.requested_via = via
|
|
313
|
+
if (policy.override_indexes.length || route) {
|
|
314
|
+
finding.policy_provenance = {
|
|
315
|
+
override_indexes: policy.override_indexes,
|
|
316
|
+
route_index: route?.index ?? null,
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return finding
|
|
320
|
+
})
|
|
321
|
+
for (const candidate of scan.candidateRefs) {
|
|
322
|
+
const policy = policyForFile(candidate.file)
|
|
323
|
+
candidate.effective_scope = values.scope === undefined ? policy.scope : SCOPE
|
|
324
|
+
if (policy.override_indexes.length) {
|
|
325
|
+
candidate.policy_provenance = { override_indexes: policy.override_indexes, route_index: null }
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
const checkFindings = findings.filter(finding =>
|
|
329
|
+
finding.effective_scope !== 'direct' || finding.usage === 'direct-api' || finding.usage === 'model-reference')
|
|
196
330
|
let changedFindings = checkFindings
|
|
197
331
|
if (CHANGED_BASE !== null) {
|
|
198
332
|
try {
|
package/feeds/amazon.json
CHANGED
package/feeds/anthropic.json
CHANGED
package/feeds/google.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"spec": "model-eol/0.1",
|
|
3
3
|
"publisher": "google",
|
|
4
|
-
"generated": "2026-08-
|
|
4
|
+
"generated": "2026-08-18T00:47:44.931Z",
|
|
5
5
|
"source": "https://ai.google.dev/gemini-api/docs/deprecations",
|
|
6
6
|
"models": [
|
|
7
7
|
{
|
|
@@ -318,7 +318,13 @@
|
|
|
318
318
|
},
|
|
319
319
|
{
|
|
320
320
|
"id": "gemini-embedding-2",
|
|
321
|
-
"source": "https://ai.google.dev/gemini-api/docs/deprecations"
|
|
321
|
+
"source": "https://ai.google.dev/gemini-api/docs/deprecations",
|
|
322
|
+
"distributions": [
|
|
323
|
+
{
|
|
324
|
+
"via": "vertex-ai",
|
|
325
|
+
"source": "https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/model-versions"
|
|
326
|
+
}
|
|
327
|
+
]
|
|
322
328
|
},
|
|
323
329
|
{
|
|
324
330
|
"id": "gemini-embedding-001",
|
|
@@ -518,9 +524,7 @@
|
|
|
518
524
|
"source": "https://ai.google.dev/gemini-api/docs/deprecations",
|
|
519
525
|
"shutdown": "2026-08-31",
|
|
520
526
|
"date_precision": "earliest",
|
|
521
|
-
"
|
|
522
|
-
"gemini-robotics-er-2-preview"
|
|
523
|
-
]
|
|
527
|
+
"replacement": "gemini-robotics-er-2-preview"
|
|
524
528
|
},
|
|
525
529
|
{
|
|
526
530
|
"id": "gemini-robotics-er-1.5-preview",
|
|
@@ -528,6 +532,85 @@
|
|
|
528
532
|
"shutdown": "2026-04-30",
|
|
529
533
|
"date_precision": "earliest",
|
|
530
534
|
"replacement": "gemini-robotics-er-1.6-preview"
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
"id": "gemini-3.7-flash",
|
|
538
|
+
"source": "https://ai.google.dev/gemini-api/docs/deprecations",
|
|
539
|
+
"distributions": [
|
|
540
|
+
{
|
|
541
|
+
"via": "vertex-ai",
|
|
542
|
+
"source": "https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/model-versions"
|
|
543
|
+
}
|
|
544
|
+
]
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
"id": "gemma-4-26b-a4b-it"
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
"id": "gemma-4-31b-it"
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
"id": "gemini-flash-latest"
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
"id": "gemini-flash-lite-latest"
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
"id": "gemini-pro-latest"
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
"id": "gemini-3.1-pro-preview-customtools"
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
"id": "nano-banana-pro-preview"
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
"id": "gemini-3.1-flash-lite-image",
|
|
569
|
+
"distributions": [
|
|
570
|
+
{
|
|
571
|
+
"via": "vertex-ai",
|
|
572
|
+
"source": "https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/model-versions"
|
|
573
|
+
}
|
|
574
|
+
]
|
|
575
|
+
},
|
|
576
|
+
{
|
|
577
|
+
"id": "gemini-omni-flash-preview"
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
"id": "gemini-robotics-er-2-preview"
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
"id": "gemini-2.5-computer-use-preview-10-2025"
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
"id": "antigravity-preview-05-2026"
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
"id": "deep-research-max-preview-04-2026"
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
"id": "deep-research-preview-04-2026"
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
"id": "deep-research-pro-preview-12-2025"
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
"id": "gemini-embedding-2-preview"
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
"id": "aqa"
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
"id": "gemini-2.5-flash-native-audio-latest"
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
"id": "gemini-2.5-flash-native-audio-preview-09-2025"
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
"id": "gemini-robotics-er-2-streaming-preview"
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
"id": "gemini-3.5-live-translate-preview"
|
|
531
614
|
}
|
|
532
615
|
]
|
|
533
616
|
}
|
package/feeds/openai.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"spec": "model-eol/0.1",
|
|
3
3
|
"publisher": "openai",
|
|
4
|
-
"generated": "2026-08-
|
|
4
|
+
"generated": "2026-08-18T00:47:44.930Z",
|
|
5
5
|
"source": "https://developers.openai.com/api/docs/deprecations",
|
|
6
6
|
"note": "Community-maintained seed dataset, compiled 2026-07-25 from the provider's deprecations page. Verify against the source before acting; this file demonstrates the format.",
|
|
7
7
|
"models": [
|
|
@@ -308,35 +308,35 @@
|
|
|
308
308
|
"source": "https://developers.openai.com/api/docs/deprecations",
|
|
309
309
|
"announced": "2026-04-22",
|
|
310
310
|
"shutdown": "2026-10-23",
|
|
311
|
-
"replacement": "gpt-5.
|
|
311
|
+
"replacement": "gpt-5.6-terra"
|
|
312
312
|
},
|
|
313
313
|
{
|
|
314
314
|
"id": "ft-gpt-4",
|
|
315
315
|
"source": "https://developers.openai.com/api/docs/deprecations",
|
|
316
316
|
"announced": "2026-04-22",
|
|
317
317
|
"shutdown": "2026-10-23",
|
|
318
|
-
"replacement": "gpt-5.
|
|
318
|
+
"replacement": "gpt-5.6-sol"
|
|
319
319
|
},
|
|
320
320
|
{
|
|
321
321
|
"id": "ft-gpt-4.1-nano-2025-04-14",
|
|
322
322
|
"source": "https://developers.openai.com/api/docs/deprecations",
|
|
323
323
|
"announced": "2026-04-22",
|
|
324
324
|
"shutdown": "2026-10-23",
|
|
325
|
-
"replacement": "gpt-5.
|
|
325
|
+
"replacement": "gpt-5.6-luna"
|
|
326
326
|
},
|
|
327
327
|
{
|
|
328
328
|
"id": "ft-babbage-002",
|
|
329
329
|
"source": "https://developers.openai.com/api/docs/deprecations",
|
|
330
330
|
"announced": "2026-04-22",
|
|
331
331
|
"shutdown": "2026-10-23",
|
|
332
|
-
"replacement": "gpt-5.
|
|
332
|
+
"replacement": "gpt-5.6-terra"
|
|
333
333
|
},
|
|
334
334
|
{
|
|
335
335
|
"id": "ft-davinci-002",
|
|
336
336
|
"source": "https://developers.openai.com/api/docs/deprecations",
|
|
337
337
|
"announced": "2026-04-22",
|
|
338
338
|
"shutdown": "2026-10-23",
|
|
339
|
-
"replacement": "gpt-5.
|
|
339
|
+
"replacement": "gpt-5.6-terra"
|
|
340
340
|
},
|
|
341
341
|
{
|
|
342
342
|
"id": "sora-2",
|
|
@@ -373,40 +373,28 @@
|
|
|
373
373
|
"source": "https://developers.openai.com/api/docs/deprecations",
|
|
374
374
|
"announced": "2025-09-26",
|
|
375
375
|
"shutdown": "2026-09-28",
|
|
376
|
-
"
|
|
377
|
-
"gpt-5.4-mini",
|
|
378
|
-
"gpt-5-mini"
|
|
379
|
-
]
|
|
376
|
+
"replacement": "gpt-5.6-terra"
|
|
380
377
|
},
|
|
381
378
|
{
|
|
382
379
|
"id": "babbage-002",
|
|
383
380
|
"source": "https://developers.openai.com/api/docs/deprecations",
|
|
384
381
|
"announced": "2025-09-26",
|
|
385
382
|
"shutdown": "2026-09-28",
|
|
386
|
-
"
|
|
387
|
-
"gpt-5.4-mini",
|
|
388
|
-
"gpt-5-mini"
|
|
389
|
-
]
|
|
383
|
+
"replacement": "gpt-5.6-terra"
|
|
390
384
|
},
|
|
391
385
|
{
|
|
392
386
|
"id": "davinci-002",
|
|
393
387
|
"source": "https://developers.openai.com/api/docs/deprecations",
|
|
394
388
|
"announced": "2025-09-26",
|
|
395
389
|
"shutdown": "2026-09-28",
|
|
396
|
-
"
|
|
397
|
-
"gpt-5.4-mini",
|
|
398
|
-
"gpt-5-mini"
|
|
399
|
-
]
|
|
390
|
+
"replacement": "gpt-5.6-terra"
|
|
400
391
|
},
|
|
401
392
|
{
|
|
402
393
|
"id": "gpt-3.5-turbo-1106",
|
|
403
394
|
"source": "https://developers.openai.com/api/docs/deprecations",
|
|
404
395
|
"announced": "2025-09-26",
|
|
405
396
|
"shutdown": "2026-09-28",
|
|
406
|
-
"
|
|
407
|
-
"gpt-5.4-mini",
|
|
408
|
-
"gpt-5-mini"
|
|
409
|
-
]
|
|
397
|
+
"replacement": "gpt-5.6-terra"
|
|
410
398
|
},
|
|
411
399
|
{
|
|
412
400
|
"id": "computer-use-preview-2025-03-11",
|
|
@@ -427,10 +415,7 @@
|
|
|
427
415
|
},
|
|
428
416
|
{
|
|
429
417
|
"id": "gpt-4o-mini-tts-2025-03-20",
|
|
430
|
-
"source": "https://developers.openai.com/api/docs/deprecations"
|
|
431
|
-
"announced": "2026-04-22",
|
|
432
|
-
"shutdown": "2026-07-23",
|
|
433
|
-
"replacement": "gpt-4o-mini-tts-2025-12-15"
|
|
418
|
+
"source": "https://developers.openai.com/api/docs/deprecations"
|
|
434
419
|
},
|
|
435
420
|
{
|
|
436
421
|
"id": "gpt-4o-search-preview-2025-03-11",
|