eslint-plugin-llm-core-mcp 0.29.2 → 0.29.4

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.
@@ -1 +1 @@
1
- {"version":3,"file":"embedded-docs.d.ts","sourceRoot":"","sources":["../src/embedded-docs.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAyC/C,CAAC;AAEF,eAAO,MAAM,SAAS,EAAE,MAAM,EAAk7B,CAAC"}
1
+ {"version":3,"file":"embedded-docs.d.ts","sourceRoot":"","sources":["../src/embedded-docs.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CA0C/C,CAAC;AAEF,eAAO,MAAM,SAAS,EAAE,MAAM,EAA08B,CAAC"}
@@ -31,6 +31,7 @@ export const embeddedDocs = {
31
31
  "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",
32
32
  "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",
33
33
  "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",
34
+ "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",
34
35
  "no-unbounded-promise-all": "# llm-core/no-unbounded-promise-all\n\nšŸ“ Disallow Promise.all fan-out over arbitrary collections without an explicit concurrency bound.\n\nšŸ’¼ This rule is enabled in the 🌐 `all` config.\n\n<!-- end auto-generated rule header -->\n\nDisallow `Promise.all(collection.map(...))` and `Promise.allSettled(collection.map(...))` fan-out over arbitrary collections unless the work is explicitly bounded.\n\nLLM-generated code often reaches for `Promise.all(items.map(...))` as a generic performance improvement. That is safe for small fixed tuples, but dangerous for user-sized arrays, database records, API pages, queues, and other arbitrary collections because it starts all work at once.\n\n## Rule Details\n\nThis rule reports obvious unbounded Promise fan-out patterns:\n\n- `Promise.all(items.map(...))`\n- `Promise.allSettled(items.map(...))`\n- `Promise.all(Array.from(items).map(...))`\n- `const jobs = items.map(...); await Promise.all(jobs)`\n\nIt intentionally allows common bounded alternatives:\n\n- small literal arrays such as `Promise.all([loadUser(), loadSettings()])`\n- `.map(...)` over literal tuples\n- callbacks wrapped in a limiter such as `limit(() => work(item))`\n- explicit batching, for example a `for...of` loop over `chunks(items, 10)`\n- sequential `for...of` loops when backpressure or ordering matters\n\n## Examples\n\n### Incorrect\n\n```ts\n// Starts one email send per user immediately.\nawait Promise.all(users.map((user) => sendEmail(user)));\n```\n\n```ts\n// allSettled still starts every item at once.\nawait Promise.allSettled(records.map((record) => processRecord(record)));\n```\n\n```ts\n// Storing the mapped promises does not make the fan-out bounded.\nconst jobs = Array.from(ids).map((id) => fetchUser(id));\nawait Promise.all(jobs);\n```\n\n### Correct\n\n```ts\n// Small fixed fan-out is bounded by the literal array size.\nconst [user, settings] = await Promise.all([\n fetchUser(userId),\n fetchSettings(userId),\n]);\n```\n\n```ts\n// Explicit concurrency limit.\nconst limit = pLimit(5);\nawait Promise.all(users.map((user) => limit(() => sendEmail(user))));\n```\n\n```ts\n// Explicit batching.\nfor (const batch of chunks(users, 10)) {\n await Promise.all(batch.map((user) => sendEmail(user)));\n}\n```\n\n```ts\n// Sequential backpressure.\nfor (const user of users) {\n await sendEmail(user);\n}\n```\n\n## What Counts as Unbounded Fan-Out\n\n| Pattern | Triggers? |\n| ------------------------------------------------------------------------------------- | --------- |\n| `Promise.all(users.map(sendEmail))` | Yes |\n| `Promise.allSettled(items.map(syncItem))` | Yes |\n| `Promise.all(Array.from(ids).map(fetchUser))` | Yes |\n| `const jobs = users.map(sendEmail); await Promise.all(jobs)` | Yes |\n| `Promise.all([fetchUser(id), fetchSettings(id)])` | No |\n| `Promise.all([1, 2, 3].map(fetchUser))` | No |\n| `Promise.all(users.map((user) => limit(() => sendEmail(user))))` | No |\n| `for (const batch of chunks(users, 10)) { await Promise.all(batch.map(sendEmail)); }` | No |\n\n## Error Messages\n\nThe error message teaches:\n\n1. **What's wrong** — a collection is being fanned out without a concurrency bound\n2. **Why** — large inputs can overload memory, APIs, or database pools\n3. **How to fix** — use `p-limit`, batching, or a sequential loop depending on the intended backpressure\n",
35
36
  "no-unsafe-array-access": "# llm-core/no-unsafe-array-access\n\nšŸ“ Require a non-empty array guard before reading the first or last element.\n\nšŸ’¼ This rule is enabled in the following configs: 🌐 `all`, šŸ† `best-practices`, āœ… `recommended`.\n\n<!-- end auto-generated rule header -->\n\nRequire an explicit non-empty-array guard before reading the first or last element from an array.\n\nLLM-generated code often handles the happy path (`items[0]`, `items[items.length - 1]`, or `const [first] = items`) but forgets the empty-array edge case. That produces `undefined`, which frequently becomes a follow-on crash when code immediately reads properties or calls methods on the value.\n\n## Rule Details\n\nThis rule reports narrow, syntactic array access patterns that read a required element without proving that the array is non-empty:\n\n- `items[0]`\n- `items[items.length - 1]`\n- `const [first] = items`\n- `const [first, ...rest] = items`\n\nThe rule allows common guard patterns in the same control-flow path:\n\n- `if (items.length > 0) { items[0] }`\n- `if (items.length) { const [first] = items }`\n- `if (items.length === 0) return; items[0]`\n- `items.length !== 0 ? items[0] : fallback`\n- `items.length && items[0]`\n\nIt also allows patterns that make the empty case explicit, such as destructuring defaults (`const [first = fallback] = items`) and optional element access (`items?.[0]`). Dynamic indexes such as `items[index]` are outside this rule's first/last-element scope.\n\n## Examples\n\n### Incorrect\n\n```ts\n// Empty arrays produce undefined, then the property read crashes.\nconst firstId = items[0].id;\n```\n\n```ts\n// Empty arrays make length - 1 equal -1, so this reads undefined.\nconst latest = items[items.length - 1];\n```\n\n```ts\n// The first element may be undefined when results is empty.\nconst [head, ...tail] = results;\n```\n\n### Correct\n\n```ts\n// Guard before reading the first element.\nconst first = items.length > 0 ? items[0] : undefined;\n```\n\n```ts\n// Early-return guards make the later access safe.\nif (items.length === 0) {\n return undefined;\n}\n\nconst latest = items[items.length - 1];\n```\n\n```ts\n// Destructuring defaults make the empty-array behavior explicit.\nconst [first = fallback] = items;\n```\n\n```ts\n// Optional access intentionally returns undefined for absent values.\nconst maybeFirst = items?.[0];\n```\n\n## What Counts as Unsafe Array Access\n\n| Pattern | Triggers? |\n| ------------------------------------------- | --------- |\n| `items[0]` | Yes |\n| `items[items.length - 1]` | Yes |\n| `const [first] = items` | Yes |\n| `const [first = fallback] = items` | No |\n| `if (items.length > 0) { items[0] }` | No |\n| `if (items.length === 0) return; items[0]` | No |\n| `items.length !== 0 ? items[0] : undefined` | No |\n| `items?.[0]` | No |\n| `items[index]` | No |\n| `[\"fallback\"][0]` | No |\n\n## Error Messages\n\nThe error message teaches:\n\n1. **What's wrong** — the code reads a first or last array element without a non-empty guard\n2. **Why** — empty arrays return `undefined`, which often causes crashes in follow-on code\n3. **How to fix** — add a length guard, early-return on empty arrays, or provide an explicit fallback/default\n",
36
37
  "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",
@@ -41,5 +42,5 @@ export const embeddedDocs = {
41
42
  "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",
42
43
  "uninvoked-array-callback": "# llm-core/uninvoked-array-callback\n\nšŸ“ Disallow callbacks on sparse arrays created with Array(length) because holes skip callback invocation.\n\nšŸ’¼ This rule is enabled in the following configs: 🌐 `all`, šŸ† `best-practices`, āœ… `recommended`.\n\n<!-- end auto-generated rule header -->\n\n## Rule details\n\nThis rule flags array callback methods called directly on sparse arrays created with `Array(length)` or `new Array(length)`. Those constructors create holes, not present `undefined` elements. JavaScript array callback methods skip holes, so callbacks such as `.map(...)`, `.forEach(...)`, `.filter(...)`, `.some(...)`, and `.every(...)` may run zero times even though the code looks like it should run once per index.\n\nLLMs often generate this pattern when they want to create `n` rows, placeholders, tasks, or test cases. The code silently returns another sparse array or performs no side effects.\n\nFlagged patterns:\n\n```ts\nconst rows = new Array(5).map((_, index) => createRow(index));\n\nArray(count).forEach((_, index) => {\n save(index);\n});\n\nconst ids = new Array(3).map((_, index) => index).filter((index) => index > 0);\n```\n\nCorrect patterns:\n\n```ts\nconst rows = Array.from({ length: 5 }, (_, index) => createRow(index));\n\nnew Array(count).fill(null).forEach((_, index) => {\n save(index);\n});\n\nconst ids = [...new Array(3)]\n .map((_, index) => index)\n .filter((index) => index > 0);\n```\n\n## What counts as a sparse array callback\n\n| Pattern | Triggers? | Why |\n| --------------------------------------- | --------- | ----------------------------------------------------------- |\n| `new Array(5).map(callback)` | Yes | Length-only arrays contain holes, so callbacks are skipped. |\n| `Array(count).forEach(callback)` | Yes | `Array(length)` has the same sparse-array behavior. |\n| `new Array(5).fill(null).map(callback)` | No | `.fill(...)` materializes present elements first. |\n| `new Array(5).fill(null, 1).map(cb)` | Yes | Partial fills leave holes outside the filled range. |\n| `[...new Array(5)].map(callback)` | No | Spreading materializes present `undefined` elements. |\n| `Array.from({ length: 5 }, callback)` | No | `Array.from` invokes the mapping callback for each index. |\n| `new Array(\"a\", \"b\").map(callback)` | No | Multiple arguments create present elements. |\n| `Array(true).map(callback)` | No | Non-number single arguments create one present element. |\n| `collection.map(callback)` | No | The rule stays narrow and only targets Array constructors. |\n\n## Error messages\n\nThe error message teaches:\n\n1. **What's wrong** — `Array(length)` created sparse slots, so the callback will not run for them.\n2. **Why** — LLM-generated code often expects one callback invocation per index, but array methods skip holes.\n3. **How to fix** — use `Array.from({ length }, mapper)` or materialize elements with `.fill(...)` before calling callback methods.\n",
43
44
  };
44
- export const ruleNames = ["bad-comparison-sequence", "bad-min-max-func", "consistent-catch-param-name", "explicit-export-types", "filename-match-export", "max-complexity", "max-file-length", "max-function-length", "max-nesting-depth", "max-params", "missing-throw", "naming-conventions", "no-any-in-generic", "no-async-array-callbacks", "no-async-promise-executor", "no-commented-out-code", "no-debug-scaffolding", "no-dynamic-code-execution", "no-empty-catch", "no-exported-function-expressions", "no-floating-promise", "no-hallucinated-local-imports", "no-hallucinated-package-imports", "no-incorrect-sort", "no-inline-disable", "no-llm-artifacts", "no-magic-numbers", "no-redundant-comments", "no-redundant-logic", "no-swallowed-errors", "no-type-assertion-any", "no-unbounded-promise-all", "no-unsafe-array-access", "no-weak-randomness-for-secrets", "prefer-early-return", "prefer-nullish-coalescing", "prefer-unknown-in-catch", "structured-logging", "throw-error-objects", "uninvoked-array-callback"];
45
+ export const ruleNames = ["bad-comparison-sequence", "bad-min-max-func", "consistent-catch-param-name", "explicit-export-types", "filename-match-export", "max-complexity", "max-file-length", "max-function-length", "max-nesting-depth", "max-params", "missing-throw", "naming-conventions", "no-any-in-generic", "no-async-array-callbacks", "no-async-promise-executor", "no-commented-out-code", "no-debug-scaffolding", "no-dynamic-code-execution", "no-empty-catch", "no-exported-function-expressions", "no-floating-promise", "no-hallucinated-local-imports", "no-hallucinated-package-imports", "no-incorrect-sort", "no-inline-disable", "no-llm-artifacts", "no-magic-numbers", "no-redundant-comments", "no-redundant-logic", "no-swallowed-errors", "no-type-assertion-any", "no-type-system-bypass", "no-unbounded-promise-all", "no-unsafe-array-access", "no-weak-randomness-for-secrets", "prefer-early-return", "prefer-nullish-coalescing", "prefer-unknown-in-catch", "structured-logging", "throw-error-objects", "uninvoked-array-callback"];
45
46
  //# sourceMappingURL=embedded-docs.js.map
@@ -1 +1 @@
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,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,kBAAkB,EAAE,63HAA63H;IACj5H,kBAAkB,EAAE,o5GAAo5G;IACx6G,uBAAuB,EAAE,w/EAAw/E;IACjhF,oBAAoB,EAAE,g1HAAg1H;IACt2H,qBAAqB,EAAE,kyFAAkyF;IACzzF,uBAAuB,EAAE,oqEAAoqE;IAC7rE,0BAA0B,EAAE,qqHAAqqH;IACjsH,wBAAwB,EAAE,w6GAAw6G;IACl8G,gCAAgC,EAAE,q6IAAq6I;IACv8I,qBAAqB,EAAE,8mGAA8mG;IACroG,2BAA2B,EAAE,kwDAAkwD;IAC/xD,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,uBAAuB,EAAC,sBAAsB,EAAC,2BAA2B,EAAC,gBAAgB,EAAC,kCAAkC,EAAC,qBAAqB,EAAC,+BAA+B,EAAC,iCAAiC,EAAC,mBAAmB,EAAC,mBAAmB,EAAC,kBAAkB,EAAC,kBAAkB,EAAC,uBAAuB,EAAC,oBAAoB,EAAC,qBAAqB,EAAC,uBAAuB,EAAC,0BAA0B,EAAC,wBAAwB,EAAC,gCAAgC,EAAC,qBAAqB,EAAC,2BAA2B,EAAC,yBAAyB,EAAC,oBAAoB,EAAC,qBAAqB,EAAC,0BAA0B,CAAC,CAAC"}
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,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,kBAAkB,EAAE,63HAA63H;IACj5H,kBAAkB,EAAE,o5GAAo5G;IACx6G,uBAAuB,EAAE,w/EAAw/E;IACjhF,oBAAoB,EAAE,g1HAAg1H;IACt2H,qBAAqB,EAAE,kyFAAkyF;IACzzF,uBAAuB,EAAE,oqEAAoqE;IAC7rE,uBAAuB,EAAE,kvFAAkvF;IAC3wF,0BAA0B,EAAE,qqHAAqqH;IACjsH,wBAAwB,EAAE,w6GAAw6G;IACl8G,gCAAgC,EAAE,q6IAAq6I;IACv8I,qBAAqB,EAAE,8mGAA8mG;IACroG,2BAA2B,EAAE,kwDAAkwD;IAC/xD,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,uBAAuB,EAAC,sBAAsB,EAAC,2BAA2B,EAAC,gBAAgB,EAAC,kCAAkC,EAAC,qBAAqB,EAAC,+BAA+B,EAAC,iCAAiC,EAAC,mBAAmB,EAAC,mBAAmB,EAAC,kBAAkB,EAAC,kBAAkB,EAAC,uBAAuB,EAAC,oBAAoB,EAAC,qBAAqB,EAAC,uBAAuB,EAAC,uBAAuB,EAAC,0BAA0B,EAAC,wBAAwB,EAAC,gCAAgC,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.2",
3
+ "version": "0.29.4",
4
4
  "description": "MCP server for just-in-time linting guidance from eslint-plugin-llm-core",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -32,17 +32,19 @@
32
32
  },
33
33
  "scripts": {
34
34
  "embed": "tsx scripts/embed-rule-docs.ts",
35
- "prebuild": "npm run embed",
35
+ "build:plugin": "npm --workspace eslint-plugin-llm-core run build",
36
+ "prebuild": "npm run build:plugin && npm run embed",
36
37
  "build": "tsc -p tsconfig.build.json",
37
38
  "prepare": "npm run embed",
38
39
  "prepublishOnly": "npm run build",
40
+ "pretest": "npm run build:plugin",
39
41
  "test": "vitest run"
40
42
  },
41
43
  "dependencies": {
42
44
  "@modelcontextprotocol/sdk": "^1.29.0",
43
45
  "@typescript-eslint/parser": "^8.0.0",
44
46
  "eslint": "^10.1.0",
45
- "eslint-plugin-llm-core": "0.34.0",
47
+ "eslint-plugin-llm-core": "0.35.1",
46
48
  "zod": "^3.23.0"
47
49
  },
48
50
  "devDependencies": {