arol-ai 0.1.2 → 0.1.3

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
@@ -76,9 +76,14 @@ Flags **only** when your code actually references the deprecated API in a scanne
76
76
 
77
77
  This split is what keeps a marketing page that mentions *"GPT-4o, GPT-4.1, and o4-mini"* from being reported as deprecated usage: those names aren't quoted string literals, so `detect.models` ignores them. Only something like `model: "o4-mini"` counts.
78
78
 
79
+ Two more layers keep matches context-aware:
80
+
81
+ - **Language scoping (`applies_to`).** Each entry lists the file extensions its signals are valid in (e.g. `["py"]`, `["js","ts","jsx","tsx","mjs"]`, or `["*"]` for model strings). An entry is only tested against files with a matching extension, so a Python-only pattern like `openai.ChatCompletion` never fires in a `.tsx` file. Defaults to `["*"]` when omitted.
82
+ - **Comment stripping.** Before matching, comments are blanked out per language — `//`, `/* */`, JSX `{/* */}`, and `#` (Python). Stripping is string-aware: a marker inside a string literal (e.g. the `//` in `"https://…"`) is **not** treated as a comment, and offsets are preserved so reported line numbers stay exact. A commented-out `model: "gpt-4o"` is ignored; the real call on the next line is not.
83
+
79
84
  Each hit records the **file path, line number, and matched text**, and one deprecation aggregates **all** of its matched locations into a single finding.
80
85
 
81
- > Having the `openai` package in `requirements.txt` does **not** flag the Assistants API deprecation. Your code has to actually use `beta.assistants` / a deprecated model id (etc.).
86
+ > Having the `openai` package in `requirements.txt` does **not** flag the Assistants API deprecation. Your code has to actually use `beta.assistants` / a deprecated model id (etc.), in a file of the right language, outside comments.
82
87
 
83
88
  **Files scanned / skipped**
84
89
 
@@ -142,6 +147,7 @@ The dataset is either a bare array of entries, or a `{ "deprecations": [ ... ] }
142
147
  "title": "Assistants API (beta)", // short headline (required)
143
148
  "severity": "high", // "high" | "medium" | "low" (required)
144
149
  "match": "pattern", // "pattern" (default) | "sdk" | "version"
150
+ "applies_to": ["py","js","ts","jsx","tsx","mjs"], // extensions to test; ["*"] = any
145
151
  "sunset_date": "2026-08-26", // ISO YYYY-MM-DD, or "" if no fixed date
146
152
  "detect": {
147
153
  "sdk": ["openai"], // scope hint for "pattern"; the trigger for "sdk"/"version"
@@ -157,7 +163,7 @@ The dataset is either a bare array of entries, or a `{ "deprecations": [ ... ] }
157
163
  }
158
164
  ```
159
165
 
160
- A model-retirement entry uses `detect.models` so it only fires on a quoted model id, never on prose:
166
+ A model-retirement entry uses `detect.models` (and `applies_to: ["*"]`) so it only fires on a quoted model id, in any language, never on prose:
161
167
 
162
168
  ```jsonc
163
169
  {
@@ -166,6 +172,7 @@ A model-retirement entry uses `detect.models` so it only fires on a quoted model
166
172
  "title": "GPT-4 family models (API shutdown)",
167
173
  "severity": "high",
168
174
  "match": "pattern",
175
+ "applies_to": ["*"],
169
176
  "sunset_date": "2026-10-23",
170
177
  "detect": {
171
178
  "sdk": ["openai"],
@@ -177,6 +184,23 @@ A model-retirement entry uses `detect.models` so it only fires on a quoted model
177
184
  }
178
185
  ```
179
186
 
187
+ A Python-only entry scopes itself with `applies_to: ["py"]`, so its patterns never fire in JS/TSX files that merely mention the API in prose:
188
+
189
+ ```jsonc
190
+ {
191
+ "id": "openai-python-v0-syntax",
192
+ "vendor": "OpenAI",
193
+ "title": "Legacy openai-python v0 call syntax",
194
+ "severity": "high",
195
+ "match": "pattern",
196
+ "applies_to": ["py"],
197
+ "sunset_date": "2023-11-06",
198
+ "detect": { "sdk": ["openai"], "patterns": ["openai\\.ChatCompletion"] },
199
+ "migration_url": "https://github.com/openai/openai-python/discussions/742",
200
+ "summary": "Instantiate a client: client.chat.completions.create(...)."
201
+ }
202
+ ```
203
+
180
204
  A `version` entry instead flags on the installed SDK version (no patterns needed):
181
205
 
182
206
  ```jsonc
@@ -203,6 +227,7 @@ A `version` entry instead flags on the installed SDK version (no patterns needed
203
227
  | `title` | string | ✓ | Short headline for the finding. |
204
228
  | `severity` | `"high"` \| `"medium"` \| `"low"` | ✓ | Drives color, sort order, and `--fail-on`. |
205
229
  | `match` | `"pattern"` \| `"sdk"` \| `"version"` | – | How the entry is triggered. **Defaults to `"pattern"`** when omitted. See [How detection works](#how-detection-works). |
230
+ | `applies_to` | string[] | – | File extensions (no dot) the entry's patterns/models are tested against, e.g. `["py"]` or `["js","ts","jsx","tsx","mjs"]`. Use `["*"]` for any file (model strings). **Defaults to `["*"]`** when omitted. |
206
231
  | `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"`. |
207
232
  | `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"*). |
208
233
  | `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. |
package/dist/data.js CHANGED
@@ -89,6 +89,11 @@ function coerceDeprecation(raw) {
89
89
  const version_range = isNonEmptyString(r.version_range)
90
90
  ? r.version_range
91
91
  : undefined;
92
+ // Language scoping: extensions this entry's patterns are valid in.
93
+ // Normalize to lowercase, dot-stripped; default to ["*"] (match any file).
94
+ const applies_to = isStringArray(r.applies_to) && r.applies_to.length > 0
95
+ ? r.applies_to.map((e) => e.toLowerCase().replace(/^\./, ""))
96
+ : ["*"];
92
97
  // Drop entries that can never fire under their match mode.
93
98
  // A "pattern" entry fires on either a raw pattern OR a model-string match.
94
99
  if (match === "pattern" && patterns.length === 0 && models.length === 0)
@@ -101,6 +106,7 @@ function coerceDeprecation(raw) {
101
106
  title: r.title,
102
107
  severity: r.severity,
103
108
  match,
109
+ applies_to,
104
110
  sunset_date: typeof r.sunset_date === "string" ? r.sunset_date : "",
105
111
  detect: { sdk, patterns, models },
106
112
  version_range,
package/dist/scanner.js CHANGED
@@ -121,9 +121,110 @@ function compileDeprecations(deprecations) {
121
121
  // Defensive: a pathological family name must not crash the scan.
122
122
  }
123
123
  }
124
- return { deprecation, regexes };
124
+ const appliesTo = new Set((deprecation.applies_to.length > 0 ? deprecation.applies_to : ["*"]).map((e) => e.toLowerCase()));
125
+ return { deprecation, regexes, appliesTo };
125
126
  });
126
127
  }
128
+ /** True if a compiled entry should be tested against a file with this extension. */
129
+ function appliesToExt(compiled, ext) {
130
+ return compiled.appliesTo.has("*") || compiled.appliesTo.has(ext);
131
+ }
132
+ function commentConfig(ext) {
133
+ switch (ext) {
134
+ case "js":
135
+ case "mjs":
136
+ case "cjs":
137
+ case "jsx":
138
+ case "ts":
139
+ case "mts":
140
+ case "cts":
141
+ case "tsx":
142
+ case "go":
143
+ return { line: ["//"], block: [["/*", "*/"]], strings: ["'", '"', "`"], triple: [] };
144
+ case "py":
145
+ return { line: ["#"], block: [], strings: ["'", '"'], triple: ['"""', "'''"] };
146
+ default:
147
+ return null;
148
+ }
149
+ }
150
+ /**
151
+ * Replace comments with spaces so they don't match, while preserving the exact
152
+ * byte length and all newlines — line/column offsets stay correct. String literals
153
+ * (including their contents) are left intact, so a comment marker inside a string
154
+ * (e.g. "https://…") is NOT treated as a comment.
155
+ */
156
+ function stripComments(src, cfg) {
157
+ const out = src.split("");
158
+ const n = src.length;
159
+ const at = (s, i) => src.startsWith(s, i);
160
+ const blank = (from, to) => {
161
+ for (let k = from; k < to; k++)
162
+ if (out[k] !== "\n")
163
+ out[k] = " ";
164
+ };
165
+ let i = 0;
166
+ while (i < n) {
167
+ // Triple-quoted strings (Python) — checked before single quotes.
168
+ let matched = false;
169
+ for (const t of cfg.triple) {
170
+ if (at(t, i)) {
171
+ i += t.length;
172
+ while (i < n && !at(t, i))
173
+ i++;
174
+ i += t.length;
175
+ matched = true;
176
+ break;
177
+ }
178
+ }
179
+ if (matched)
180
+ continue;
181
+ // Ordinary string literals — preserve contents verbatim.
182
+ for (const q of cfg.strings) {
183
+ if (src[i] === q) {
184
+ i++;
185
+ while (i < n && src[i] !== q) {
186
+ if (src[i] === "\\")
187
+ i++; // skip escaped char
188
+ i++;
189
+ }
190
+ i++; // closing quote (or past end if unterminated)
191
+ matched = true;
192
+ break;
193
+ }
194
+ }
195
+ if (matched)
196
+ continue;
197
+ // Block comments.
198
+ for (const [open, close] of cfg.block) {
199
+ if (at(open, i)) {
200
+ const end = src.indexOf(close, i + open.length);
201
+ const stop = end === -1 ? n : end + close.length;
202
+ blank(i, stop);
203
+ i = stop;
204
+ matched = true;
205
+ break;
206
+ }
207
+ }
208
+ if (matched)
209
+ continue;
210
+ // Line comments.
211
+ for (const lc of cfg.line) {
212
+ if (at(lc, i)) {
213
+ let k = i;
214
+ while (k < n && src[k] !== "\n")
215
+ k++;
216
+ blank(i, k);
217
+ i = k;
218
+ matched = true;
219
+ break;
220
+ }
221
+ }
222
+ if (matched)
223
+ continue;
224
+ i++;
225
+ }
226
+ return out.join("");
227
+ }
127
228
  /** Precompute the byte offset at which each line starts. */
128
229
  function computeLineStarts(content) {
129
230
  const starts = [0];
@@ -354,7 +455,16 @@ function scanRepo(root, deprecations, options = {}) {
354
455
  continue;
355
456
  }
356
457
  scannedFiles++;
357
- scanContent(content, rel, compiled, patternSink);
458
+ // Language scoping: only test entries valid for this file's extension.
459
+ const ext = path.extname(rel).slice(1).toLowerCase();
460
+ const applicable = compiled.filter((c) => appliesToExt(c, ext));
461
+ if (applicable.length === 0)
462
+ continue;
463
+ // Comment stripping: match against de-commented source so mentions in
464
+ // comments don't count. Offsets are preserved, so line numbers stay correct.
465
+ const cfg = commentConfig(ext);
466
+ const scanText = cfg ? stripComments(content, cfg) : content;
467
+ scanContent(scanText, rel, applicable, patternSink);
358
468
  }
359
469
  // 3. Build findings — one per deprecation, evaluated per its match mode.
360
470
  const findings = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arol-ai",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
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",
@@ -5,6 +5,7 @@
5
5
  "title": "Assistants API (beta)",
6
6
  "severity": "high",
7
7
  "match": "pattern",
8
+ "applies_to": ["py", "js", "ts", "jsx", "tsx", "mjs"],
8
9
  "sunset_date": "2026-08-26",
9
10
  "detect": {
10
11
  "sdk": ["openai"],
@@ -25,6 +26,7 @@
25
26
  "title": "Claude Sonnet 4 & Opus 4",
26
27
  "severity": "high",
27
28
  "match": "pattern",
29
+ "applies_to": ["*"],
28
30
  "sunset_date": "2026-06-15",
29
31
  "detect": {
30
32
  "sdk": ["@anthropic-ai/sdk", "anthropic"],
@@ -46,6 +48,7 @@
46
48
  "title": "Retired Claude models (Haiku 3, 3.5/3.7 Sonnet, Claude 2.x)",
47
49
  "severity": "high",
48
50
  "match": "pattern",
51
+ "applies_to": ["*"],
49
52
  "sunset_date": "2026-04-20",
50
53
  "detect": {
51
54
  "sdk": ["@anthropic-ai/sdk", "anthropic"],
@@ -69,6 +72,7 @@
69
72
  "title": "Gemini 2.5 Pro / Flash / Flash-Lite",
70
73
  "severity": "high",
71
74
  "match": "pattern",
75
+ "applies_to": ["*"],
72
76
  "sunset_date": "2026-10-16",
73
77
  "detect": {
74
78
  "sdk": ["@google/generative-ai", "@google/genai", "google-generativeai"],
@@ -85,6 +89,7 @@
85
89
  "title": "Gemini 2.0 Flash family",
86
90
  "severity": "high",
87
91
  "match": "pattern",
92
+ "applies_to": ["*"],
88
93
  "sunset_date": "2026-06-01",
89
94
  "detect": {
90
95
  "sdk": ["@google/generative-ai", "@google/genai", "google-generativeai"],
@@ -106,11 +111,17 @@
106
111
  "title": "Gemini 1.0 / 1.5 models",
107
112
  "severity": "high",
108
113
  "match": "pattern",
114
+ "applies_to": ["*"],
109
115
  "sunset_date": "2026-04-29",
110
116
  "detect": {
111
117
  "sdk": ["@google/generative-ai", "@google/genai", "google-generativeai"],
112
118
  "patterns": [],
113
- "models": ["gemini-1.5-pro", "gemini-1.5-flash", "gemini-1.0-pro", "gemini-pro"]
119
+ "models": [
120
+ "gemini-1.5-pro",
121
+ "gemini-1.5-flash",
122
+ "gemini-1.0-pro",
123
+ "gemini-pro"
124
+ ]
114
125
  },
115
126
  "migration_url": "https://ai.google.dev/gemini-api/docs/deprecations",
116
127
  "summary": "All Gemini 1.0 and 1.5 models are shut down and return 404. Migrate to Gemini 2.5+ or 3.x.",
@@ -122,6 +133,7 @@
122
133
  "title": "GPT-4 family models (API shutdown)",
123
134
  "severity": "high",
124
135
  "match": "pattern",
136
+ "applies_to": ["*"],
125
137
  "sunset_date": "2026-10-23",
126
138
  "date_confidence": "verify",
127
139
  "detect": {
@@ -149,6 +161,7 @@
149
161
  "title": "Retired legacy OpenAI models (GPT-3 era, early snapshots)",
150
162
  "severity": "high",
151
163
  "match": "pattern",
164
+ "applies_to": ["*"],
152
165
  "sunset_date": "2024-01-04",
153
166
  "detect": {
154
167
  "sdk": ["openai"],
@@ -174,6 +187,7 @@
174
187
  "title": "Removed legacy Stripe.js methods",
175
188
  "severity": "high",
176
189
  "match": "pattern",
190
+ "applies_to": ["js", "ts", "jsx", "tsx", "mjs"],
177
191
  "sunset_date": "2026-03-25",
178
192
  "detect": {
179
193
  "sdk": ["@stripe/stripe-js", "stripe"],
@@ -197,6 +211,7 @@
197
211
  "title": "Sources API / legacy Charges API",
198
212
  "severity": "medium",
199
213
  "match": "pattern",
214
+ "applies_to": ["js", "ts", "jsx", "tsx", "mjs"],
200
215
  "sunset_date": "2024-05-15",
201
216
  "detect": {
202
217
  "sdk": ["stripe"],
@@ -212,6 +227,7 @@
212
227
  "title": "Notify API",
213
228
  "severity": "high",
214
229
  "match": "pattern",
230
+ "applies_to": ["py", "js", "ts", "jsx", "tsx", "mjs"],
215
231
  "sunset_date": "2027-01-31",
216
232
  "date_confidence": "verify",
217
233
  "detect": {
@@ -228,6 +244,7 @@
228
244
  "title": "Programmable Chat API",
229
245
  "severity": "high",
230
246
  "match": "pattern",
247
+ "applies_to": ["py", "js", "ts", "jsx", "tsx", "mjs"],
231
248
  "sunset_date": "2022-07-25",
232
249
  "detect": {
233
250
  "sdk": ["twilio", "twilio-chat"],
@@ -243,6 +260,7 @@
243
260
  "title": "AWS SDK for JavaScript v2",
244
261
  "severity": "medium",
245
262
  "match": "sdk",
263
+ "applies_to": ["js", "ts", "jsx", "tsx", "mjs"],
246
264
  "sunset_date": "2025-09-08",
247
265
  "detect": {
248
266
  "sdk": ["aws-sdk"],
@@ -258,6 +276,7 @@
258
276
  "title": "Legacy API keys (hapikey)",
259
277
  "severity": "high",
260
278
  "match": "pattern",
279
+ "applies_to": ["py", "js", "ts", "jsx", "tsx", "mjs"],
261
280
  "sunset_date": "2022-11-30",
262
281
  "detect": {
263
282
  "sdk": ["@hubspot/api-client"],
@@ -266,5 +285,70 @@
266
285
  "migration_url": "https://developers.hubspot.com/changelog/upcoming-api-key-sunset",
267
286
  "summary": "HubSpot deprecated legacy API keys (the hapikey query param) on Nov 30, 2022; requests using hapikey now fail. Migrate to Private App access tokens (Bearer auth). The eCommerce Bridge and Accounting Extension APIs were also sunset Dec 1, 2022.",
268
287
  "source": "https://developers.hubspot.com/changelog/upcoming-api-key-sunset"
288
+ },
289
+ {
290
+ "id": "openai-python-v0-syntax",
291
+ "vendor": "OpenAI",
292
+ "title": "Legacy openai-python v0 call syntax",
293
+ "severity": "high",
294
+ "match": "pattern",
295
+ "applies_to": ["py"],
296
+ "sunset_date": "2023-11-06",
297
+ "detect": {
298
+ "sdk": ["openai"],
299
+ "patterns": [
300
+ "openai\\.ChatCompletion",
301
+ "openai\\.Completion\\.create",
302
+ "openai\\.Embedding\\.create",
303
+ "openai\\.Moderation\\.create"
304
+ ]
305
+ },
306
+ "migration_url": "https://github.com/openai/openai-python/discussions/742",
307
+ "summary": "openai-python v1.0 (Nov 2023) removed module-level calls. openai.ChatCompletion/Completion/Embedding.create now raise APIRemovedInV1. Instantiate a client (client = OpenAI(); client.chat.completions.create(...)) or run `openai migrate`.",
308
+ "source": "https://github.com/openai/openai-python/issues/2172"
309
+ },
310
+ {
311
+ "id": "vercel-ai-sdk-v5-removed",
312
+ "vendor": "Vercel (AI SDK)",
313
+ "title": "AI SDK v4→v5/v6 removed APIs",
314
+ "severity": "medium",
315
+ "match": "pattern",
316
+ "applies_to": ["js", "ts", "jsx", "tsx", "mjs"],
317
+ "sunset_date": "2025-07-31",
318
+ "detect": {
319
+ "sdk": ["ai"],
320
+ "patterns": [
321
+ "from ['\"]ai/react['\"]",
322
+ "from ['\"]ai/openai['\"]",
323
+ "experimental_streamText",
324
+ "experimental_generateText",
325
+ "StreamingTextResponse"
326
+ ]
327
+ },
328
+ "migration_url": "https://ai-sdk.dev/docs/migration-guides/migration-guide-5-0",
329
+ "summary": "AI SDK v5 (Jul 2025) removed deprecated APIs: 'ai/react' → '@ai-sdk/react', experimental_streamText → streamText, StreamingTextResponse removed, useChat maxSteps removed. v6 moved provider imports ('ai/openai' → '@ai-sdk/openai').",
330
+ "source": "https://ai-sdk.dev/docs/migration-guides/migration-guide-5-0"
331
+ },
332
+ {
333
+ "id": "langchain-legacy-imports",
334
+ "vendor": "LangChain",
335
+ "title": "Legacy LangChain imports & chains",
336
+ "severity": "medium",
337
+ "match": "pattern",
338
+ "applies_to": ["py"],
339
+ "sunset_date": "2024-05-01",
340
+ "detect": {
341
+ "sdk": ["langchain"],
342
+ "patterns": [
343
+ "from langchain\\.llms import",
344
+ "from langchain\\.chat_models import",
345
+ "from langchain\\.embeddings import",
346
+ "initialize_agent",
347
+ "LLMChain"
348
+ ]
349
+ },
350
+ "migration_url": "https://python.langchain.com/docs/versions/v0_2/",
351
+ "summary": "Pre-0.2 LangChain imports are deprecated/removed: langchain.llms/chat_models/embeddings → langchain_community or provider packages (langchain_openai). LLMChain and initialize_agent are deprecated (use LCEL / create_agent; legacy moved to langchain-classic in 1.0).",
352
+ "source": "https://github.com/langchain-ai/langchain/discussions/19083"
269
353
  }
270
354
  ]