eslint-plugin-llm-core-mcp 0.29.9 โ†’ 0.29.11

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.
@@ -31,7 +31,7 @@ export const embeddedDocs = {
31
31
  "no-magic-numbers": "# llm-core/no-magic-numbers\n\n๐Ÿ“ Disallow magic numbers and enforce named constants for clarity.\n\n๐Ÿ’ผ This rule is enabled in the following configs: ๐ŸŒ `all`, ๐Ÿ† `best-practices`, โœ… `recommended`.\n\n<!-- end auto-generated rule header -->\n\nDisallow magic numbers and enforce named constants for clarity.\n\n## Rule Details\n\nLLMs scatter hardcoded numbers throughout generated code โ€” timeouts, limits, sizes, retry counts โ€” without explaining what they represent. This rule forces numbers to be extracted into named constants.\n\n## Examples\n\n### Incorrect\n\n```ts\nif (retries > 5) {\n throw new Error(\"Too many retries\");\n}\n\nsetTimeout(callback, 3000);\n\nif (file.size > 1048576) {\n throw new Error(\"File too large\");\n}\n```\n\n### Correct\n\n```ts\nconst MAX_RETRIES = 5;\nif (retries > MAX_RETRIES) {\n throw new Error(\"Too many retries\");\n}\n\nconst RETRY_DELAY_MS = 3000;\nsetTimeout(callback, RETRY_DELAY_MS);\n\nconst MAX_FILE_SIZE_BYTES = 1048576;\nif (file.size > MAX_FILE_SIZE_BYTES) {\n throw new Error(\"File too large\");\n}\n```\n\n## What This Rule Allows\n\nBy default, these are permitted without named constants:\n\n- **`0`, `1`, `-1`, `2`** โ€” ubiquitous in loops, checks, and arithmetic\n- **Array indexes** โ€” `items[3]` (configurable)\n- **Default parameter values** โ€” `function retry(attempts = 3)` (configurable)\n- **Enum initializers** โ€” `enum Status { Active = 10 }` (configurable)\n- **Type contexts** โ€” type-level numeric literals\n- **Const declarations** โ€” `const MAX = 5` (this IS the named constant)\n\n## Options\n\n### `ignore`\n\nNumbers to allow without named constants. Default: `[0, 1, -1, 2]`.\n\n### `ignoreArrayIndexes`\n\nAllow numbers as computed array/object indexes. Default: `true`.\n\n### `ignoreDefaultValues`\n\nAllow numbers as default parameter values. Default: `true`.\n\n### `ignoreEnums`\n\nAllow numbers in enum initializers. Default: `true`.\n\n### `skipTestFiles`\n\nWhether to skip test files (`.test.ts`, `.spec.ts`, etc.). Default: `true`.\n\nTest files are full of numeric literals in assertions โ€” `expect(sum(2, 3)).toBe(5)` โ€” where extracting to constants hurts readability.\n\n### `ignoreObjectProperties`\n\nAllow numbers used as object literal property values. Default: `false`.\n\nUseful for data files like pricing tables, model config maps, and HTTP status maps where the values **are** the data:\n\n```ts\n// With ignoreObjectProperties: true โ€” no errors\nconst PRICING = { basic: 9.99, pro: 29.99, enterprise: 99.99 };\nconst HTTP_STATUS = { BAD_REQUEST: 400, NOT_FOUND: 404 };\n```\n\nNumbers outside object literals are still flagged even with this option enabled. This only applies to object literal property values (`ObjectExpression`) โ€” destructuring defaults like `function f({ timeout = 5000 }) {}` are still flagged.\n\n```json\n{\n \"llm-core/no-magic-numbers\": [\n \"error\",\n {\n \"ignore\": [0, 1, -1, 2, 100],\n \"ignoreArrayIndexes\": true,\n \"ignoreDefaultValues\": true,\n \"ignoreEnums\": true,\n \"skipTestFiles\": true,\n \"ignoreObjectProperties\": true\n }\n ]\n}\n```\n\n## Error Messages\n\nError messages follow a structured teaching format designed for LLM self-correction:\n\n1. **What's wrong** โ€” identifies the specific magic number\n2. **Why** โ€” explains that readers can't tell what the number represents\n3. **How to fix** โ€” shows a before/after extraction to a named constant using the actual number\n",
32
32
  "no-object-parameters": "# llm-core/no-object-parameters\n\n๐Ÿ“ Disallow broad object types in function parameters.\n\n๐Ÿ’ผ This rule is enabled in the following configs: ๐ŸŒ `all`, โœ… `recommended`, โŒจ๏ธ `typescript`.\n\n<!-- end auto-generated rule header -->\n",
33
33
  "no-redundant-comments": "# llm-core/no-redundant-comments\n\n๐Ÿ“ Disallow comments that merely narrate the next line of code without adding intent.\n\n๐Ÿ’ผ This rule is enabled in the following configs: ๐ŸŒ `all`, ๐Ÿงน `hygiene`, โœ… `recommended`.\n\n<!-- end auto-generated rule header -->\n\nDisallow comments that merely narrate adjacent code instead of explaining intent, constraints, or non-obvious behavior.\n\n## Rule Details\n\nLLMs often add conversational comments that describe the next line of code in plain English: `// Validate the input`, `// Return the result`, or `// Check if the user exists`. These comments do not preserve design intent. They make files noisier and train future edits to keep narrating implementation mechanics instead of documenting why the code exists.\n\nThis rule reports narrow, high-confidence cases where a line comment immediately precedes code that already says the same thing.\n\n## Examples\n\n### Incorrect\n\n```ts\n// Validate the user input\nvalidateUser(input);\n\n// Return the result\nreturn result;\n\n// Check if the user exists\nif (user) {\n activateUser(user);\n}\n\n// Set the user name\nuser.name = name;\n```\n\n### Correct\n\n```ts\n// Reject webhook payloads before persistence because they are untrusted\nvalidateWebhookPayload(payload);\n\n// Only return cached data when the caller accepts stale results\nreturn cache.get(key);\n\n// This branch handles retry after a 429 response\nif (shouldRetry(response)) {\n retry();\n}\n```\n\n## What Counts as a Redundant Comment\n\n| Pattern | Triggers? |\n| -------------------------------------------------------------- | --------- |\n| `// Return the result` before `return result` | Yes |\n| `// Validate the user input` before `validateUser(input)` | Yes |\n| `// Set the user name` before `user.name = name` | Yes |\n| `// Validate before persisting because payloads are untrusted` | No |\n| `// TODO(#123): add request tracing` | No |\n| JSDoc API documentation | No |\n| ESLint or TypeScript directive comments | No |\n\n## Error Messages\n\nThe error message teaches:\n\n1. **What's wrong** -- the comment narrates adjacent code without adding intent\n2. **Why** -- conversational comments are common LLM noise and reduce maintainability\n3. **How to fix** -- delete the comment or rewrite it to explain why, constraints, or tradeoffs\n",
34
- "no-redundant-logic": "# llm-core/no-redundant-logic\n\n๐Ÿ“ Disallow redundant boolean logic and unnecessary control flow patterns.\n\n๐Ÿ’ผ This rule is enabled in the following configs: ๐ŸŒ `all`, โœ… `recommended`, ๐ŸŽจ `style`.\n\n๐Ÿ’ก This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).\n\n<!-- end auto-generated rule header -->\n\nFlags four patterns where LLMs produce verbose-but-correct boolean logic that can be simplified without changing semantics.\n\n## Rule Details\n\n### Pattern 1 โ€” Redundant boolean comparison\n\nExplicit `=== true`, `!== true`, `=== false`, or `!== false` comparisons on an expression that already evaluates to a boolean.\n\nOnly strict equality (`===`/`!==`) is flagged. Loose equality (`==`/`!=`) has different truthy/falsy semantics and is intentionally excluded.\n\n> **Known limitation:** This rule does not use type information. If the compared expression is a union type (e.g., `string | boolean`), the `=== true` comparison may be intentional type narrowing, not a redundant check. Disable the rule on that line if this applies.\n\n#### Incorrect\n\n```ts\nif (isActive === true) {\n}\nif (isValid !== true) {\n}\nif (hasPermission === false) {\n}\nwhile (running !== false) {}\n```\n\n#### Correct\n\n```ts\nif (isActive) {\n}\nif (!isValid) {\n}\nif (!hasPermission) {\n}\nwhile (running) {}\n```\n\n### Pattern 2 โ€” Unnecessary else after return/throw\n\nAn `else` block that follows an `if` block ending in `return` or `throw` is unreachable via fall-through. The `else` can be removed and its body placed at the outer scope.\n\nOnly flags when:\n\n- The `if` consequent ends with `return` or `throw`\n- The `else` is a simple block (single `return` or `throw`), not an `else if` chain\n- Pattern 4 does not apply to the same node (to avoid double-reporting)\n\n#### Incorrect\n\n```ts\nfunction getLabel(status: string): string {\n if (status === \"active\") {\n return \"Active\";\n } else {\n return \"Inactive\";\n }\n}\n```\n\n#### Correct\n\n```ts\nfunction getLabel(status: string): string {\n if (status === \"active\") {\n return \"Active\";\n }\n return \"Inactive\";\n}\n```\n\n### Pattern 3 โ€” Ternary returning boolean literals\n\nA ternary of the form `condition ? true : false` or `condition ? false : true` is identical to the condition (or its negation).\n\n#### Incorrect\n\n```ts\nconst isEligible = age >= 18 ? true : false;\nconst isBlocked = isAdmin ? false : true;\n```\n\n#### Correct\n\n```ts\nconst isEligible = age >= 18;\nconst isBlocked = !isAdmin;\n```\n\n### Pattern 4 โ€” If/else returning or assigning boolean literals\n\nWhen both branches of an `if`/`else` exclusively `return` or assign the same variable to a boolean literal, the entire construct reduces to a single expression.\n\n#### Incorrect\n\n```ts\nif (items.length > 0) {\n return true;\n} else {\n return false;\n}\n\nlet isValid;\nif (age >= 18) {\n isValid = true;\n} else {\n isValid = false;\n}\n```\n\n#### Correct\n\n```ts\nreturn items.length > 0;\nconst isValid = age >= 18;\n```\n\n## Out of Scope\n\n- De Morgan transformations (`!(a && b)` โ†’ `!a || !b`) โ€” too subjective\n- `!!value` double-negation โ€” intentional type-coercion pattern\n- Loose equality (`== true`) โ€” different semantics\n- Complex boolean simplification โ€” requires symbolic reasoning\n\n## Error Messages\n\nEach pattern has a distinct `messageId`:\n\n| Pattern | `messageId` |\n| ------- | ---------------------------- |\n| 1 | `redundantBooleanComparison` |\n| 2 | `unnecessaryElse` |\n| 3 | `ternaryBooleanLiteral` |\n| 4 | `ifElseBooleanLiteral` |\n\nAll four messages follow the standard what/why/how format.\n\n## Suggestions\n\nAll four patterns provide suggestions (not auto-fixes) โ€” the transformations are safe but the developer should confirm intent before applying.\n",
34
+ "no-redundant-logic": "# llm-core/no-redundant-logic\n\n๐Ÿ“ Disallow redundant boolean logic and unnecessary control flow patterns.\n\n๐Ÿ’ผ This rule is enabled in the following configs: ๐ŸŒ `all`, โœ… `recommended`, ๐ŸŽจ `style`.\n\n๐Ÿ’ก This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).\n\n<!-- end auto-generated rule header -->\n\nFlags four patterns where LLMs produce verbose-but-correct boolean logic that can be simplified without changing semantics.\n\n## Rule Details\n\n### Pattern 1 โ€” Redundant boolean comparison\n\nExplicit `=== true`, `!== true`, `=== false`, or `!== false` comparisons on an expression that already evaluates to a boolean.\n\nOnly strict equality (`===`/`!==`) is flagged. Loose equality (`==`/`!=`) has different truthy/falsy semantics and is intentionally excluded.\n\nThe operand must be provably boolean from syntax: a boolean literal, a negation, a comparison, a logical expression with boolean operands, or a `const` initialized from one of these expressions. Names such as `isActive`, type annotations, and type assertions are not proof. Unknown inputs and properties retain their strict comparisons, including outside conditional tests.\n\nCalls do not establish a boolean return type, including native-looking predicates such as `Set.has`, `RegExp.test`, and `String.endsWith`: their methods and prototypes can be replaced. Strict comparisons on calls are preserved. Negation and comparison operators still guarantee boolean results. Expressions inside dynamic `with` scopes are excluded from proof.\n\n#### Incorrect\n\n```ts\nconst isActive = status === \"active\";\nconst isValid = errors.length === 0;\nconst hasPermission = permissions.length > 0;\nconst running = !stopped;\n\nif (isActive === true) {\n}\nif (isValid !== true) {\n}\nif (hasPermission === false) {\n}\nwhile (running !== false) {}\n```\n\n#### Correct\n\n```ts\nif (isActive) {\n}\nif (!isValid) {\n}\nif (!hasPermission) {\n}\nwhile (running) {}\n\n// These checks distinguish literal booleans from other falsy/truthy values.\nconst rejectsAll = schema === false;\nconst nullable = schema.nullable === true;\n```\n\n### Pattern 2 โ€” Unnecessary else after return/throw\n\nAn `else` block that follows an `if` block ending in `return` or `throw` is unreachable via fall-through. The `else` can be removed and its body placed at the outer scope.\n\nOnly flags when:\n\n- The `if` consequent ends with `return` or `throw`\n- The `else` is a simple block (single `return` or `throw`), not an `else if` chain\n- Pattern 4 does not apply to the same node (to avoid double-reporting)\n\n#### Incorrect\n\n```ts\nfunction getLabel(status: string): string {\n if (status === \"active\") {\n return \"Active\";\n } else {\n return \"Inactive\";\n }\n}\n```\n\n#### Correct\n\n```ts\nfunction getLabel(status: string): string {\n if (status === \"active\") {\n return \"Active\";\n }\n return \"Inactive\";\n}\n```\n\n### Pattern 3 โ€” Ternary returning boolean literals\n\nA ternary of the form `condition ? true : false` or `condition ? false : true` is identical to the condition (or its negation).\n\n#### Incorrect\n\n```ts\nconst isEligible = age >= 18 ? true : false;\nconst isBlocked = isAdmin ? false : true;\n```\n\n#### Correct\n\n```ts\nconst isEligible = age >= 18;\nconst isBlocked = !isAdmin;\n```\n\n### Pattern 4 โ€” If/else returning or assigning boolean literals\n\nWhen both branches of an `if`/`else` exclusively `return` or assign the same variable to a boolean literal, the entire construct reduces to a single expression.\n\n#### Incorrect\n\n```ts\nif (items.length > 0) {\n return true;\n} else {\n return false;\n}\n\nlet isValid;\nif (age >= 18) {\n isValid = true;\n} else {\n isValid = false;\n}\n```\n\n#### Correct\n\n```ts\nreturn items.length > 0;\nconst isValid = age >= 18;\n```\n\n## Out of Scope\n\n- De Morgan transformations (`!(a && b)` โ†’ `!a || !b`) โ€” too subjective\n- `!!value` double-negation โ€” intentional type-coercion pattern\n- Loose equality (`== true`) โ€” different semantics\n- Complex boolean simplification โ€” requires symbolic reasoning\n\n## Error Messages\n\nEach pattern has a distinct `messageId`:\n\n| Pattern | `messageId` |\n| ------- | ---------------------------- |\n| 1 | `redundantBooleanComparison` |\n| 2 | `unnecessaryElse` |\n| 3 | `ternaryBooleanLiteral` |\n| 4 | `ifElseBooleanLiteral` |\n\nAll four messages follow the standard what/why/how format.\n\n## Suggestions\n\nAll four patterns provide suggestions (not auto-fixes) โ€” the transformations are safe but the developer should confirm intent before applying.\n",
35
35
  "no-swallowed-errors": "# llm-core/no-swallowed-errors\n\n๐Ÿ“ Disallow catch blocks that only log to console and swallow the error.\n\n๐Ÿ’ผ This rule is enabled in the following configs: ๐ŸŒ `all`, ๐Ÿ† `best-practices`, โœ… `recommended`.\n\n<!-- end auto-generated rule header -->\n\nDisallow `catch` blocks that only call `console.log`, `console.warn`, `console.error`, or `console.debug` without rethrowing, returning, or delegating the failure to a real error handler.\n\n## Rule Details\n\nLLMs often generate placeholder error handling like `catch (error) { console.error(error); }`. That looks intentional, but it still swallows the failure: callers do not see an error, control flow continues, and production systems quietly drift into bad state.\n\nThis rule focuses on the narrow, high-signal case where a non-empty `catch` block contains **only console logging calls**. It deliberately does **not** overlap with `no-empty-catch` โ€” truly empty or comment-only blocks belong to that rule.\n\n## Examples\n\n### Incorrect\n\n```ts\ntry {\n await processOrder(order);\n} catch (error) {\n console.error(error);\n}\n\ntry {\n await loadConfig();\n} catch (error) {\n console.warn(\"Config failed\", error);\n console.debug(error);\n}\n```\n\n### Correct\n\n```ts\ntry {\n await processOrder(order);\n} catch (error) {\n throw new Error(\"Failed to process order\", { cause: error });\n}\n\ntry {\n return await loadConfig();\n} catch (error) {\n logger.error(\"Config load failed\", { error });\n return null;\n}\n\ntry {\n await publishEvent(event);\n} catch (error) {\n Sentry.captureException(error);\n}\n\ntry {\n await performWork();\n} catch (error) {\n console.error(error);\n throw error;\n}\n```\n\n## What This Rule Flags\n\n| Catch body contents | Flagged? |\n| ----------------------------------------------- | -------- |\n| `console.error(error)` | โœ… Yes |\n| `console.log(error); console.warn(\"failed\", e)` | โœ… Yes |\n| `console.error(error); throw error;` | โŒ No |\n| `console.error(error); handleError(error);` | โŒ No |\n| `handleError(error)` | โŒ No |\n| empty `catch {}` | โŒ No |\n\n## Why Console-Only Handling Is Risky\n\n- Logging alone does not change control flow.\n- Callers cannot recover because they never learn the operation failed.\n- Background tasks can keep running with partial or invalid state.\n- The code looks \"handled\" in review even though the failure was dropped.\n\n## Error Messages\n\nThe teaching message explains:\n\n1. **What's wrong** โ€” the catch block only logs and swallows the error\n2. **Why** โ€” execution continues as if nothing failed\n3. **How to fix** โ€” rethrow, return an explicit fallback, or delegate to a real handler such as a logger or error tracker\n",
36
36
  "no-type-assertion-any": "# llm-core/no-type-assertion-any\n\n๐Ÿ“ Disallow type assertions to `any` that bypass TypeScript's type safety.\n\n๐Ÿ’ผ This rule is enabled in the following configs: ๐ŸŒ `all`, โœ… `recommended`, โŒจ๏ธ `typescript`.\n\n<!-- end auto-generated rule header -->\n\nDisallow type assertions to `any` (`as any` and `<any>` syntax).\n\n## Rule Details\n\nLLMs reach for `as any` as the default escape hatch when types don't align. This completely disables TypeScript's type checking at the assertion site and for every downstream usage of the value. It is the single most common way generated code defeats type safety.\n\n## Examples\n\n### Incorrect\n\n```ts\n// Bypasses all type checking\nconst result = data as any;\n\n// Silently accesses potentially nonexistent property\n(response as any).body;\n\n// Hides a type incompatibility\nprocess(input as any);\n\n// Angle bracket syntax (same problem)\nconst value = <any>data;\n```\n\n### Correct\n\n```ts\n// Assert to a specific known type\nconst result = data as ExpectedType;\n\n// Double assertion through unknown (explicit escape hatch)\nconst result = data as unknown as ExpectedType;\n\n// Use unknown and narrow with type guards\nfunction process(data: unknown) {\n if (typeof data === \"string\") {\n handle(data); // TypeScript knows it's a string\n }\n}\n\n// Properly type the response\nconst body = (response as ResponseWithBody).body;\n```\n\n## What This Rule Catches\n\nThe rule flags both forms of type assertion to `any`:\n\n1. **`as any`** โ€” `value as any`\n2. **`<any>`** โ€” `<any>value` (angle bracket syntax)\n\n### What This Rule Does NOT Catch\n\n- `any` in type annotations (`const x: any`) โ€” use `@typescript-eslint/no-explicit-any` for this\n- `any` in generic type arguments (`Array<any>`) โ€” use `llm-core/no-any-in-generic` for this\n- Implicit `any` from missing type annotations โ€” use `@typescript-eslint/no-unsafe-*` rules for this\n\n## Error Messages\n\nThe error message teaches the proper alternative:\n\n1. **What's wrong** โ€” the assertion bypasses all type checking\n2. **Why** โ€” `any` disables TypeScript's safety and leaks into downstream code\n3. **How to fix** โ€” assert to a specific type, or use `unknown` with type guards\n",
37
37
  "no-type-system-bypass": "# llm-core/no-type-system-bypass\n\n๐Ÿ“ Disallow broad TypeScript escape hatches that hide type errors instead of fixing them.\n\n๐Ÿ’ผ This rule is enabled in the following configs: ๐ŸŒ `all`, โœ… `recommended`, โŒจ๏ธ `typescript`.\n\n<!-- end auto-generated rule header -->\n\nDisallow broad TypeScript escape hatches that silence the compiler instead of fixing the type model.\n\n## Rule Details\n\nLLMs commonly resolve TypeScript errors by adding suppression comments, double assertions, non-null assertions, or explicit `any` annotations. These patterns make code compile while removing the compiler evidence that would have caught a real bug.\n\nThis rule complements narrower rules such as `no-type-assertion-any`, `no-any-in-generic`, and `prefer-unknown-in-catch` by catching additional high-risk bypasses:\n\n- `@ts-ignore`\n- unexplained or generic `@ts-expect-error`\n- double assertions through `unknown`\n- non-null assertions (`!`)\n- explicit `any` annotations outside `as any` assertions and catch parameters\n\n## Examples\n\n### Incorrect\n\n```ts\n// @ts-ignore\ncallLibrary({ experimental: true });\n\n// @ts-expect-error\ncallLibrary({ experimental: true });\n\nconst user = data as unknown as User;\nconst legacyUser = <User>(<unknown>data);\nconst name = user!.profile.name;\nlet payload: any = loadPayload();\nfunction handle(payload: any): void {\n process(payload);\n}\n```\n\n### Correct\n\n```ts\n// @ts-expect-error TS2345: upstream package types reject documented runtime option\ncallLibrary({ experimental: true });\n\nconst user = UserSchema.parse(data);\nif (!user.profile) {\n throw new Error(\"User profile is required\");\n}\n\nconst payload: unknown = loadPayload();\nif (isPayload(payload)) {\n process(payload);\n}\n```\n\n## Relationship to Nearby Rules\n\n| Existing rule | What it covers | This rule avoids/reinforces |\n| ------------------------- | ---------------------------------- | ---------------------------------------------------------- |\n| `no-type-assertion-any` | `value as any` and `<any>value` | Does not duplicate those assertion reports |\n| `no-any-in-generic` | `Array<any>`, `Promise<any>`, etc. | Does not duplicate generic type argument reports |\n| `prefer-unknown-in-catch` | `catch (error: any)` | Skips catch parameters so the focused rule can report them |\n\n## Error Messages\n\nThe error messages teach:\n\n1. **What's wrong** -- the code suppresses or bypasses TypeScript's checks\n2. **Why** -- LLMs often use these patterns to hide compiler errors rather than resolving contracts\n3. **How to fix** -- use specific types, `unknown` plus narrowing, runtime guards, schemas, or justified `@ts-expect-error` comments\n",
@@ -44,7 +44,7 @@ export const embeddedDocs = {
44
44
  "no-weak-randomness-for-secrets": "# llm-core/no-weak-randomness-for-secrets\n\n๐Ÿ“ Disallow weak or predictable randomness when creating security-sensitive values.\n\n๐Ÿ’ผ This rule is enabled in the following configs: ๐ŸŒ `all`, ๐Ÿ† `best-practices`, โœ… `recommended`.\n\n<!-- end auto-generated rule header -->\n\nRequire cryptographic randomness when creating security-sensitive values such as tokens, sessions, nonces, salts, reset codes, API keys, and credentials.\n\nLLM-generated code often reaches for `Math.random()`, timestamps, or counters because the result looks unique in demos. Those values are predictable and can make authentication or recovery flows guessable.\n\n## Rule Details\n\nThis rule reports weak or predictable sources when the target name is security-sensitive:\n\n- variable declarations such as `const token = Math.random()`\n- assignments such as `user.apiKey = Math.random()`\n- object properties such as `{ secret: Date.now().toString(36) }`\n- sensitive generator functions such as `function generateToken() { return Math.random() }` or `const generateToken = () => Math.random()`\n\nThe default sensitive-name pattern covers `token`, `secret`, `password`, `sessionId`, `apiKey`, `nonce`, `salt`, `resetCode`, `verificationCode`, `authCode`, and `credential`. Non-security randomness is allowed by default so UI jitter, sampling, visual effects, and tests can still use `Math.random()`.\n\n## Examples\n\n### Incorrect\n\n```ts\n// Math.random is predictable and should not create auth tokens.\nconst token = Math.random().toString(36).slice(2);\n```\n\n```ts\n// Timestamp-plus-random session IDs are still guessable.\nconst sessionId = `${Date.now()}-${Math.random()}`;\n```\n\n```ts\n// Short reset codes from Math.random are easy to brute force.\nconst passwordResetCode = Math.floor(Math.random() * 1_000_000).toString();\n```\n\n```ts\n// API keys must not use weak randomness.\nuser.apiKey = Math.random().toString(36);\n```\n\n### Correct\n\n```ts\n// Node: use cryptographic random bytes for token material.\nimport { randomBytes } from \"node:crypto\";\n\nconst token = randomBytes(32).toString(\"hex\");\n```\n\n```ts\n// Node: randomUUID is appropriate for session identifiers.\nimport { randomUUID } from \"node:crypto\";\n\nconst sessionId = randomUUID();\n```\n\n```ts\n// Browser: use crypto.getRandomValues for random bytes.\nconst bytes = new Uint8Array(32);\ncrypto.getRandomValues(bytes);\nconst resetCode = encode(bytes);\n```\n\n```ts\n// Non-security randomness is still allowed by default.\nconst displayJitter = Math.random() * 100;\n```\n\n## What Counts as Weak Randomness for Secrets\n\n| Pattern | Triggers? |\n| ---------------------------------------------------- | --------- |\n| `const token = Math.random().toString(36)` | Yes |\n| `const sessionId = Date.now() + \"-\" + Math.random()` | Yes |\n| `` const nonce = `${Date.now()}-${counter++}` `` | Yes |\n| `user.apiKey = Math.random().toString(36)` | Yes |\n| `{ secret: new Date().getTime().toString(36) }` | Yes |\n| `function generateToken() { return Math.random() }` | Yes |\n| `const generateToken = () => Math.random()` | Yes |\n| `const token = randomBytes(32).toString(\"hex\")` | No |\n| `const sessionId = randomUUID()` | No |\n| `crypto.getRandomValues(bytes)` | No |\n| `const displayJitter = Math.random() * 100` | No |\n\n## Options\n\n### `sensitiveNamePattern`\n\nOverride the default case-insensitive sensitive-name regular expression.\n\n```ts\n{\n \"llm-core/no-weak-randomness-for-secrets\": [\n \"error\",\n { \"sensitiveNamePattern\": \"(token|inviteCode|apiKey)\" }\n ]\n}\n```\n\n### `allowMathRandomForNonSensitiveNames`\n\nDefaults to `true`. Set to `false` to report `Math.random()` even when the target name is not security-sensitive.\n\n### `checkFunctionReturnNames`\n\nDefaults to `true`. Set to `false` to skip checks for sensitive function declarations or function-valued assignments such as `generateToken()` returning weak randomness.\n\n## Error Messages\n\nThe error message teaches:\n\n1. **What's wrong** โ€” weak randomness is being used for a security-sensitive value\n2. **Why** โ€” predictable values compromise sessions, reset flows, API keys, nonces, and similar secrets\n3. **How to fix** โ€” use `crypto.randomBytes`, `crypto.randomUUID`, or browser `crypto.getRandomValues`\n",
45
45
  "no-widen-then-assert": "# llm-core/no-widen-then-assert\n\n๐Ÿ“ Disallow declaring a variable's type wider than its value, then asserting it back to the narrow type.\n\n๐Ÿ’ผ This rule is enabled in the following configs: ๐ŸŒ `all`, โœ… `recommended`, โŒจ๏ธ `typescript`.\n\n<!-- end auto-generated rule header -->\n\nDisallow declaring a variable's type wider than its value, then asserting it back to the narrow type.\n\n## Rule Details\n\nLLMs sometimes resolve an unrelated type error by loosening a variable's declared type to `T | undefined` or `T | null`, then forcing it back to `T` with an `as` assertion or `!` at the point of use. Neither step fixes anything: the initializer never produced a nullish value, the variable is never reassigned, and the assertion just discards the annotation the code added a moment earlier. The result compiles, but it's a type contract that was never true in either direction.\n\nThis rule flags that specific round trip. It fires only when all of the following hold:\n\n- The variable is declared with an explicit `T | undefined`, `T | null`, or `T | null | undefined` annotation, where `T` is not itself `any`/`unknown`.\n- The variable is declared with `let` or `const` โ€” never `var` (see Known Limitations).\n- The initializer is a provably concrete value โ€” a literal, template literal, object/array literal, `new` expression, or function/arrow expression โ€” never a call whose result could genuinely be nullish.\n- The variable is never reassigned anywhere in its scope.\n- A later read of the variable is forced back to `T` via `as T`, `<T>`, or non-null `!`.\n\nIt intentionally does not use TypeScript's type checker, so it works without a `parserOptions.project` setup, matching every other rule in this plugin. It also intentionally skips `any`/`unknown` widening โ€” including when `any`/`unknown` is the sole non-nullish union member (`T | undefined` where `T` is `any`/`unknown` isn't a real narrowing; TypeScript already collapses `unknown | undefined` to `unknown`) โ€” that pattern is already covered by `no-type-system-bypass`.\n\n## Known Limitations\n\n- **`var` is out of scope entirely.** `var` is function/module-scoped, not block-scoped, so resolving its declaring scope from a nested declarator can miss its binding, and hoisting means a read before the declaration can be genuinely `undefined` at runtime โ€” something `let`/`const` (which throw via the temporal dead zone) can't produce. Rather than partially support `var` with different guarantees than `let`/`const`, the rule skips it.\n- **Type matching is textual, not resolved.** Because there's no type checker, the concrete type is matched by comparing source text. In the rare case where the same type name is shadowed by a different, incompatible declaration between the variable's scope and the assertion site (e.g. a locally redeclared `type T` inside a nested function), the rule can match text that doesn't actually refer to the same type. This is an accepted, narrow tradeoff โ€” full resolution would require the type checker, which would break this rule's zero-config design.\n\n## Examples\n\n### Incorrect\n\n```ts\nlet name: string | undefined = \"Alice\";\nconsole.log(name as string);\n\nlet count: number | undefined = 0;\nuse(count!);\n```\n\n### Correct\n\n```ts\n// The type matches the value โ€” no widening was added.\nconst name: string = \"Alice\";\nconsole.log(name);\n\n// The value is honestly nullable and handled with a check.\nlet name: string | undefined = maybeGetName();\nconsole.log(name ?? \"unknown\");\n\n// The variable is reassigned, so the wider type is doing real work.\nlet name: string | undefined = \"Alice\";\nname = maybeClearName();\nconsole.log(name as string);\n```\n\n## What Counts as Widen-Then-Assert\n\n| Pattern | Triggers? |\n| -------------------------------------------------------------------------------------- | --------- |\n| `let x: T \\| undefined = <literal>; ...; x as T;` | Yes |\n| `let x: T \\| null = <literal>; ...; x!;` | Yes |\n| `let x: T \\| undefined = <literal>; ...; x = other(); ...; x as T;` (reassigned) | No |\n| `let x: T \\| undefined = someCall(); ...; x as T;` (initializer not provably concrete) | No |\n| `let x: T \\| undefined = <literal>; ...; x ?? fallback;` (no assertion) | No |\n| `let x: T \\| any = <literal>; ...; x as T;` (`any`/`unknown` widening) | No |\n| `let x: unknown \\| undefined = <literal>; ...; x as unknown;` (`any`/`unknown` is `T`) | No |\n| `var x: T \\| undefined = <literal>; ...; x as T;` (`var`, any nesting) | No |\n\n## Relationship to Nearby Rules\n\n| Existing rule | What it covers | This rule avoids/reinforces |\n| ----------------------- | ------------------------------------------------- | ------------------------------------------------------------------- |\n| `no-type-system-bypass` | `any` widening, `as unknown as T`, `!`, ts-ignore | Skips `any`/`unknown` widening so the two rules don't double-report |\n| `no-type-assertion-any` | `value as any` | Only concerns assertions back to the original concrete type |\n\n## Error Messages\n\nThe error message teaches:\n\n1. **What's wrong** -- the variable was declared wider than its value, then forced back\n2. **Why** -- the assertion fabricates evidence the compiler never had; the widening never reflected a real possibility\n3. **How to fix** -- remove the added nullish member from the declaration, or handle real nullability with a check instead of an assertion\n",
46
46
  "prefer-early-return": "# llm-core/prefer-early-return\n\n๐Ÿ“ Enforce guard clauses (early returns) instead of wrapping function bodies in a single if statement.\n\n๐Ÿ’ผ This rule is enabled in the following configs: ๐ŸŒ `all`, โœ… `recommended`, ๐ŸŽจ `style`.\n\n<!-- end auto-generated rule header -->\n\nEnforce guard clauses (early returns) instead of wrapping function bodies in a single if statement.\n\n## Rule Details\n\nLLMs consistently wrap entire function bodies in a single `if` statement instead of using guard clauses. This creates unnecessary nesting and obscures the main logic. Guard clauses handle edge cases at the top of the function, leaving the \"happy path\" at the base indentation level.\n\nThis rule complements `max-nesting-depth` by teaching the specific pattern that prevents deep nesting in the first place.\n\n## Examples\n\n### Incorrect\n\n```ts\n// Entire body wrapped in if โ€” should use guard clause\nfunction process(data: string) {\n if (isValid(data)) {\n transform(data);\n save(data);\n }\n}\n\n// If/else where else is a simple throw โ€” invert to guard\nfunction validate(input: string) {\n if (input.length > 0) {\n parse(input);\n process(input);\n } else {\n throw new Error(\"empty input\");\n }\n}\n\n// Arrow function with wrapped body\nconst fetchUser = async (id: string) => {\n if (id) {\n const user = await db.find(id);\n return user;\n }\n};\n```\n\n### Correct\n\n```ts\n// Guard clause โ€” early return for the edge case\nfunction process(data: string) {\n if (!isValid(data)) return;\n transform(data);\n save(data);\n}\n\n// Guard clause โ€” early throw\nfunction validate(input: string) {\n if (input.length === 0) throw new Error(\"empty input\");\n parse(input);\n process(input);\n}\n\n// Guard clause in arrow function\nconst fetchUser = async (id: string) => {\n if (!id) return;\n const user = await db.find(id);\n return user;\n};\n```\n\n## What This Rule Catches\n\nThe rule flags functions where:\n\n1. The **only statement** in the function body is an `if` statement\n2. The `if` body contains at least `minBodyStatements` statements (default: 2)\n3. There is **no else**, or the else is a **single return/throw**\n\n### What This Rule Does NOT Catch\n\n- Functions with multiple statements before the `if` (only the wrapping pattern)\n- `if/else` where both branches have substantial logic (genuine branching)\n- `if/else if` chains (multiple conditions, not a simple guard)\n- `if` bodies with fewer statements than the `minBodyStatements` threshold\n\n## Options\n\n### `minBodyStatements`\n\nMinimum number of statements in the `if` body to trigger the rule. Default: `2`.\n\nSetting this to `1` catches even simple cases like `if (x) { doSomething(); }`. Setting it higher reduces noise for shorter functions.\n\n```json\n{ \"llm-core/prefer-early-return\": [\"error\", { \"minBodyStatements\": 3 }] }\n```\n\n## Error Messages\n\nThe error message teaches the guard clause pattern:\n\n1. **What's wrong** โ€” the function body is wrapped in a single `if`\n2. **Why** โ€” unnecessary nesting obscures the main logic\n3. **How to fix** โ€” concrete before/after showing the inverted condition with early return\n",
47
- "prefer-nullish-coalescing": "# llm-core/prefer-nullish-coalescing\n\n๐Ÿ“ Prefer nullish coalescing over logical OR when providing default values.\n\n๐Ÿ’ผ This rule is enabled in the following configs: ๐ŸŒ `all`, โœ… `recommended`, ๐ŸŽจ `style`.\n\n๐Ÿ’ก This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).\n\n<!-- end auto-generated rule header -->\n\nPrefer `??` over `||` when providing fallback values so valid falsy values are preserved.\n\n## Rule Details\n\n`||` treats `0`, `\"\"`, and `false` as missing values. When code is selecting a default value, `??` is usually safer because it only falls back for `null` or `undefined`.\n\nThis rule uses syntax-only heuristics. It reports likely default-value expressions and intentionally skips common boolean logic patterns, including boolean contexts and boolean-like names such as `isReady`, `hasAccess`, or `canRetry`.\n\n## Examples\n\n### Incorrect\n\n```ts\nconst name = input || \"Unknown\";\nconst count = value || 10;\noptions.timeout = options.timeout || 5000;\nreturn cachedValue || computeDefault();\n```\n\n### Correct\n\n```ts\nconst name = input ?? \"Unknown\";\nconst count = value ?? 10;\noptions.timeout = options.timeout ?? 5000;\nreturn cachedValue ?? computeDefault();\n```\n\n### Not Reported\n\n```ts\nif (isReady || isForced) {\n run();\n}\n\nreturn flags.isReady || computeDefault();\nconst enabled = config.enabled || false;\nconst value = input || cached || \"fallback\";\n```\n\n## Limitations\n\nThis rule does not use type information. It avoids some obvious boolean false positives, but it cannot know the runtime type of every expression. It also skips chained logical expressions where a safe suggestion would require restructuring parentheses or converting the whole chain.\n",
47
+ "prefer-nullish-coalescing": "# llm-core/prefer-nullish-coalescing\n\n๐Ÿ“ Prefer nullish coalescing over logical OR when providing default values.\n\n๐Ÿ’ผ This rule is enabled in the following configs: ๐ŸŒ `all`, โœ… `recommended`, ๐ŸŽจ `style`.\n\n๐Ÿ’ก This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).\n\n<!-- end auto-generated rule header -->\n\nPrefer `??` over `||` when providing fallback values so valid falsy values are preserved.\n\n## Rule Details\n\n`||` treats `0`, `\"\"`, and `false` as missing values. When code is selecting a default value, `??` is usually safer because it only falls back for `null` or `undefined`.\n\nThis rule uses syntax-only heuristics. It reports likely default-value expressions and intentionally skips common boolean logic patterns, including boolean contexts and boolean-like names such as `isReady`, `hasAccess`, or `canRetry`.\n\nIt also recognizes `Set.has`, `RegExp.test`, and `String.endsWith` when syntax and scope establish the built-in receiver. Supported receivers include direct `new Set(...)`, `new RegExp(...)`, `new String(...)`, regular-expression/string/template literals, and earlier `const` bindings to them. Constructors must be unshadowed. Object receivers must be used only for calls to the relevant predicate; reassignment, method replacement, passing the object elsewhere, and alias escapes prevent proof. Primitive strings are immutable and can be used elsewhere.\n\nThis applies to both variable initializers and return statements. Arbitrary methods named `has`, `test`, or `endsWith` are not assumed to return booleans.\n\nBuilt-in call recognition is a heuristic used only to suppress likely default-value warnings. It is not a return-type guarantee and is never used to remove strict boolean comparisons. Explicit access to the relevant built-in prototype, constructor aliases, or constructor values passed elsewhere disables that heuristic. Unrelated static reads and direct construction/calls remain supported.\n\n## Examples\n\n### Incorrect\n\n```ts\nconst name = input || \"Unknown\";\nconst count = value || 10;\noptions.timeout = options.timeout || 5000;\nreturn cachedValue || computeDefault();\n```\n\n### Correct\n\n```ts\nconst name = input ?? \"Unknown\";\nconst count = value ?? 10;\noptions.timeout = options.timeout ?? 5000;\nreturn cachedValue ?? computeDefault();\n```\n\n### Not Reported\n\n```ts\nif (isReady || isForced) {\n run();\n}\n\nreturn flags.isReady || computeDefault();\nconst enabled = config.enabled || false;\nconst value = input || cached || \"fallback\";\n\nconst knownNames = new Set();\nconst suffixPattern = /_token$/;\nconst name = \"custom_token\";\nconst matches = knownNames.has(name) || suffixPattern.test(name);\nconst technical = knownNames.has(name) || name.endsWith(\"_trace_id\");\n```\n\n## Limitations\n\nThis rule does not use type information. It avoids some obvious boolean false positives, but it cannot know the runtime type of every expression. It also skips chained logical expressions where a safe suggestion would require restructuring parentheses or converting the whole chain.\n\nBuilt-in recognition assumes standard, unmodified built-ins. Constructor-based proof is disabled when the file explicitly mutates or exposes `globalThis`, `global`, `window`, or `self`, including aliases, computed global-object access, and targets passed to reflective mutation APIs. Unrelated static member reads do not disable proof. Changes from code outside the analyzed file cannot be inferred.\n\nGlobal-property references inside assignment patterns, default/rest targets, `for-in`/`for-of` targets, TypeScript wrappers, or optional chains conservatively disable constructor proof as well. This includes destructuring assignments whose property target has no immediate assignment-expression parent.\n\nUnknown parameters, computed methods, optional calls, aliases to constructors/objects, and object receivers with other uses remain outside that conservative proof.\n",
48
48
  "prefer-unknown-in-catch": "# llm-core/prefer-unknown-in-catch\n\n๐Ÿ“ Disallow `any` type annotation on catch clause parameters; prefer `unknown`.\n\n๐Ÿ’ผ This rule is enabled in the following configs: ๐ŸŒ `all`, โœ… `recommended`, โŒจ๏ธ `typescript`.\n\n<!-- end auto-generated rule header -->\n\nDisallow `catch (e: any)` โ€” use `catch (e: unknown)` and narrow before accessing properties.\n\n## Rule Details\n\nLLMs often generate `catch (e: any)` to suppress TypeScript errors on `e.message` or `e.stack`. This defeats TypeScript's type safety entirely for that variable: you can pass it to typed functions, access non-existent properties, and trigger runtime errors with no compile-time warning.\n\nSince TypeScript 4.0, `useUnknownInCatchVariables` (enabled by `strict`) makes catch parameters `unknown` by default. Explicitly annotating `any` overrides this protection.\n\nThis rule only targets catch clause parameters, not general `any` usage elsewhere. It complements `@typescript-eslint/no-explicit-any` without duplicating it.\n\n## Examples\n\n### Incorrect\n\n```ts\ntry {\n await fetchData();\n} catch (e: any) {\n // e.message compiles but is unsafe โ€” e might not be an Error\n console.error(e.message);\n}\n```\n\n### Correct\n\n```ts\n// Unknown + instanceof narrowing โ€” safe\ntry {\n await fetchData();\n} catch (e: unknown) {\n if (e instanceof Error) {\n console.error(e.message);\n } else {\n console.error(String(e));\n }\n}\n\n// No annotation needed (TS infers unknown under strict mode)\ntry {\n await fetchData();\n} catch (e) {\n if (e instanceof Error) console.error(e.message);\n}\n\n// Reusable utility\nfunction toError(e: unknown): Error {\n return e instanceof Error ? e : new Error(String(e));\n}\n\ntry {\n await fetchData();\n} catch (e) {\n logger.error(toError(e));\n}\n```\n\n## What This Rule Does NOT Flag\n\n- `any` in function parameters, generics, or type aliases โ€” those are outside this rule's scope\n- Catch parameters with no annotation โ€” TS's own `useUnknownInCatchVariables` handles that\n- `catch (e: unknown)` โ€” that is the correct pattern\n\n## Error Messages\n\nThe error message teaches:\n\n1. **What's wrong** โ€” `any` disables type-safety for the caught value\n2. **Why** โ€” TS 4.0 made this `unknown` by default for good reason\n3. **How to fix** โ€” use `unknown` with `instanceof Error` narrowing or a `toError` helper\n",
49
49
  "structured-logging": "# llm-core/structured-logging\n\n๐Ÿ“ Enforce structured logging with static messages and dynamic values as separate metadata.\n\n๐Ÿ’ผ This rule is enabled in the following configs: ๐ŸŒ `all`, ๐Ÿ† `best-practices`, โœ… `recommended`.\n\n<!-- end auto-generated rule header -->\n\nEnforce structured logging with static messages and dynamic values as separate metadata.\n\n## Rule Details\n\nLLMs frequently interpolate dynamic values directly into log messages using template literals or string concatenation. This breaks log aggregation, prevents filtering, and can leak sensitive data into log messages.\n\nThis rule enforces that logging functions receive static string messages, with dynamic values passed as structured metadata in separate arguments.\n\n## Examples\n\n### Incorrect\n\n```ts\n// Template literals with dynamic values\nconsole.error(`Failed to fetch data for user ${userId}`);\nlogger.error(`Request ${requestId} failed with ${statusCode}`);\nlogError(`Processing failed: ${error.message}`);\nlogException(error, `Request ${requestId} timed out`);\n\n// String concatenation\nconsole.log(\"User \" + userId + \" logged in\");\nlogger.warn(\"Slow query: \" + duration + \"ms\");\n```\n\n### Correct\n\n```ts\n// Static messages with structured metadata\nconsole.error(\"Failed to fetch data\", { userId });\nlogger.error(\"Request failed\", { requestId, statusCode });\nlogError(\"Processing failed\", { error: error.message });\nlogException(error, \"Request timed out\", { requestId });\n\n// Static template literals (no expressions) are fine\nconsole.log(`Server started`);\nlogger.info(\"User logged in\", { userId });\n```\n\n## Options\n\n### `logFunctions`\n\nArray of standalone function names to check. Default:\n\n```json\n[\"logError\", \"logInfo\", \"logWarn\", \"logDebug\", \"logException\"]\n```\n\n### `logMethods`\n\nArray of method names to check when called on any object (e.g., `logger.error`, `console.warn`). Default:\n\n```json\n[\"log\", \"info\", \"warn\", \"error\", \"debug\", \"trace\"]\n```\n\n### Example Configuration\n\n```json\n{\n \"llm-core/structured-logging\": [\n \"error\",\n {\n \"logFunctions\": [\"logError\", \"logInfo\", \"customLog\"],\n \"logMethods\": [\"log\", \"info\", \"warn\", \"error\", \"write\"]\n }\n ]\n}\n```\n\n**Note:** Setting either option replaces the defaults entirely โ€” it does not merge with them.\n\n## What This Rule Does NOT Flag\n\n- Static string messages (no interpolation or concatenation)\n- Template literals without expressions\n- Dynamic values passed as metadata arguments (second, third, etc.)\n- Non-logging function calls (e.g., `throw new Error(\\`Not found: ${id}\\`)`)\n- Non-string arguments (numbers, objects)\n- Functions/methods not in the configured lists\n\n## Error Messages\n\nError messages follow a structured teaching format designed for LLM self-correction:\n\n1. **What's wrong** โ€” log message contains dynamic values\n2. **Why** โ€” dynamic messages break log aggregation and filtering\n3. **How to fix** โ€” shows how to extract dynamic values into structured metadata\n",
50
50
  "throw-error-objects": "# llm-core/throw-error-objects\n\n๐Ÿ“ Disallow throwing non-Error values such as strings, template literals, plain objects, or arrays.\n\n๐Ÿ’ผ This rule is enabled in the following configs: ๐ŸŒ `all`, ๐Ÿ† `best-practices`, โœ… `recommended`.\n\n<!-- end auto-generated rule header -->\n\nDisallow throwing string literals, template literals, plain objects, arrays, or other non-Error values.\n\n## Rule Details\n\nLLMs often generate `throw \"something went wrong\"` or `throw { code: 404 }` as a shorthand. These patterns lose the stack trace, break `e instanceof Error` checks, and cause silent failures in logging and error-reporting tools that expect an `Error` object.\n\nThis is a syntax-only rule and does not require type information. It allows `throw` of identifiers, member expressions, call expressions, and `new` expressions because it cannot statically prove their type โ€” those forms are assumed to be Error instances or safe wrappers.\n\n## Examples\n\n### Incorrect\n\n```ts\n// String literal โ€” no stack trace\nthrow \"something went wrong\";\n\n// Template literal โ€” still no stack trace\nthrow `failed to connect to ${host}`;\n\n// Plain object โ€” instanceof Error fails, .stack is undefined\nthrow { code: 404, message: \"not found\" };\n\n// Array โ€” completely wrong type\nthrow [\"validation\", \"failed\"];\n\n// Number\nthrow 500;\n```\n\n### Correct\n\n```ts\n// Error instance โ€” stack trace, instanceof, .message all work\nthrow new Error(\"something went wrong\");\n\n// Error subclass\nthrow new ValidationError(\"invalid input\");\n\n// Identifier โ€” may hold an Error, allowed\nthrow err;\n\n// Call expression โ€” assumed to create a proper Error\nthrow createError(\"context\", { cause: err });\n\n// Enrich an existing error with context\nthrow Object.assign(new Error(\"not found\"), { code: 404 });\n```\n\n## What This Rule Does NOT Flag\n\n- `throw identifier` โ€” the identifier may hold an Error; type-checking would be needed to verify\n- `throw expr.property` โ€” same reason\n- `throw callExpression()` โ€” assumed to return an Error\n- `throw new SomeThing()` โ€” assumed to construct an Error\n\n## Error Messages\n\nThe error message teaches:\n\n1. **What's wrong** โ€” the thrown value is not an Error instance\n2. **Why** โ€” stack traces are lost, catch handlers receive unexpected types\n3. **How to fix** โ€” `new Error(msg)`, `Object.assign(new Error(...), extras)`, or Error subclasses\n",
@@ -1 +1 @@
1
- {"version":3,"file":"embedded-docs.js","sourceRoot":"","sources":["../src/embedded-docs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"embedded-docs.js","sourceRoot":"","sources":["../src/embedded-docs.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE,MAAM,CAAC,MAAM,YAAY,GAA2B;IAClD,yBAAyB,EAAE,yyEAAyyE;IACp0E,kBAAkB,EAAE,6oFAA6oF;IACjqF,6BAA6B,EAAE,ipGAAipG;IAChrG,uBAAuB,EAAE,slLAAslL;IAC/mL,uBAAuB,EAAE,8qFAA8qF;IACvsF,gBAAgB,EAAE,8mHAA8mH;IAChoH,iBAAiB,EAAE,wrEAAwrE;IAC3sE,qBAAqB,EAAE,ksEAAksE;IACztE,mBAAmB,EAAE,+qEAA+qE;IACpsE,YAAY,EAAE,i8EAAi8E;IAC/8E,eAAe,EAAE,2vDAA2vD;IAC5wD,oBAAoB,EAAE,0sDAA0sD;IAChuD,mBAAmB,EAAE,srFAAsrF;IAC3sF,0BAA0B,EAAE,yyHAAyyH;IACr0H,2BAA2B,EAAE,ooEAAooE;IACjqE,4BAA4B,EAAE,mQAAmQ;IACjS,uBAAuB,EAAE,8kGAA8kG;IACvmG,sBAAsB,EAAE,myGAAmyG;IAC3zG,2BAA2B,EAAE,y/GAAy/G;IACthH,gBAAgB,EAAE,kxFAAkxF;IACpyF,kCAAkC,EAAE,6/HAA6/H;IACjiI,qBAAqB,EAAE,4iHAA4iH;IACnkH,+BAA+B,EAAE,qhFAAqhF;IACtjF,iCAAiC,EAAE,yiKAAyiK;IAC5kK,mBAAmB,EAAE,gwDAAgwD;IACrxD,mBAAmB,EAAE,w7DAAw7D;IAC78D,yBAAyB,EAAE,yPAAyP;IACpR,kBAAkB,EAAE,63HAA63H;IACj5H,kBAAkB,EAAE,o5GAAo5G;IACx6G,sBAAsB,EAAE,yOAAyO;IACjQ,uBAAuB,EAAE,w/EAAw/E;IACjhF,oBAAoB,EAAE,omJAAomJ;IAC1nJ,qBAAqB,EAAE,kyFAAkyF;IACzzF,uBAAuB,EAAE,oqEAAoqE;IAC7rE,uBAAuB,EAAE,kvFAAkvF;IAC3wF,0BAA0B,EAAE,qqHAAqqH;IACjsH,uBAAuB,EAAE,mQAAmQ;IAC5R,oBAAoB,EAAE,2OAA2O;IACjQ,yBAAyB,EAAE,0OAA0O;IACrQ,wBAAwB,EAAE,w6GAAw6G;IACl8G,2BAA2B,EAAE,oQAAoQ;IACjS,gCAAgC,EAAE,q6IAAq6I;IACv8I,sBAAsB,EAAE,ssLAAssL;IAC9tL,qBAAqB,EAAE,8mGAA8mG;IACroG,2BAA2B,EAAE,o7HAAo7H;IACj9H,yBAAyB,EAAE,uzEAAuzE;IACl1E,oBAAoB,EAAE,6hGAA6hG;IACnjG,qBAAqB,EAAE,+2EAA+2E;IACt4E,0BAA0B,EAAE,4nGAA4nG;CACzpG,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAa,CAAC,yBAAyB,EAAC,kBAAkB,EAAC,6BAA6B,EAAC,uBAAuB,EAAC,uBAAuB,EAAC,gBAAgB,EAAC,iBAAiB,EAAC,qBAAqB,EAAC,mBAAmB,EAAC,YAAY,EAAC,eAAe,EAAC,oBAAoB,EAAC,mBAAmB,EAAC,0BAA0B,EAAC,2BAA2B,EAAC,4BAA4B,EAAC,uBAAuB,EAAC,sBAAsB,EAAC,2BAA2B,EAAC,gBAAgB,EAAC,kCAAkC,EAAC,qBAAqB,EAAC,+BAA+B,EAAC,iCAAiC,EAAC,mBAAmB,EAAC,mBAAmB,EAAC,yBAAyB,EAAC,kBAAkB,EAAC,kBAAkB,EAAC,sBAAsB,EAAC,uBAAuB,EAAC,oBAAoB,EAAC,qBAAqB,EAAC,uBAAuB,EAAC,uBAAuB,EAAC,0BAA0B,EAAC,uBAAuB,EAAC,oBAAoB,EAAC,yBAAyB,EAAC,wBAAwB,EAAC,2BAA2B,EAAC,gCAAgC,EAAC,sBAAsB,EAAC,qBAAqB,EAAC,2BAA2B,EAAC,yBAAyB,EAAC,oBAAoB,EAAC,qBAAqB,EAAC,0BAA0B,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-llm-core-mcp",
3
- "version": "0.29.9",
3
+ "version": "0.29.11",
4
4
  "description": "MCP server for just-in-time linting guidance from eslint-plugin-llm-core",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -41,16 +41,16 @@
41
41
  "test": "vitest run"
42
42
  },
43
43
  "dependencies": {
44
- "@modelcontextprotocol/sdk": "^1.30.0",
45
- "@typescript-eslint/parser": "^8.70.0",
46
- "eslint": "^10.10.0",
47
- "eslint-plugin-llm-core": "0.37.0",
48
- "zod": "^4.6.5"
44
+ "@modelcontextprotocol/sdk": "^1.32.1",
45
+ "@typescript-eslint/parser": "^8.71.2",
46
+ "eslint": "^10.13.0",
47
+ "eslint-plugin-llm-core": "0.37.2",
48
+ "zod": "^4.6.7"
49
49
  },
50
50
  "devDependencies": {
51
- "@types/node": "^26.5.1",
52
- "tsx": "^4.23.13",
51
+ "@types/node": "^26.6.5",
52
+ "tsx": "^4.23.15",
53
53
  "typescript": "^6.0.2",
54
- "vitest": "^4.1.11"
54
+ "vitest": "^5.0.5"
55
55
  }
56
56
  }