activate-agentmd 2.4.0 → 2.5.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.
@@ -26,12 +26,6 @@ const PATTERNS = [
26
26
  test: /\bSELECT\s+\*\s+FROM\b/i,
27
27
  message: "`SELECT *` — name the columns you need.",
28
28
  },
29
- {
30
- id: "pydantic-v1-api", standard: "Backend/pydantic", severity: "medium", files: [".py"],
31
- test: /\.(dict|json|parse_obj|parse_raw)\(|@(validator|root_validator)\b|class Config:/,
32
- not: /model_dump|model_validate/,
33
- message: "Pydantic v1 API (`.dict()`, `.json()`, `@validator`, `class Config`) — use `model_dump`, `model_validate`, `field_validator`, `ConfigDict`.",
34
- },
35
29
  {
36
30
  id: "eval-exec", standard: "Security/command-injection", severity: "high",
37
31
  files: [".js", ".ts", ".tsx", ".py", ".rb", ".php"],
@@ -82,11 +76,6 @@ const PATTERNS = [
82
76
  not: /process\.env|os\.environ|getenv|\$\{|<[A-Z_]+>|example|placeholder|changeme|xxx/i,
83
77
  message: "Looks like a hard-coded secret — read it from the environment or a secret store.",
84
78
  },
85
- {
86
- id: "next-sync-request-api", standard: "Frontend/nextjs", severity: "medium", files: [".ts", ".tsx"],
87
- test: /\b(cookies|headers)\(\)\.(get|set|getAll|has)\(/,
88
- message: "Next.js 15+: `cookies()` / `headers()` are async — `(await cookies()).get(...)`.",
89
- },
90
79
  {
91
80
  id: "console-log-in-server", standard: "Backend/logging", severity: "low", files: [".ts", ".js"],
92
81
  test: /^\s*console\.log\(/,
@@ -94,12 +83,17 @@ const PATTERNS = [
94
83
  },
95
84
  ];
96
85
 
86
+ // `pydantic-v1-api` and `next-sync-request-api` used to live here and fired
87
+ // on every project — flagging `.dict()` in a repo pinned to Pydantic v1, where
88
+ // it is the correct call. They are version checks now, in pins.json, and run
89
+ // only when that major is detected (see versions.js pinPatterns).
90
+
97
91
  /**
98
92
  * Run the patterns over a unified diff. Only added lines are checked, and the
99
93
  * file's extension must be in the pattern's list. Returns review-shaped
100
94
  * findings: { file, line, severity, standard, rule, issue, fix, pattern }.
101
95
  */
102
- function runPatterns(diff, installedStandardIds) {
96
+ function runPatterns(diff, installedStandardIds, patterns = PATTERNS) {
103
97
  const findings = [];
104
98
  let file = null, ext = "", line = 0;
105
99
  for (const raw of diff.split("\n")) {
@@ -119,13 +113,16 @@ function runPatterns(diff, installedStandardIds) {
119
113
  if (!raw.startsWith("+")) continue;
120
114
  const code = raw.slice(1);
121
115
  if (/^\s*(\/\/|#|\*|<!--)/.test(code)) continue; // comments
122
- for (const p of PATTERNS) {
116
+ for (const p of patterns) {
123
117
  if (!p.files.includes(ext)) continue;
124
118
  if (!p.test.test(code)) continue;
125
119
  if (p.not && p.not.test(code)) continue;
126
120
  // Only report against standards the project actually installed, unless
127
121
  // the caller passes null to mean "everything".
128
- if (installedStandardIds && !installedStandardIds.has(p.standard)) continue;
122
+ // Pin checks are scoped by the detected version instead: whether the
123
+ // project installed a Next.js standard says nothing about whether it
124
+ // runs Next 16.
125
+ if (!p.pin && installedStandardIds && !installedStandardIds.has(p.standard)) continue;
129
126
  findings.push({ file, line, severity: p.severity, standard: p.standard, rule: p.id, issue: p.message, fix: null, pattern: true });
130
127
  }
131
128
  }
package/src/lib/pro.js CHANGED
@@ -21,7 +21,7 @@ const { verify, fingerprint, daysLeft } = require("./license");
21
21
  const { resolveKey } = require("./credentials");
22
22
  const { cmd } = require("./invocation");
23
23
 
24
- const UPGRADE_URL = process.env.AGENTMD_PRO_URL || "https://agent-dot-md.vercel.app/pro";
24
+ const UPGRADE_URL = process.env.AGENTMD_PRO_URL || "https://agentmd.pages.dev/pro";
25
25
 
26
26
  /**
27
27
  * Every gated capability, with the sentence shown when it is reached without
@@ -28,7 +28,7 @@ const crypto = require("crypto");
28
28
  const { configDir } = require("./credentials");
29
29
 
30
30
  const FILE = "telemetry.json";
31
- const ENDPOINT = process.env.AGENTMD_TELEMETRY_URL || "https://agent-dot-md.vercel.app/api/telemetry";
31
+ const ENDPOINT = process.env.AGENTMD_TELEMETRY_URL || "https://agentmd.pages.dev/api/telemetry";
32
32
  const settingsPath = () => path.join(configDir(), FILE);
33
33
 
34
34
  /** Exactly what may be sent. A field not in here cannot reach the wire. */
@@ -20,112 +20,31 @@
20
20
  const fs = require("fs");
21
21
  const path = require("path");
22
22
 
23
- /** lib -> major -> rules. Keys are npm names, PyPI names, or "go" / "python". */
24
- const PINS = {
25
- next: {
26
- label: "Next.js",
27
- 13: ["App Router (`app/`) exists but `pages/` is still common; check which this project uses before adding routes."],
28
- 14: ["App Router with Server Actions (stable). Navigate with `next/navigation`, not `next/router`, inside `app/`."],
29
- 15: [
30
- "Request APIs are async: `await params`, `await searchParams`, `await cookies()`, `await headers()` — synchronous access is deprecated.",
31
- "`fetch` is no longer cached by default; opt in with `cache: 'force-cache'` or `revalidate`.",
32
- "Ships React 19: `ref` is a prop, `use()` and `useActionState` are available.",
33
- ],
34
- 16: [
35
- "Turbopack is the default bundler; do not add webpack-only config without checking it is still needed.",
36
- "Request APIs are async only — `params`, `searchParams`, `cookies()`, `headers()` must be awaited; synchronous access was removed.",
37
- "`middleware.ts` is `proxy.ts`; the old filename is not picked up.",
38
- "Ships React 19: `ref` is a prop, `use()` and `useActionState` are available.",
39
- ],
40
- },
41
- react: {
42
- label: "React",
43
- 18: ["Mount with `createRoot` (not `ReactDOM.render`). Effects double-invoke in StrictMode during development — write them idempotently."],
44
- 19: [
45
- "`ref` is a regular prop on function components — do not use `forwardRef`.",
46
- "Use `use()` to read promises/context, `useActionState` and `useFormStatus` for forms; Actions can be passed to `<form action>`.",
47
- "`propTypes` and `defaultProps` on function components are ignored; use TypeScript and default parameters.",
48
- ],
49
- },
50
- pydantic: {
51
- label: "Pydantic",
52
- 1: ["Pydantic v1 API: `.dict()`, `.json()`, `@validator`, `class Config`. Do not use v2 names (`model_dump`, `field_validator`)."],
53
- 2: [
54
- "Use `.model_dump()` / `.model_dump_json()` / `.model_validate()` — `.dict()`, `.json()`, `.parse_obj()` are deprecated v1 names.",
55
- "Validators are `@field_validator` and `@model_validator`; `@validator` and `@root_validator` are deprecated.",
56
- "Configure with `model_config = ConfigDict(...)`, not an inner `class Config`. Use `TypeAdapter` for non-model types.",
57
- ],
58
- },
59
- sqlalchemy: {
60
- label: "SQLAlchemy",
61
- 2: [
62
- "Use 2.0 style: `select()` with `session.execute()` / `session.scalars()`; `session.query()` is legacy.",
63
- "Declare models with `DeclarativeBase`, `Mapped[...]` and `mapped_column()`; `declarative_base()` and `Column` are legacy.",
64
- ],
65
- },
66
- django: {
67
- label: "Django",
68
- 5: ["Django 5: `db_default` and `GeneratedField` exist; `USE_L10N` was removed; async ORM methods (`aget`, `acreate`, `afirst`) are available."],
69
- },
70
- prisma: {
71
- label: "Prisma",
72
- 5: ["Prisma 5: `prisma.$extends` for extensions; `@prisma/client` generated in `node_modules` — run `prisma generate` after schema changes."],
73
- 6: ["Prisma 6: same API as 5; `omit` in queries is stable. Run `prisma generate` after schema changes."],
74
- 7: [
75
- "Prisma 7 has no Rust query engine by default: instantiate the client with a driver adapter (for example `@prisma/adapter-pg`), not a bare `new PrismaClient()`.",
76
- "Connection config lives in `prisma.config.ts`, not in the schema's `datasource url`.",
77
- ],
78
- },
79
- tailwindcss: {
80
- label: "Tailwind CSS",
81
- 3: ["Tailwind v3: `tailwind.config.js` with `content` globs, `@tailwind base/components/utilities` in CSS."],
82
- 4: [
83
- "Tailwind v4 is CSS-first: `@import \"tailwindcss\"`, design tokens in `@theme`, `@custom-variant` — there is no `tailwind.config.js` unless the project added `@config`.",
84
- "`@apply` in a separate CSS file needs `@reference`. Renamed utilities: `shadow-sm`→`shadow-xs`, `outline-none`→`outline-hidden`, `ring`→`ring-3`, `rounded-sm`→`rounded-xs`.",
85
- ],
86
- },
87
- express: {
88
- label: "Express",
89
- 5: [
90
- "Express 5: rejected promises from async handlers reach error middleware automatically — no `next(err)` wrappers needed.",
91
- "`req.query` is a read-only getter; route syntax is path-to-regexp v8 (`/{*splat}` instead of `*`, optional segments with `{}`); `res.redirect('back')` is gone.",
92
- ],
93
- },
94
- zod: {
95
- label: "Zod",
96
- 4: [
97
- "Zod 4: use top-level formats `z.email()`, `z.uuid()`, `z.url()` — `z.string().email()` is deprecated.",
98
- "Error details are `.issues` (not `.errors`); the `message` option is `error`; use `z.strictObject` / `z.looseObject` instead of `.strict()` / `.passthrough()`.",
99
- ],
100
- },
101
- eslint: {
102
- label: "ESLint",
103
- 9: ["ESLint 9 uses flat config (`eslint.config.js`); `.eslintrc*` is ignored unless `ESLINT_USE_FLAT_CONFIG=false`."],
104
- },
105
- vue: {
106
- label: "Vue",
107
- 3: ["Vue 3: Composition API with `<script setup>`, `defineProps` / `defineEmits`; Options API still works. Vue 2 is end-of-life — do not add Vue 2 patterns like `Vue.extend`."],
108
- },
109
- go: {
110
- label: "Go",
111
- // keyed by "1.NN" from the go directive
112
- "1.22": ["Go 1.22+: loop variables are per-iteration; `for i := range 10` works; `net/http` ServeMux supports methods and wildcards (`mux.HandleFunc(\"GET /items/{id}\", …)`)."],
113
- "1.23": ["Go 1.23+: range-over-func iterators (`iter.Seq`); everything from 1.22 (per-iteration loop vars, method patterns in ServeMux)."],
114
- "1.24": ["Go 1.24+: generic type aliases; `testing.B.Loop` for benchmarks; `go tool` directives in go.mod; everything from 1.22/1.23."],
115
- "1.25": ["Go 1.25+: `testing/synctest` for concurrent tests; container-aware GOMAXPROCS; everything from 1.22–1.24."],
116
- },
117
- python: {
118
- label: "Python",
119
- "3.12": ["Python 3.12+: `type` statement and `class C[T]` generics (PEP 695), `typing.override`, f-strings may nest quotes; `distutils` is gone."],
120
- "3.13": ["Python 3.13+: `warnings.deprecated`; everything from 3.12 (PEP 695 generics, `typing.override`)."],
121
- },
122
- };
23
+ /**
24
+ * The table lives in pins.json, not here, because two writers need it:
25
+ * people adding a pin, and scripts/measure-pins.js writing back the measured
26
+ * effect. `pins` keeps the lib -> label + major -> rules shape that link,
27
+ * the website and the score already read; `checks` and `proof` sit beside it
28
+ * rather than inside, so no consumer mistakes them for a major.
29
+ */
30
+ const DATA = require("../pins.json");
31
+ const PINS = DATA.pins;
32
+ const CHECKS = DATA.checks || {};
33
+ const PROOF = DATA.proof || {};
123
34
 
124
35
  const major = (range) => {
125
36
  const m = /(\d+)/.exec(String(range || ""));
126
37
  return m ? m[1] : null;
127
38
  };
128
39
 
40
+ function installedVersion(cwd, name) {
41
+ try {
42
+ return JSON.parse(fs.readFileSync(path.join(cwd, "node_modules", name, "package.json"), "utf-8")).version || null;
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+
129
48
  /** Read the majors this project runs, from whichever manifests exist. */
130
49
  function detectVersions(cwd) {
131
50
  const found = [];
@@ -142,8 +61,14 @@ function detectVersions(cwd) {
142
61
  try { pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8")); } catch {}
143
62
  const deps = pkg ? { ...pkg.dependencies, ...pkg.devDependencies } : {};
144
63
  for (const lib of ["next", "react", "prisma", "tailwindcss", "express", "zod", "eslint", "vue"]) {
145
- const range = deps[lib] ?? (lib === "prisma" ? deps["@prisma/client"] : undefined);
146
- if (range) push(lib, major(range), `package.json (${lib} ${range})`);
64
+ const name = deps[lib] !== undefined ? lib : lib === "prisma" && deps["@prisma/client"] !== undefined ? "@prisma/client" : null;
65
+ if (!name) continue;
66
+ // What is installed beats what is declared: `^15` says nothing about
67
+ // whether a lockfile bump already put 16 on disk. Works for npm, pnpm,
68
+ // yarn and bun alike, without parsing four lockfile formats.
69
+ const installed = installedVersion(cwd, name);
70
+ if (installed) push(lib, major(installed), `node_modules (${name} ${installed})`);
71
+ else push(lib, major(deps[name]), `package.json (${name} ${deps[name]})`);
147
72
  }
148
73
  }
149
74
 
@@ -187,4 +112,32 @@ function renderPins(pins) {
187
112
  return lines.join("\n");
188
113
  }
189
114
 
190
- module.exports = { PINS, detectVersions, renderPins };
115
+ /**
116
+ * The detectors for the *old* API of each detected pin, in the shape
117
+ * runPatterns consumes. Scoped by version, not by installed standard: a
118
+ * Next 13 project must not be told `cookies()` is async, and a Next 16
119
+ * project must be told whether or not it installed a Next.js standard.
120
+ */
121
+ function pinPatterns(pins) {
122
+ const out = [];
123
+ for (const p of pins) {
124
+ for (const c of (CHECKS[p.lib] && CHECKS[p.lib][p.version]) || []) {
125
+ out.push({
126
+ id: c.id,
127
+ standard: `${p.label} ${p.version}`,
128
+ severity: c.severity || "medium",
129
+ files: c.files,
130
+ test: new RegExp(c.test, "m"),
131
+ not: c.not ? new RegExp(c.not, "m") : null,
132
+ message: c.message,
133
+ pin: true,
134
+ });
135
+ }
136
+ }
137
+ return out;
138
+ }
139
+
140
+ /** Measured effect of a pin, if scripts/measure-pins.js has run for it. */
141
+ const proofOf = (lib, version) => (PROOF[lib] && PROOF[lib][version]) || null;
142
+
143
+ module.exports = { PINS, CHECKS, PROOF, detectVersions, renderPins, pinPatterns, proofOf };
package/src/pins.json ADDED
@@ -0,0 +1,403 @@
1
+ {
2
+ "$comment": "Version pins: breaking changes per dependency major that a model trained on the previous major gets wrong. `pins` is what link writes (keep its shape: lib -> label + major -> rules). `checks` are deterministic detectors for the OLD api, used by `review --fast`, `lint` and as the judge in scripts/measure-pins.js. `proof` is written only by scripts/measure-pins.js — never by hand.",
3
+ "pins": {
4
+ "next": {
5
+ "13": [
6
+ "App Router (`app/`) exists but `pages/` is still common; check which this project uses before adding routes."
7
+ ],
8
+ "14": [
9
+ "App Router with Server Actions (stable). Navigate with `next/navigation`, not `next/router`, inside `app/`."
10
+ ],
11
+ "15": [
12
+ "Request APIs are async: `await params`, `await searchParams`, `await cookies()`, `await headers()` — synchronous access is deprecated.",
13
+ "`fetch` is no longer cached by default; opt in with `cache: 'force-cache'` or `revalidate`.",
14
+ "Ships React 19: `ref` is a prop, `use()` and `useActionState` are available."
15
+ ],
16
+ "16": [
17
+ "Turbopack is the default bundler; do not add webpack-only config without checking it is still needed.",
18
+ "Request APIs are async only — `params`, `searchParams`, `cookies()`, `headers()` must be awaited; synchronous access was removed.",
19
+ "`middleware.ts` is `proxy.ts`; the old filename is not picked up.",
20
+ "Ships React 19: `ref` is a prop, `use()` and `useActionState` are available."
21
+ ],
22
+ "label": "Next.js"
23
+ },
24
+ "react": {
25
+ "18": [
26
+ "Mount with `createRoot` (not `ReactDOM.render`). Effects double-invoke in StrictMode during development — write them idempotently."
27
+ ],
28
+ "19": [
29
+ "`ref` is a regular prop on function components — do not use `forwardRef`.",
30
+ "Use `use()` to read promises/context, `useActionState` and `useFormStatus` for forms; Actions can be passed to `<form action>`.",
31
+ "`propTypes` and `defaultProps` on function components are ignored; use TypeScript and default parameters."
32
+ ],
33
+ "label": "React"
34
+ },
35
+ "pydantic": {
36
+ "1": [
37
+ "Pydantic v1 API: `.dict()`, `.json()`, `@validator`, `class Config`. Do not use v2 names (`model_dump`, `field_validator`)."
38
+ ],
39
+ "2": [
40
+ "Use `.model_dump()` / `.model_dump_json()` / `.model_validate()` — `.dict()`, `.json()`, `.parse_obj()` are deprecated v1 names.",
41
+ "Validators are `@field_validator` and `@model_validator`; `@validator` and `@root_validator` are deprecated.",
42
+ "Configure with `model_config = ConfigDict(...)`, not an inner `class Config`. Use `TypeAdapter` for non-model types."
43
+ ],
44
+ "label": "Pydantic"
45
+ },
46
+ "sqlalchemy": {
47
+ "2": [
48
+ "Use 2.0 style: `select()` with `session.execute()` / `session.scalars()`; `session.query()` is legacy.",
49
+ "Declare models with `DeclarativeBase`, `Mapped[...]` and `mapped_column()`; `declarative_base()` and `Column` are legacy."
50
+ ],
51
+ "label": "SQLAlchemy"
52
+ },
53
+ "django": {
54
+ "5": [
55
+ "Django 5: `db_default` and `GeneratedField` exist; `USE_L10N` was removed; async ORM methods (`aget`, `acreate`, `afirst`) are available."
56
+ ],
57
+ "label": "Django"
58
+ },
59
+ "prisma": {
60
+ "5": [
61
+ "Prisma 5: `prisma.$extends` for extensions; `@prisma/client` generated in `node_modules` — run `prisma generate` after schema changes."
62
+ ],
63
+ "6": [
64
+ "Prisma 6: same API as 5; `omit` in queries is stable. Run `prisma generate` after schema changes."
65
+ ],
66
+ "7": [
67
+ "Prisma 7 has no Rust query engine by default: instantiate the client with a driver adapter (for example `@prisma/adapter-pg`), not a bare `new PrismaClient()`.",
68
+ "Connection config lives in `prisma.config.ts`, not in the schema's `datasource url`."
69
+ ],
70
+ "label": "Prisma"
71
+ },
72
+ "tailwindcss": {
73
+ "3": [
74
+ "Tailwind v3: `tailwind.config.js` with `content` globs, `@tailwind base/components/utilities` in CSS."
75
+ ],
76
+ "4": [
77
+ "Tailwind v4 is CSS-first: `@import \"tailwindcss\"`, design tokens in `@theme`, `@custom-variant` — there is no `tailwind.config.js` unless the project added `@config`.",
78
+ "`@apply` in a separate CSS file needs `@reference`. Renamed utilities: `shadow-sm`→`shadow-xs`, `outline-none`→`outline-hidden`, `ring`→`ring-3`, `rounded-sm`→`rounded-xs`."
79
+ ],
80
+ "label": "Tailwind CSS"
81
+ },
82
+ "express": {
83
+ "5": [
84
+ "Express 5: rejected promises from async handlers reach error middleware automatically — no `next(err)` wrappers needed.",
85
+ "`req.query` is a read-only getter; route syntax is path-to-regexp v8 (`/{*splat}` instead of `*`, optional segments with `{}`); `res.redirect('back')` is gone."
86
+ ],
87
+ "label": "Express"
88
+ },
89
+ "zod": {
90
+ "4": [
91
+ "Zod 4: use top-level formats `z.email()`, `z.uuid()`, `z.url()` — `z.string().email()` is deprecated.",
92
+ "Error details are `.issues` (not `.errors`); the `message` option is `error`; use `z.strictObject` / `z.looseObject` instead of `.strict()` / `.passthrough()`."
93
+ ],
94
+ "label": "Zod"
95
+ },
96
+ "eslint": {
97
+ "9": [
98
+ "ESLint 9 uses flat config (`eslint.config.js`); `.eslintrc*` is ignored unless `ESLINT_USE_FLAT_CONFIG=false`."
99
+ ],
100
+ "label": "ESLint"
101
+ },
102
+ "vue": {
103
+ "3": [
104
+ "Vue 3: Composition API with `<script setup>`, `defineProps` / `defineEmits`; Options API still works. Vue 2 is end-of-life — do not add Vue 2 patterns like `Vue.extend`."
105
+ ],
106
+ "label": "Vue"
107
+ },
108
+ "go": {
109
+ "label": "Go",
110
+ "1.22": [
111
+ "Go 1.22+: loop variables are per-iteration; `for i := range 10` works; `net/http` ServeMux supports methods and wildcards (`mux.HandleFunc(\"GET /items/{id}\", …)`)."
112
+ ],
113
+ "1.23": [
114
+ "Go 1.23+: range-over-func iterators (`iter.Seq`); everything from 1.22 (per-iteration loop vars, method patterns in ServeMux)."
115
+ ],
116
+ "1.24": [
117
+ "Go 1.24+: generic type aliases; `testing.B.Loop` for benchmarks; `go tool` directives in go.mod; everything from 1.22/1.23."
118
+ ],
119
+ "1.25": [
120
+ "Go 1.25+: `testing/synctest` for concurrent tests; container-aware GOMAXPROCS; everything from 1.22–1.24."
121
+ ]
122
+ },
123
+ "python": {
124
+ "label": "Python",
125
+ "3.12": [
126
+ "Python 3.12+: `type` statement and `class C[T]` generics (PEP 695), `typing.override`, f-strings may nest quotes; `distutils` is gone."
127
+ ],
128
+ "3.13": [
129
+ "Python 3.13+: `warnings.deprecated`; everything from 3.12 (PEP 695 generics, `typing.override`)."
130
+ ]
131
+ }
132
+ },
133
+ "checks": {
134
+ "next": {
135
+ "15": [
136
+ {
137
+ "id": "next-sync-request-api",
138
+ "files": [
139
+ ".ts",
140
+ ".tsx",
141
+ ".js",
142
+ ".jsx",
143
+ ".mjs"
144
+ ],
145
+ "test": "\\b(cookies|headers)\\(\\)\\.(get|set|getAll|has|delete)\\(",
146
+ "message": "Next.js 15+: `cookies()` / `headers()` are async — `(await cookies()).get(...)`."
147
+ },
148
+ {
149
+ "id": "next-sync-params",
150
+ "files": [
151
+ ".ts",
152
+ ".tsx"
153
+ ],
154
+ "test": "\\{\\s*(params|searchParams)\\s*[,}][^)]*\\)?\\s*:\\s*\\{\\s*(params|searchParams)\\s*:\\s*\\{",
155
+ "message": "Next.js 15+: `params` / `searchParams` are Promises — type them `Promise<{...}>` and `await` them."
156
+ }
157
+ ],
158
+ "16": [
159
+ {
160
+ "id": "next-sync-request-api",
161
+ "files": [
162
+ ".ts",
163
+ ".tsx",
164
+ ".js",
165
+ ".jsx",
166
+ ".mjs"
167
+ ],
168
+ "test": "\\b(cookies|headers)\\(\\)\\.(get|set|getAll|has|delete)\\(",
169
+ "message": "Next.js 16: `cookies()` / `headers()` must be awaited — synchronous access was removed."
170
+ },
171
+ {
172
+ "id": "next-sync-params",
173
+ "files": [
174
+ ".ts",
175
+ ".tsx"
176
+ ],
177
+ "test": "\\{\\s*(params|searchParams)\\s*[,}][^)]*\\)?\\s*:\\s*\\{\\s*(params|searchParams)\\s*:\\s*\\{",
178
+ "message": "Next.js 16: `params` / `searchParams` are Promises — type them `Promise<{...}>` and `await` them."
179
+ }
180
+ ]
181
+ },
182
+ "react": {
183
+ "18": [
184
+ {
185
+ "id": "react-legacy-render",
186
+ "files": [
187
+ ".ts",
188
+ ".tsx",
189
+ ".js",
190
+ ".jsx",
191
+ ".mjs"
192
+ ],
193
+ "test": "\\bReactDOM\\.render\\(",
194
+ "message": "React 18+: mount with `createRoot(el).render(...)`, not `ReactDOM.render`."
195
+ }
196
+ ],
197
+ "19": [
198
+ {
199
+ "id": "react-forward-ref",
200
+ "files": [
201
+ ".ts",
202
+ ".tsx",
203
+ ".js",
204
+ ".jsx",
205
+ ".mjs"
206
+ ],
207
+ "test": "\\bforwardRef\\s*[<(]",
208
+ "message": "React 19: `ref` is a regular prop — drop `forwardRef`."
209
+ },
210
+ {
211
+ "id": "react-function-proptypes",
212
+ "files": [
213
+ ".ts",
214
+ ".tsx",
215
+ ".js",
216
+ ".jsx",
217
+ ".mjs"
218
+ ],
219
+ "test": "^\\s*[A-Z]\\w*\\.(propTypes|defaultProps)\\s*=",
220
+ "message": "React 19: `propTypes` / `defaultProps` on function components are ignored — use TypeScript and default parameters."
221
+ }
222
+ ]
223
+ },
224
+ "pydantic": {
225
+ "1": [
226
+ {
227
+ "id": "pydantic-v2-api-on-v1",
228
+ "files": [
229
+ ".py"
230
+ ],
231
+ "test": "\\.model_dump\\(|\\.model_validate\\(|@field_validator\\b|ConfigDict\\(",
232
+ "message": "This project pins Pydantic v1 — `model_dump`, `field_validator`, `ConfigDict` do not exist there."
233
+ }
234
+ ],
235
+ "2": [
236
+ {
237
+ "id": "pydantic-v1-api",
238
+ "files": [
239
+ ".py"
240
+ ],
241
+ "test": "\\.(dict|json|parse_obj|parse_raw)\\(\\s*\\)|@(validator|root_validator)\\b|^\\s*class Config\\s*:",
242
+ "not": "model_dump|model_validate",
243
+ "message": "Pydantic v2: use `model_dump`, `model_validate`, `field_validator`, `model_config = ConfigDict(...)`."
244
+ }
245
+ ]
246
+ },
247
+ "sqlalchemy": {
248
+ "2": [
249
+ {
250
+ "id": "sqlalchemy-legacy-query",
251
+ "files": [
252
+ ".py"
253
+ ],
254
+ "test": "\\b(session|db\\.session|db)\\.query\\(\\s*[A-Z]",
255
+ "message": "SQLAlchemy 2.0: `session.execute(select(Model))` / `session.scalars(...)`, not `session.query(Model)`."
256
+ },
257
+ {
258
+ "id": "sqlalchemy-declarative-base",
259
+ "files": [
260
+ ".py"
261
+ ],
262
+ "test": "\\bdeclarative_base\\(\\)",
263
+ "message": "SQLAlchemy 2.0: subclass `DeclarativeBase` and use `Mapped[...]` / `mapped_column()`."
264
+ }
265
+ ]
266
+ },
267
+ "django": {
268
+ "5": [
269
+ {
270
+ "id": "django-use-l10n",
271
+ "files": [
272
+ ".py"
273
+ ],
274
+ "test": "^\\s*USE_L10N\\s*=",
275
+ "message": "Django 5: `USE_L10N` was removed."
276
+ }
277
+ ]
278
+ },
279
+ "prisma": {
280
+ "7": [
281
+ {
282
+ "id": "prisma-bare-client",
283
+ "files": [
284
+ ".ts",
285
+ ".tsx",
286
+ ".js",
287
+ ".jsx",
288
+ ".mjs"
289
+ ],
290
+ "test": "new\\s+PrismaClient\\(\\s*\\)",
291
+ "message": "Prisma 7: no Rust engine by default — pass a driver adapter, `new PrismaClient({ adapter })`."
292
+ }
293
+ ]
294
+ },
295
+ "tailwindcss": {
296
+ "4": [
297
+ {
298
+ "id": "tailwind-v3-directives",
299
+ "files": [
300
+ ".css",
301
+ ".scss",
302
+ ".pcss"
303
+ ],
304
+ "test": "^\\s*@tailwind\\s+(base|components|utilities)\\b",
305
+ "message": "Tailwind v4: `@import \"tailwindcss\";` replaces the three `@tailwind` directives."
306
+ }
307
+ ]
308
+ },
309
+ "express": {
310
+ "5": [
311
+ {
312
+ "id": "express-bare-wildcard",
313
+ "files": [
314
+ ".ts",
315
+ ".tsx",
316
+ ".js",
317
+ ".jsx",
318
+ ".mjs"
319
+ ],
320
+ "test": "\\.(get|post|put|patch|delete|all|use)\\(\\s*[\\x27\"][^\\x27\"]*\\/\\*[\\x27\"]",
321
+ "message": "Express 5: path-to-regexp v8 — name the wildcard, `/{*splat}`, not `*`."
322
+ },
323
+ {
324
+ "id": "express-redirect-back",
325
+ "files": [
326
+ ".ts",
327
+ ".tsx",
328
+ ".js",
329
+ ".jsx",
330
+ ".mjs"
331
+ ],
332
+ "test": "res\\.redirect\\(\\s*[\\x27\"]back[\\x27\"]",
333
+ "message": "Express 5: `res.redirect(\"back\")` was removed — use `req.get(\"Referrer\") || \"/\"`."
334
+ }
335
+ ]
336
+ },
337
+ "zod": {
338
+ "4": [
339
+ {
340
+ "id": "zod-string-format",
341
+ "files": [
342
+ ".ts",
343
+ ".tsx",
344
+ ".js",
345
+ ".jsx",
346
+ ".mjs"
347
+ ],
348
+ "test": "z\\.string\\(\\)\\.(email|uuid|url|cuid|ulid|ipv4|ipv6|datetime)\\(",
349
+ "message": "Zod 4: use the top-level formats `z.email()`, `z.uuid()`, `z.url()`."
350
+ },
351
+ {
352
+ "id": "zod-error-errors",
353
+ "files": [
354
+ ".ts",
355
+ ".tsx",
356
+ ".js",
357
+ ".jsx",
358
+ ".mjs"
359
+ ],
360
+ "test": "\\.error\\.errors\\b",
361
+ "message": "Zod 4: error details are `.issues`, not `.errors`."
362
+ }
363
+ ]
364
+ },
365
+ "vue": {
366
+ "3": [
367
+ {
368
+ "id": "vue2-global-api",
369
+ "files": [
370
+ ".js",
371
+ ".ts",
372
+ ".vue"
373
+ ],
374
+ "test": "\\bVue\\.extend\\(|\\bnew\\s+Vue\\(",
375
+ "message": "Vue 3: `createApp()` and `defineComponent()` — `new Vue` / `Vue.extend` are Vue 2."
376
+ }
377
+ ]
378
+ },
379
+ "python": {
380
+ "3.12": [
381
+ {
382
+ "id": "python-distutils",
383
+ "files": [
384
+ ".py"
385
+ ],
386
+ "test": "^\\s*(from|import)\\s+distutils\\b",
387
+ "message": "Python 3.12: `distutils` is gone — use `setuptools` or `sysconfig`."
388
+ }
389
+ ],
390
+ "3.13": [
391
+ {
392
+ "id": "python-distutils",
393
+ "files": [
394
+ ".py"
395
+ ],
396
+ "test": "^\\s*(from|import)\\s+distutils\\b",
397
+ "message": "Python 3.13: `distutils` is gone — use `setuptools` or `sysconfig`."
398
+ }
399
+ ]
400
+ }
401
+ },
402
+ "proof": {}
403
+ }