arol-ai 0.1.0 → 0.1.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/README.md CHANGED
@@ -35,25 +35,26 @@ arol-ai scan
35
35
 
36
36
  ```
37
37
  arol · local deprecation scan
38
- Scanned 128 files · 3 APIs detected
38
+ Scanned 128 files · 1 API detected
39
39
 
40
- 3 deprecations found (2 high, 1 medium)
40
+ 1 deprecation found (1 high, 0 medium, 0 low)
41
41
 
42
- AWS · AWS SDK for JavaScript v2 end-of-support HIGH
43
- sunsets 2025-09-08 (passed 269 days ago)
44
- The monolithic 'aws-sdk' (v2) entered maintenance mode in 2024 and reached
45
- end-of-support. Migrate to the modular AWS SDK for JavaScript v3.
42
+ OpenAI · Assistants API (beta) HIGH
43
+ sunsets 2026-08-26 (in 84 days)
44
+ The Assistants API beta is being removed on Aug 26, 2026; requests to
45
+ /v1/assistants and /v1/threads will fail. Migrate to the Responses API +
46
+ Conversations API.
46
47
  found in:
47
- package.json aws-sdk@^2.1400.0
48
- src/storage.ts:1, 42
49
- → migrate: https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/migrating-to-v3.html
50
-
51
- ...
48
+ src/agents/run.ts:42 beta.assistants
49
+ src/agents/run.ts:88 → beta.threads
50
+ → migrate: https://platform.openai.com/docs/assistants/migration
52
51
 
53
52
  These are today's deprecations. New ones land constantly — get
54
53
  alerted before the next one breaks you → arol.ai
55
54
  ```
56
55
 
56
+ Note the citations point at the **exact source lines that use the deprecated API**, not at the manifest. Having the `openai` package installed is not enough on its own — your code has to actually call the removed surface.
57
+
57
58
  When nothing is found:
58
59
 
59
60
  ```
@@ -62,20 +63,29 @@ When nothing is found:
62
63
 
63
64
  ## How detection works
64
65
 
65
- For every entry in the dataset, `arol-ai` runs two independent checks:
66
+ Detection keys on **actual usage, not mere SDK presence.** Each dataset entry declares a `match` mode that decides what triggers it.
67
+
68
+ ### `match: "pattern"` — the default
69
+
70
+ Flags **only** when one of the entry's `detect.patterns` regexes matches inside a scanned **source file** — i.e. your code actually references the deprecated endpoint, method, or model string. `detect.sdk` is just a scope hint here and is **never** a trigger on its own. This is the default and covers almost everything.
71
+
72
+ - Extensions scanned: `.js .mjs .cjs .jsx .ts .mts .cts .tsx .py .go`
73
+ - Skipped directories: `node_modules`, `.git`, `dist`, `build`, `.next`, `out`, `coverage`, `.venv`, `venv`, `vendor`
74
+ - Each hit records the **file path, line number, and matched text**, and one deprecation aggregates **all** of its matched locations into a single finding.
75
+
76
+ > Having the `openai` package in `requirements.txt` does **not** flag the Assistants API deprecation. Your code has to actually use `beta.assistants` / `beta.threads` (etc.).
77
+
78
+ ### `match: "sdk"`
66
79
 
67
- 1. **Manifest scan** — parses the repo's root manifests for declared dependencies and their versions:
68
- - `package.json` (`dependencies`, `devDependencies`, `peerDependencies`, `optionalDependencies`, plus simple npm `workspaces`)
69
- - `requirements.txt` (Python)
70
- - `go.mod` (Go)
80
+ Flags when a `detect.sdk` package appears in a manifest, **regardless of code** — for the rare "this whole SDK / version line is end-of-life" case.
71
81
 
72
- A dependency matches if its name equals one of the entry's `detect.sdk` names (case-insensitively, with PyPI-style `_ . -` normalization).
82
+ ### `match: "version"`
73
83
 
74
- 2. **Inline scan** walks source files and regex-matches each entry's `detect.patterns`, recording the file paths and line numbers.
75
- - Extensions scanned: `.js .mjs .cjs .jsx .ts .mts .cts .tsx .py .go`
76
- - Skipped directories: `node_modules`, `.git`, `dist`, `build`, `.next`, `out`, `coverage`, `.venv`, `venv`, `vendor`
84
+ Flags when a `detect.sdk` package appears in a manifest **and** its declared version satisfies the entry's `version_range` (e.g. `"<3.0.0"`). Semver-style compare for npm; best-effort numeric compare for pip/go. If `version_range` is omitted, it behaves like `"sdk"`. *(No version entries ship today.)*
77
85
 
78
- A deprecation is **detected** if its SDK is present **OR** any of its patterns match. Both kinds of evidence are shown in the report.
86
+ **Manifests parsed** (used by `sdk`/`version` modes): `package.json` (`dependencies`, `devDependencies`, `peerDependencies`, `optionalDependencies`, plus simple npm `workspaces`), `requirements.txt` (Python), and `go.mod` (Go). A dependency matches a `detect.sdk` name case-insensitively, with PyPI-style `_ . -` normalization.
87
+
88
+ The report cites **source code locations** for `pattern` findings, and the **manifest line** (`package.json → name@version`) for `sdk`/`version` findings.
79
89
 
80
90
  ## CLI
81
91
 
@@ -110,32 +120,45 @@ All detections are **data-driven** — the bundled dataset lives at
110
120
 
111
121
  ### Schema
112
122
 
123
+ The dataset is either a bare array of entries, or a `{ "deprecations": [ ... ] }` object. A typical `pattern` entry:
124
+
113
125
  ```jsonc
114
126
  {
115
- "schema_version": 1, // optional, informational
116
- "updated": "2026-06-01", // optional, informational
117
- "deprecations": [
118
- {
119
- "id": "aws-sdk-js-v2-eos", // unique, stable identifier (required)
120
- "vendor": "AWS", // who owns the API (required)
121
- "title": "AWS SDK for JavaScript v2 end-of-support", // short headline (required)
122
- "severity": "high", // "high" | "medium" | "low" (required)
123
- "sunset_date": "2025-09-08",// ISO YYYY-MM-DD, or "" if no fixed date
124
- "detect": {
125
- "sdk": ["aws-sdk"], // dependency/module names to find in manifests
126
- "patterns": [ // regex strings matched against source files
127
- "require\\(\\s*['\"]aws-sdk['\"]\\s*\\)",
128
- "from\\s+['\"]aws-sdk['\"]"
129
- ]
130
- },
131
- "migration_url": "https://docs.aws.amazon.com/.../migrating-to-v3.html",
132
- "summary": "One or two sentences explaining the change and what to do."
133
- }
134
- ]
127
+ "id": "openai-assistants-api", // unique, stable identifier (required)
128
+ "vendor": "OpenAI", // who owns the API (required)
129
+ "title": "Assistants API (beta)", // short headline (required)
130
+ "severity": "high", // "high" | "medium" | "low" (required)
131
+ "match": "pattern", // "pattern" (default) | "sdk" | "version"
132
+ "sunset_date": "2026-08-26", // ISO YYYY-MM-DD, or "" if no fixed date
133
+ "detect": {
134
+ "sdk": ["openai"], // scope hint for "pattern"; the trigger for "sdk"/"version"
135
+ "patterns": [ // regex strings matched against source files
136
+ "beta\\.assistants",
137
+ "beta\\.threads",
138
+ "/v1/assistants"
139
+ ]
140
+ },
141
+ "migration_url": "https://platform.openai.com/docs/assistants/migration",
142
+ "summary": "One or two sentences explaining the change and what to do."
135
143
  }
136
144
  ```
137
145
 
138
- A bare top-level array (`[ { ...entry }, ... ]`) is also accepted.
146
+ A `version` entry instead flags on the installed SDK version (no patterns needed):
147
+
148
+ ```jsonc
149
+ {
150
+ "id": "example-sdk-v2-eol",
151
+ "vendor": "Example",
152
+ "title": "example-sdk v2 line end-of-life",
153
+ "severity": "medium",
154
+ "match": "version",
155
+ "version_range": "<3.0.0", // flags only when the declared version is in range
156
+ "sunset_date": "",
157
+ "detect": { "sdk": ["example-sdk"], "patterns": [] },
158
+ "migration_url": "https://example.com/migrate",
159
+ "summary": "Upgrade example-sdk to v3+."
160
+ }
161
+ ```
139
162
 
140
163
  ### Field reference
141
164
 
@@ -145,19 +168,21 @@ A bare top-level array (`[ { ...entry }, ... ]`) is also accepted.
145
168
  | `vendor` | string | ✓ | Displayed before the title. |
146
169
  | `title` | string | ✓ | Short headline for the finding. |
147
170
  | `severity` | `"high"` \| `"medium"` \| `"low"` | ✓ | Drives color, sort order, and `--fail-on`. |
171
+ | `match` | `"pattern"` \| `"sdk"` \| `"version"` | – | How the entry is triggered. **Defaults to `"pattern"`** when omitted. See [How detection works](#how-detection-works). |
172
+ | `version_range` | string | – | For `match: "version"` only — e.g. `"<3.0.0"`, `">=1.2.0"`, `"=2.1.0"`. If omitted, a `version` entry behaves like `"sdk"`. |
148
173
  | `sunset_date` | string | – | ISO `YYYY-MM-DD`. Use `""` for unmaintained/no-fixed-date items; the report shows a relative hint (e.g. *"in 42 days"* / *"passed 12 days ago"*). |
149
- | `detect.sdk` | string[] | – | Manifest dependency/module names. May be empty for pattern-only detection. |
150
- | `detect.patterns` | string[] | – | **JSON-escaped** regular-expression strings (so `\d` becomes `\\d`). Matched per source file; invalid regexes are skipped safely. May be empty for manifest-only detection. |
174
+ | `detect.sdk` | string[] | – | Manifest dependency/module names. For `match: "pattern"` this is only a **scope hint and never triggers** a finding; for `sdk`/`version` it is the trigger. |
175
+ | `detect.patterns` | string[] | – | **JSON-escaped** regular-expression strings (so `\d` becomes `\\d`). Matched against source-file contents; invalid regexes are skipped safely. Required (non-empty) for `match: "pattern"`. |
151
176
  | `migration_url` | string | – | Link shown in the report. |
152
177
  | `summary` | string | – | One or two sentences of guidance. |
153
178
 
154
- > An entry with **both** `detect.sdk` and `detect.patterns` empty can never match and is ignored.
179
+ > A `pattern` entry with no `patterns`, or an `sdk`/`version` entry with no `sdk`, can never fire and is dropped at load time.
155
180
 
156
181
  ### Writing good patterns
157
182
 
158
- - Patterns are matched **case-sensitively** with the global flag over each file's contents; line numbers are reported.
159
- - Escape backslashes for JSON: a regex `\bimport\b` is written `"\\bimport\\b"`.
160
- - Prefer specific anchors (`require\(\s*['"]name['"]\s*\)`, `from\s+['"]name['"]`) over bare package names to avoid false positives.
183
+ - Patterns are matched **case-sensitively** with the global flag over each file's contents; the file path, line number, and matched text are reported.
184
+ - Match the **deprecated surface itself** the method/property (`beta\.assistants`), endpoint path (`/v1/threads`), or model string (`claude-opus-4-20250514`) — not the import or the package name. Importing an SDK isn't usage; calling the removed API is.
185
+ - Escape backslashes (and literal dots) for JSON: a regex `beta\.assistants` is written `"beta\\.assistants"`.
161
186
  - Avoid `^`/`$` line anchors — matching runs against the whole file, not line-by-line; use `\b` word boundaries instead.
162
187
 
163
188
  ## Development
package/dist/cli.js CHANGED
@@ -103,6 +103,7 @@ function runScan(targetPath, opts) {
103
103
  vendor: f.deprecation.vendor,
104
104
  title: f.deprecation.title,
105
105
  severity: f.deprecation.severity,
106
+ match: f.deprecation.match,
106
107
  sunset_date: f.deprecation.sunset_date,
107
108
  migration_url: f.deprecation.migration_url,
108
109
  summary: f.deprecation.summary,
package/dist/data.js CHANGED
@@ -37,6 +37,7 @@ exports.loadDeprecations = loadDeprecations;
37
37
  const fs = __importStar(require("fs"));
38
38
  const path = __importStar(require("path"));
39
39
  const SEVERITIES = ["high", "medium", "low"];
40
+ const MATCH_MODES = ["pattern", "sdk", "version"];
40
41
  /**
41
42
  * Locate the bundled deprecations.json. Tries several candidate locations so the
42
43
  * tool works both when running the compiled output (dist/) from a published
@@ -80,16 +81,27 @@ function coerceDeprecation(raw) {
80
81
  const detect = r.detect;
81
82
  const sdk = detect && isStringArray(detect.sdk) ? detect.sdk : [];
82
83
  const patterns = detect && isStringArray(detect.patterns) ? detect.patterns : [];
83
- // An entry with neither SDKs nor patterns can never match drop it.
84
- if (sdk.length === 0 && patterns.length === 0)
84
+ // Default to "pattern" detection keys on real usage, not SDK presence.
85
+ const match = MATCH_MODES.includes(r.match)
86
+ ? r.match
87
+ : "pattern";
88
+ const version_range = isNonEmptyString(r.version_range)
89
+ ? r.version_range
90
+ : undefined;
91
+ // Drop entries that can never fire under their match mode.
92
+ if (match === "pattern" && patterns.length === 0)
93
+ return null;
94
+ if ((match === "sdk" || match === "version") && sdk.length === 0)
85
95
  return null;
86
96
  return {
87
97
  id: r.id,
88
98
  vendor: r.vendor,
89
99
  title: r.title,
90
100
  severity: r.severity,
101
+ match,
91
102
  sunset_date: typeof r.sunset_date === "string" ? r.sunset_date : "",
92
103
  detect: { sdk, patterns },
104
+ version_range,
93
105
  migration_url: typeof r.migration_url === "string" ? r.migration_url : "",
94
106
  summary: typeof r.summary === "string" ? r.summary : "",
95
107
  };
package/dist/report.js CHANGED
@@ -67,22 +67,24 @@ function sunsetPhrase(s, sunsetDate, now) {
67
67
  // Past or imminent sunsets are the urgent ones.
68
68
  return days <= 30 ? s.red(base + hint) : s.yellow(base + hint);
69
69
  }
70
- /** Group pattern matches by file into "path:line, line" summaries. */
70
+ /** One line per source location: "path:line → matched text". */
71
71
  function formatPatternMatches(s, finding) {
72
- const byFile = new Map();
73
- for (const pm of finding.patternMatches) {
74
- const arr = byFile.get(pm.file) ?? [];
75
- arr.push(pm.line);
76
- byFile.set(pm.file, arr);
77
- }
78
- const lines = [];
79
- for (const [file, nums] of byFile) {
80
- const uniqueSorted = Array.from(new Set(nums)).sort((a, b) => a - b);
81
- const shown = uniqueSorted.slice(0, 8);
82
- const more = uniqueSorted.length > shown.length
83
- ? s.gray(` +${uniqueSorted.length - shown.length} more`)
84
- : "";
85
- lines.push(`${s.cyan(file)}${s.gray(":")}${shown.join(s.gray(", "))}${more}`);
72
+ // De-duplicate identical file:line:text triples and order by file then line.
73
+ const seen = new Set();
74
+ const items = finding.patternMatches.filter((pm) => {
75
+ const key = `${pm.file}:${pm.line}:${pm.text}`;
76
+ if (seen.has(key))
77
+ return false;
78
+ seen.add(key);
79
+ return true;
80
+ });
81
+ items.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
82
+ const MAX = 12;
83
+ const shown = items.slice(0, MAX);
84
+ const lines = shown.map((pm) => `${s.cyan(pm.file)}${s.gray(":")}${pm.line} ${s.gray("→")} ${pm.text}`);
85
+ const extra = items.length - shown.length;
86
+ if (extra > 0) {
87
+ lines.push(s.gray(`+${extra} more location${extra === 1 ? "" : "s"}`));
86
88
  }
87
89
  return lines;
88
90
  }
package/dist/scanner.js CHANGED
@@ -133,9 +133,9 @@ function scanContent(content, relPath, compiled, sink) {
133
133
  if (seenLines.has(line))
134
134
  continue; // one record per line per pattern
135
135
  seenLines.add(line);
136
- const lineStart = lineStarts[line - 1];
137
- const nextStart = line < lineStarts.length ? lineStarts[line] : content.length;
138
- const text = content.slice(lineStart, nextStart).replace(/\r?\n$/, "").trim();
136
+ // Cite the matched substring itself (e.g. "beta.assistants"), normalized
137
+ // and length-capped, so the report points at exactly what triggered.
138
+ const text = (m[0] ?? "").replace(/\s+/g, " ").trim().slice(0, 120);
139
139
  if (!recorded) {
140
140
  recorded = [];
141
141
  sink.set(deprecation.id, recorded);
@@ -171,6 +171,57 @@ function matchManifests(deprecations, refs) {
171
171
  }
172
172
  return byId;
173
173
  }
174
+ /** Best-effort parse of a dotted numeric version from a declared string. */
175
+ function parseVersionNumbers(raw) {
176
+ if (!raw)
177
+ return null;
178
+ // Pull the first dotted-number run, ignoring ^ ~ >= <= operators and a "v" prefix.
179
+ const m = /(\d+(?:\.\d+)*)/.exec(raw);
180
+ if (!m)
181
+ return null;
182
+ return m[1].split(".").map((n) => parseInt(n, 10));
183
+ }
184
+ function compareVersions(a, b) {
185
+ const len = Math.max(a.length, b.length);
186
+ for (let i = 0; i < len; i++) {
187
+ const x = a[i] ?? 0;
188
+ const y = b[i] ?? 0;
189
+ if (x !== y)
190
+ return x < y ? -1 : 1;
191
+ }
192
+ return 0;
193
+ }
194
+ /**
195
+ * Best-effort check that a declared version satisfies a simple range such as
196
+ * "<3.0.0", ">=1.2.0", or "=2.1.0" / "2.1.0". Semver-ish: compares dotted numbers.
197
+ * Returns true when no range is given; false when a range is required but the
198
+ * declared version can't be parsed (stay conservative — don't over-flag).
199
+ */
200
+ function versionInRange(declared, range) {
201
+ if (!range)
202
+ return true; // no constraint → presence already established by caller
203
+ const rm = /^\s*(<=|>=|<|>|={1,3})?\s*v?(\d+(?:\.\d+)*)\s*$/.exec(range);
204
+ if (!rm)
205
+ return true; // unparseable range → don't invent a false constraint
206
+ const op = rm[1] || "=";
207
+ const target = rm[2].split(".").map((n) => parseInt(n, 10));
208
+ const have = parseVersionNumbers(declared);
209
+ if (!have)
210
+ return false;
211
+ const cmp = compareVersions(have, target);
212
+ switch (op) {
213
+ case "<":
214
+ return cmp < 0;
215
+ case "<=":
216
+ return cmp <= 0;
217
+ case ">":
218
+ return cmp > 0;
219
+ case ">=":
220
+ return cmp >= 0;
221
+ default:
222
+ return cmp === 0;
223
+ }
224
+ }
174
225
  /**
175
226
  * Scan a repository for deprecation usage.
176
227
  * @param root repo root to scan.
@@ -178,11 +229,15 @@ function matchManifests(deprecations, refs) {
178
229
  */
179
230
  function scanRepo(root, deprecations) {
180
231
  const absRoot = path.resolve(root);
181
- // 1. Manifest scan.
232
+ // Partition by match mode: "pattern" entries key on real source usage, while
233
+ // "sdk"/"version" entries key on the manifest.
234
+ const patternDeps = deprecations.filter((d) => d.match === "pattern");
235
+ const manifestDeps = deprecations.filter((d) => d.match === "sdk" || d.match === "version");
236
+ // 1. Manifest scan (drives sdk/version entries; also lists the manifests read).
182
237
  const { refs, manifests } = (0, manifests_1.collectManifestDeps)(absRoot);
183
- const manifestMatches = matchManifests(deprecations, refs);
184
- // 2. Inline scan.
185
- const compiled = compileDeprecations(deprecations);
238
+ const manifestMatches = matchManifests(manifestDeps, refs);
239
+ // 2. Inline source scan (drives pattern entries — usage, not mere presence).
240
+ const compiled = compileDeprecations(patternDeps);
186
241
  const patternSink = new Map();
187
242
  const files = fast_glob_1.default.sync([`**/*.{${SOURCE_EXTENSIONS.join(",")}}`], {
188
243
  cwd: absRoot,
@@ -215,14 +270,30 @@ function scanRepo(root, deprecations) {
215
270
  scannedFiles++;
216
271
  scanContent(content, rel, compiled, patternSink);
217
272
  }
218
- // 3. Combine: detected if a manifest match OR a pattern match exists.
273
+ // 3. Build findings one per deprecation, evaluated per its match mode.
219
274
  const findings = [];
220
275
  for (const deprecation of deprecations) {
276
+ if (deprecation.match === "pattern") {
277
+ // Flag ONLY on a real source hit; manifest/SDK presence is irrelevant here.
278
+ const pm = patternSink.get(deprecation.id) ?? [];
279
+ if (pm.length === 0)
280
+ continue;
281
+ findings.push({ deprecation, manifestMatches: [], patternMatches: pm });
282
+ continue;
283
+ }
284
+ // "sdk" / "version": evaluate against the manifest.
221
285
  const mm = manifestMatches.get(deprecation.id) ?? [];
222
- const pm = patternSink.get(deprecation.id) ?? [];
223
- if (mm.length === 0 && pm.length === 0)
286
+ if (mm.length === 0)
224
287
  continue;
225
- findings.push({ deprecation, manifestMatches: mm, patternMatches: pm });
288
+ if (deprecation.match === "version") {
289
+ const inRange = mm.filter((m) => versionInRange(m.version, deprecation.version_range));
290
+ if (inRange.length === 0)
291
+ continue;
292
+ findings.push({ deprecation, manifestMatches: inRange, patternMatches: [] });
293
+ continue;
294
+ }
295
+ // "sdk": mere presence in a manifest is enough.
296
+ findings.push({ deprecation, manifestMatches: mm, patternMatches: [] });
226
297
  }
227
298
  return { scannedFiles, manifestsScanned: manifests, findings };
228
299
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arol-ai",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Scan a local repo for upcoming third-party API/SDK deprecations. Fully local — no network, no telemetry, your code never leaves the machine.",
5
5
  "keywords": [
6
6
  "deprecation",
@@ -4,6 +4,7 @@
4
4
  "vendor": "OpenAI",
5
5
  "title": "Assistants API (beta)",
6
6
  "severity": "high",
7
+ "match": "pattern",
7
8
  "sunset_date": "2026-08-26",
8
9
  "detect": {
9
10
  "sdk": ["openai"],
@@ -23,6 +24,7 @@
23
24
  "vendor": "Anthropic",
24
25
  "title": "Claude Sonnet 4 & Opus 4",
25
26
  "severity": "high",
27
+ "match": "pattern",
26
28
  "sunset_date": "2026-06-15",
27
29
  "detect": {
28
30
  "sdk": ["@anthropic-ai/sdk", "anthropic"],
@@ -42,6 +44,7 @@
42
44
  "vendor": "Anthropic",
43
45
  "title": "Retired Claude models (Haiku 3, 3.5/3.7 Sonnet, Claude 2.x)",
44
46
  "severity": "high",
47
+ "match": "pattern",
45
48
  "sunset_date": "2026-04-20",
46
49
  "detect": {
47
50
  "sdk": ["@anthropic-ai/sdk", "anthropic"],
@@ -63,6 +66,7 @@
63
66
  "vendor": "Google (Gemini)",
64
67
  "title": "Gemini 2.5 Pro / Flash / Flash-Lite",
65
68
  "severity": "high",
69
+ "match": "pattern",
66
70
  "sunset_date": "2026-10-16",
67
71
  "detect": {
68
72
  "sdk": ["@google/generative-ai", "@google/genai", "google-generativeai"],
@@ -81,6 +85,7 @@
81
85
  "vendor": "Google (Gemini)",
82
86
  "title": "Gemini 2.0 Flash family",
83
87
  "severity": "high",
88
+ "match": "pattern",
84
89
  "sunset_date": "2026-06-01",
85
90
  "detect": {
86
91
  "sdk": ["@google/generative-ai", "@google/genai", "google-generativeai"],
@@ -100,6 +105,7 @@
100
105
  "vendor": "Google (Gemini)",
101
106
  "title": "Gemini 1.0 / 1.5 models",
102
107
  "severity": "high",
108
+ "match": "pattern",
103
109
  "sunset_date": "2026-04-29",
104
110
  "detect": {
105
111
  "sdk": ["@google/generative-ai", "@google/genai", "google-generativeai"],