eslint-plugin-llm-core-mcp 0.25.2

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.
Files changed (38) hide show
  1. package/README.md +56 -0
  2. package/dist/embedded-docs.d.ts +3 -0
  3. package/dist/embedded-docs.d.ts.map +1 -0
  4. package/dist/embedded-docs.js +37 -0
  5. package/dist/embedded-docs.js.map +1 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +2 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/resources/index.d.ts +3 -0
  11. package/dist/resources/index.d.ts.map +1 -0
  12. package/dist/resources/index.js +7 -0
  13. package/dist/resources/index.js.map +1 -0
  14. package/dist/resources/rule-data.d.ts +12 -0
  15. package/dist/resources/rule-data.d.ts.map +1 -0
  16. package/dist/resources/rule-data.js +57 -0
  17. package/dist/resources/rule-data.js.map +1 -0
  18. package/dist/resources/rule-doc.d.ts +3 -0
  19. package/dist/resources/rule-doc.d.ts.map +1 -0
  20. package/dist/resources/rule-doc.js +80 -0
  21. package/dist/resources/rule-doc.js.map +1 -0
  22. package/dist/resources/rules-list.d.ts +3 -0
  23. package/dist/resources/rules-list.d.ts.map +1 -0
  24. package/dist/resources/rules-list.js +26 -0
  25. package/dist/resources/rules-list.js.map +1 -0
  26. package/dist/server.d.ts +6 -0
  27. package/dist/server.d.ts.map +1 -0
  28. package/dist/server.js +48 -0
  29. package/dist/server.js.map +1 -0
  30. package/dist/tools/get-active-instructions.d.ts +3 -0
  31. package/dist/tools/get-active-instructions.d.ts.map +1 -0
  32. package/dist/tools/get-active-instructions.js +37 -0
  33. package/dist/tools/get-active-instructions.js.map +1 -0
  34. package/dist/tools/lint-file.d.ts +18 -0
  35. package/dist/tools/lint-file.d.ts.map +1 -0
  36. package/dist/tools/lint-file.js +225 -0
  37. package/dist/tools/lint-file.js.map +1 -0
  38. package/package.json +54 -0
package/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # eslint-plugin-llm-core-mcp
2
+
3
+ MCP stdio server for just-in-time guidance from `eslint-plugin-llm-core`.
4
+
5
+ The server exposes:
6
+
7
+ - `lint_file`: lints a file or directory with the target project's own ESLint
8
+ flat config and returns `llm-core/*` violations with their guidance attached.
9
+ - `get_active_instructions`: returns the active generated guidance markdown.
10
+ - `llm-core://rules`: lists registered rules, descriptions, categories, and
11
+ instruction availability.
12
+ - `llm-core://rules/{ruleName}`: returns embedded markdown documentation for one
13
+ rule.
14
+
15
+ ## MCP Client Setup
16
+
17
+ Register the server with an MCP-capable client using `npx`:
18
+
19
+ ```json
20
+ {
21
+ "mcpServers": {
22
+ "llm-core": {
23
+ "command": "npx",
24
+ "args": ["-y", "eslint-plugin-llm-core-mcp"]
25
+ }
26
+ }
27
+ }
28
+ ```
29
+
30
+ `lint_file` uses the project ESLint configuration it discovers from the target
31
+ path. In v1 there is no built-in fallback config, so the target project must
32
+ already install and configure `eslint-plugin-llm-core` in `eslint.config.*`.
33
+
34
+ ## Manual Smoke Check
35
+
36
+ After building the package, confirm the stdio server starts:
37
+
38
+ ```bash
39
+ npm --workspace eslint-plugin-llm-core-mcp run build
40
+ node packages/mcp-server/dist/server.js
41
+ ```
42
+
43
+ The process waits for MCP JSON-RPC messages on stdin. Stop it with `Ctrl-C`.
44
+
45
+ For the published package, MCP clients should launch:
46
+
47
+ ```bash
48
+ npx -y eslint-plugin-llm-core-mcp
49
+ ```
50
+
51
+ ## Versioning
52
+
53
+ `eslint-plugin-llm-core-mcp` versions in lockstep with
54
+ `eslint-plugin-llm-core` under the existing Changesets release flow. Changes
55
+ that affect either package should add a changeset entry for both packages when
56
+ compatibility depends on matching versions.
@@ -0,0 +1,3 @@
1
+ export declare const embeddedDocs: Record<string, string>;
2
+ export declare const ruleNames: string[];
3
+ //# sourceMappingURL=embedded-docs.d.ts.map
@@ -0,0 +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,CAiC/C,CAAC;AAEF,eAAO,MAAM,SAAS,EAAE,MAAM,EAAotB,CAAC"}
@@ -0,0 +1,37 @@
1
+ // AUTO-GENERATED by scripts/embed-rule-docs.ts — do not edit by hand.
2
+ export const embeddedDocs = {
3
+ "bad-comparison-sequence": "# llm-core/bad-comparison-sequence\n\nšŸ“ Disallow chained equality and comparison expressions that compare an intermediate boolean result.\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 chained JavaScript/TypeScript comparisons such as `0 <= ratio <= 1` and `a === b === c`. JavaScript evaluates these expressions left-to-right, so the first comparison produces a boolean and the second comparison uses that boolean as an operand.\n\nLLMs often generate this pattern when translating mathematical range notation directly into code. The result looks reasonable but silently checks the wrong condition.\n\nFlagged patterns:\n\n```ts\nif (0 <= ratio <= 1) {\n accept(ratio);\n}\n\nconst inside = min < value < max;\n\nif ((a === b) === c) {\n sync();\n}\n```\n\nCorrect patterns:\n\n```ts\nif (0 <= ratio && ratio <= 1) {\n accept(ratio);\n}\n\nconst inside = min < value && value < max;\n\nif (a === b && b === c) {\n sync();\n}\n```\n\n## What counts as a bad comparison sequence\n\n| Pattern | Triggers? | Why |\n| -------------------------- | --------- | ------------------------------------------------------------------ |\n| `0 <= value <= 1` | Yes | The second comparison compares the boolean result of `0 <= value`. |\n| `min < value < max` | Yes | JavaScript does not support mathematical chained comparisons. |\n| `a === b === c` | Yes | The final equality compares `(a === b)` to `c`. |\n| `0 <= value && value <= 1` | No | Each comparison explicitly checks the intended value. |\n| `(a < b) === true` | No | Explicit boolean checks are outside this rule's narrow scope. |\n| `score > threshold` | No | Single comparisons are valid. |\n\n## Error messages\n\nThe error message teaches:\n\n1. **What's wrong** — the chained comparison does not behave like mathematical notation.\n2. **Why** — JavaScript evaluates left-to-right and compares an intermediate boolean result.\n3. **How to fix** — split the chain into explicit comparisons joined with `&&`.\n",
4
+ "bad-min-max-func": "# llm-core/bad-min-max-func\n\nšŸ“ Disallow inverted nested Math.min/Math.max clamps that always return a constant bound.\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 nested `Math.min`/`Math.max` clamp expressions whose numeric literal bounds are inverted. These expressions look like they constrain a value to a range, but they actually collapse to a constant bound.\n\nLLMs often generate clamp logic while normalizing percentages, limits, scores, or UI coordinates. When the lower and upper bounds are swapped, the code still typechecks and looks plausible while silently discarding the input value.\n\nFlagged patterns:\n\n```ts\nconst clamped = Math.min(Math.max(value, 100), 0);\nconst clamped = Math.min(Math.max(100, value), 0);\nconst clamped = Math.max(Math.min(value, 0), 100);\n```\n\nCorrect patterns:\n\n```ts\nconst clamped = Math.min(Math.max(value, 0), 100);\nconst clamped = Math.max(Math.min(value, 100), 0);\n```\n\n## What counts as an inverted clamp\n\n| Pattern | Triggers? | Why |\n| ----------------------------------------- | --------- | -------------------------------------------------------------- |\n| `Math.min(Math.max(value, 100), 0)` | Yes | The inner value is at least `100`; the outer call returns `0`. |\n| `Math.max(Math.min(value, 0), 100)` | Yes | The inner value is at most `0`; the outer call returns `100`. |\n| `Math.min(Math.max(value, 0), 100)` | No | Lower and upper bounds are ordered correctly. |\n| `Math.max(Math.min(value, 100), 0)` | No | Alternate clamp spelling with ordered bounds. |\n| `Math.min(Math.max(value, 100), 100)` | No | Equal bounds intentionally collapse to one fixed value. |\n| `Math.min(Math.max(value, lower), upper)` | No | Computed bounds are outside this narrow syntactic scope. |\n| `min(max(value, 100), 0)` | No | Non-`Math` helpers may implement different semantics. |\n\n## Error messages\n\nThe error message teaches:\n\n1. **What's wrong** — the nested `Math.min`/`Math.max` clamp has inverted numeric bounds and always returns a constant.\n2. **Why** — LLM-generated clamp code can swap lower and upper bounds, preserving plausible syntax while discarding the input value.\n3. **How to fix** — put the lower bound in `Math.max` and the upper bound in `Math.min`, or use the equivalent `Math.max(Math.min(value, upper), lower)` ordering.\n",
5
+ "consistent-catch-param-name": "# llm-core/consistent-catch-param-name\n\nšŸ“ Enforce consistent naming for catch clause parameters across the codebase.\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\n## Rule Details\n\nLLMs frequently mix naming conventions (`e`, `err`, `error`, `ex`) in the same codebase. Consistent naming makes error handling patterns more recognizable, reduces cognitive load when reviewing AI-generated code, and makes search/refactoring easier.\n\nBy default the expected name is `\"error\"`. You can configure any valid identifier via the `name` option.\n\n## Options\n\n```json\n{\n \"llm-core/consistent-catch-param-name\": [\"error\", { \"name\": \"error\" }]\n}\n```\n\n| Option | Type | Default | Description |\n| ------ | ------ | --------- | ------------------------------------------------- |\n| `name` | string | `\"error\"` | The required name for all catch clause parameters |\n\n## What Counts\n\n| Pattern | Checked? |\n| ----------------------------------------------- | -------- |\n| `catch (e) {}` | āœ… Yes |\n| `catch (err) {}` | āœ… Yes |\n| `catch (e: unknown) {}` | āœ… Yes |\n| `catch {}` (optional binding) | āŒ No |\n| `catch ({ message }) {}` (object destructuring) | āŒ No |\n| `catch ([a]) {}` (array destructuring) | āŒ No |\n\n## Examples\n\n### Incorrect\n\n```ts\n// \"e\" when \"error\" is expected (default)\ntry {\n doWork();\n} catch (e) {\n console.error(e);\n}\n\n// \"err\" when \"error\" is expected\ntry {\n saveRecord();\n} catch (err) {\n logger.error(err);\n}\n\n// Mixed names in the same file\ntry {\n doWork();\n} catch (e) {\n console.error(e);\n}\ntry {\n doMore();\n} catch (err) {\n console.error(err);\n}\n```\n\n### Correct\n\n```ts\n// Default option — \"error\"\ntry {\n doWork();\n} catch (error) {\n console.error(error);\n}\n\n// Custom option — \"err\"\n// eslint-config: [\"error\", { \"name\": \"err\" }]\ntry {\n doWork();\n} catch (err) {\n console.error(err);\n}\n\n// Optional catch binding — not checked\ntry {\n doWork();\n} catch {\n // intentionally suppressed\n}\n\n// Destructuring — not checked\ntry {\n doWork();\n} catch ({ message }) {\n console.error(message);\n}\n```\n\n## Error Messages\n\nWhen the rule fires, the message teaches:\n\n1. **What's wrong** — the catch parameter is named `'{{ actual }}'` but should be `'{{ expected }}'`\n2. **Why** — LLMs mix naming conventions, making patterns harder to recognize and refactor\n3. **How to fix** — rename the parameter from `{{ actual }}` to `{{ expected }}`\n\nA suggestion fix is provided to rename the parameter and all its references within the catch block in a single editor action.\n\n## When Not to Use It\n\nIf your team intentionally allows multiple catch variable names in different contexts, you can disable this rule or set it to `\"warn\"`.\n",
6
+ "explicit-export-types": "# llm-core/explicit-export-types\n\nšŸ“ Require explicit parameter and return type annotations on exported functions.\n\nšŸ’¼ This rule is enabled in the following configs: 🌐 `all`, āœ… `recommended`, āŒØļø `typescript`.\n\n<!-- end auto-generated rule header -->\n\nRequire explicit parameter and return type annotations on all exported functions, including function declarations, arrow functions, and function expressions.\n\n## Rule Details\n\nLLMs frequently omit type annotations on exported functions, letting TypeScript infer types from the implementation. This reverses the correct order: types should define the contract _before_ the implementation is written. Without explicit annotations, the type contract is an accidental by-product of the code rather than a deliberate specification.\n\nThis rule applies only to **exported** functions. Internal helpers and non-exported functions are not flagged — they are implementation details whose types are constrained by the callers that use them.\n\n## Examples\n\n### Incorrect\n\n```ts\n// āŒ Missing return type — TypeScript infers from implementation\nexport function processOrder(order: Order) {\n return { id: order.id, status: \"processed\" };\n}\n\n// āŒ Missing parameter type — TypeScript cannot check callers\nexport function validate(input): boolean {\n return input.value > 0;\n}\n\n// āŒ Both missing — no contract at all\nexport const handler = (req) => req.body;\n\n// āŒ Default export missing return type\nexport default function handle(req: Request) {\n return req.body;\n}\n```\n\n### Correct\n\n```ts\n// āœ… Both parameter and return types explicit\nexport function processOrder(order: Order): ProcessedOrder {\n return { id: order.id, status: \"processed\" };\n}\n\n// āœ… Arrow function with full annotations\nexport const validate = (input: ValidatorInput): boolean => {\n return input.value > 0;\n};\n\n// āœ… No params — return type still required\nexport function ping(): void {}\n\n// āœ… Destructured param with type annotation on the pattern\nexport function greet({ name }: User): string {\n return `Hello ${name}`;\n}\n\n// āœ… Default param with explicit type\nexport function retry(times: number = 3): void {}\n\n// āœ… Default param with primitive literal — type is inferrable\nexport function retry(times = 3): void {}\n```\n\n## What This Rule Catches\n\nThe rule flags exported functions missing:\n\n1. **Return type annotation** — `export function foo(x: string) { ... }` (return type inferred)\n2. **Parameter type annotation** — `export function foo(x): string { ... }` (param type implicit)\n\nCovered export forms:\n\n- `export function foo(...)` — named function declaration\n- `export default function foo(...)` — default function declaration\n- `export const foo = (...) => ...` — arrow function on exported const\n- `export const foo = function(...) { ... }` — function expression on exported const\n\n### What This Rule Does NOT Catch\n\n- Non-exported functions — `function internal(x) { return x; }` — internal helpers are not flagged\n- Typed destructuring — `export function foo({ id }: User): void {}` — the pattern has a type annotation\n- Typed rest params — `export function foo(...args: string[]): void {}` — already annotated\n- Default params with explicit types — `export function foo(x: number = 0): string { ... }` — fine\n- Default params with primitive literal values — `export function foo(x = 0): string { ... }` — TypeScript infers the type deterministically from the literal, so the annotation is not required. This avoids a conflict with `@typescript-eslint/no-inferrable-types`. Non-literal defaults (e.g., `x = getDefault()`) are still flagged.\n\n### Known Limitations\n\nTwo indirect export patterns are not checked because they require scope traversal to resolve:\n\n1. **Specifier exports** — `function foo(x) {} export { foo };` — the function is defined separately and exported via a specifier. The rule only checks inline `export function` declarations.\n\n2. **Identifier default exports** — `const fn = (x) => x; export default fn;` — the default export is a reference, not an inline function. The rule only checks `export default function` and `export default () =>` forms.\n\nBoth are false negatives. They can be addressed in a follow-up by adding scope-aware analysis.\n\n## Why This Matters for LLMs\n\nWithout explicit type annotations on exported functions, an agent:\n\n- Cannot verify that callers are passing the right shape before running the code\n- May change the return structure without realising it breaks the type contract\n- Has an unbounded solution space — any return shape is valid when the type is inferred\n\nWith explicit annotations, the TypeScript compiler enforces the contract. The agent must define the types first, which forces it to reason about the API before implementing it.\n\n## Relationship to `@typescript-eslint` Rules\n\n`@typescript-eslint/explicit-function-return-type` and `@typescript-eslint/explicit-module-boundary-types` cover similar ground but are not enabled in `recommended`. This rule fills that gap with LLM-specific error messages that teach the correct process: define types first, then implement against them.\n\nThis rule is compatible with `@typescript-eslint/no-inferrable-types`. Parameters with primitive literal defaults (numbers, strings, booleans, null, bigint) are not flagged, so there is no conflict when both rules are enabled.\n\n## Error Messages\n\nEach error message follows a structured teaching format:\n\n1. **What's wrong** — the specific missing annotation\n2. **Why it matters** — why type-first development requires explicit annotations\n3. **How to fix** — concrete steps: define a type, then annotate the function\n",
7
+ "filename-match-export": "# llm-core/filename-match-export\n\nšŸ“ Enforce that filenames match their single exported function, class, or component name.\n\nšŸ’¼ This rule is enabled in the following configs: 🌐 `all`, āœ… `recommended`, šŸŽØ `style`.\n\n<!-- end auto-generated rule header -->\n\nEnforce that filenames match their single exported function, class, or component name.\n\n## Rule Details\n\nLLMs frequently create files with names that don't match their contents (e.g., `utils.ts` exporting `UserService`). This rule enforces that when a file has exactly one export, the filename must match the export name.\n\nSupported filename conventions:\n\n- **Exact match** — `UserService.ts` exports `UserService`\n- **kebab-case → camelCase** — `user-service.ts` exports `userService`\n- **kebab-case → PascalCase** — `user-service.ts` exports `UserService`\n- **kebab-case → acronym-aware PascalCase** — `bedrock-kb-rag-tool.ts` exports `BedrockKBRagTool` (each kebab segment matches as TitleCase or ALLCAPS, handling acronym casing)\n- **PascalCase → camelCase** — `UserService.ts` exports `userService`\n\n## Examples\n\n### Incorrect\n\n```ts\n// account-service.ts — filename doesn't match export\nexport function UserService() {\n // ...\n}\n\n// getData.ts — filename doesn't match default export\nexport default function fetchData() {\n // ...\n}\n\n// Input.tsx — filename doesn't match component\nexport function Button() {\n return <button>Click</button>;\n}\n```\n\n### Correct\n\n```ts\n// UserService.ts\nexport function UserService() {\n // ...\n}\n\n// user-service.ts — kebab-case matches PascalCase export\nexport function UserService() {\n // ...\n}\n\n// fetchData.ts\nexport default function fetchData() {\n // ...\n}\n\n// Button.tsx\nexport function Button() {\n return <button>Click</button>;\n}\n```\n\n## What This Rule Does NOT Flag\n\n- **`index.ts`** — barrel files have no single export to match\n- **Files with multiple exports** — no single \"primary\" export to enforce\n- **Test files** (`*.test.ts`, `*.spec.ts`) — test filenames follow their own conventions\n- **Utility files** (`types.ts`, `constants.ts`, `enums.ts`, `errors.ts`, `utils.ts`, `helpers.ts`) — these contain multiple related items by convention\n- **Re-exports** (`export { foo } from './foo'`) — passthrough, not authored content\n- **Anonymous default exports** — no name to match against\n\n## Error Messages\n\nError messages follow a structured teaching format designed for LLM self-correction:\n\n1. **What's wrong** — identifies the filename and export that don't match\n2. **Why** — explains that mismatched names hurt navigation and searchability\n3. **How to fix** — suggests the expected filename based on the current naming convention\n",
8
+ "max-complexity": "# llm-core/max-complexity\n\nšŸ“ Enforce a maximum cyclomatic complexity per function to encourage decomposition.\n\nšŸ’¼ This rule is enabled in the following configs: 🌐 `all`, 🧮 `complexity`, āœ… `recommended`.\n\n<!-- end auto-generated rule header -->\n\nEnforce a maximum cyclomatic complexity per function to encourage decomposition.\n\n## Rule Details\n\nLLM-generated code often accumulates `if` chains, loops, logical shortcuts, and optional-chaining branches in a single function because each step looks locally reasonable. The result is code with too many independent execution paths to test or modify safely.\n\nThis rule matches ESLint's core `complexity` rule in classic mode:\n\n- Base complexity starts at `1`\n- Each `if`, `else if`, loop, `catch`, ternary, logical expression, non-default `switch` case, default parameter, logical assignment, optional member access, and optional call adds `1`\n- Complexity resets for each function, class field initializer, and class static block\n\n## Examples\n\n### Incorrect\n\n```ts\nfunction getValue(type: string): number {\n if (type === \"a\") return 1;\n if (type === \"b\") return 2;\n if (type === \"c\") return 3;\n if (type === \"d\") return 4;\n if (type === \"e\") return 5;\n if (type === \"f\") return 6;\n if (type === \"g\") return 7;\n if (type === \"h\") return 8;\n if (type === \"i\") return 9;\n if (type === \"j\") return 10;\n return 11;\n}\n```\n\n```ts\nclass Workflow {\n handle(step: number): number {\n if (step === 1) return 1;\n if (step === 2) return 2;\n if (step === 3) return 3;\n return 4;\n }\n}\n```\n\n### Correct\n\n```ts\nconst VALUES: Record<string, number> = {\n a: 1,\n b: 2,\n c: 3,\n d: 4,\n};\n\nfunction getValue(type: string): number {\n return VALUES[type] ?? 0;\n}\n```\n\n```ts\nfunction handleWorkflow(step: number): number {\n if (step <= 0) return 0;\n\n return lookupStepValue(step);\n}\n\nfunction lookupStepValue(step: number): number {\n const STEP_VALUES: Record<number, number> = { 1: 1, 2: 2, 3: 3 };\n return STEP_VALUES[step] ?? 4;\n}\n```\n\n## Options\n\n### `max`\n\nMaximum allowed cyclomatic complexity. Default: `10`.\n\n```json\n{ \"llm-core/max-complexity\": [\"error\", { \"max\": 8 }] }\n```\n\n### `skipTestFiles`\n\nSkips `*.test.*` and `*.spec.*` files. Default: `true`.\n\n```json\n{ \"llm-core/max-complexity\": [\"error\", { \"skipTestFiles\": false }] }\n```\n\n## Error Messages\n\nThe message is intentionally prescriptive:\n\n- It reports the function or code-path boundary name, current complexity, and configured maximum\n- It explains why each additional branch makes the function riskier to change\n- It demonstrates a concrete rewrite from branching logic to a lookup table\n\n## Decomposition Strategies\n\nWhen this rule fires, start with the smallest rewrite that removes branching paths without obscuring intent:\n\n1. **Lookup tables** — replace repeated equality branches with a data structure\n2. **Guard clauses** — exit early so the main path stays flat\n3. **Function extraction** — move a cohesive branch cluster into a named helper\n4. **Strategy pattern** — route behavior through an object of handlers when branching selects an algorithm\n\n## Complementary Tools\n\n- [`no-nested-ternary`](https://eslint.org/docs/latest/rules/no-nested-ternary) — prevents dense conditional expressions from hiding branching\n- [`no-useless-assignment`](https://eslint.org/docs/latest/rules/no-useless-assignment) — catches temporary variables that often appear during branch-heavy rewrites\n- [`max-nesting-depth`](./max-nesting-depth.md) — limits how deep branching stacks once you have already reduced path count\n",
9
+ "max-file-length": "# llm-core/max-file-length\n\nšŸ“ Enforce a maximum number of lines per file to encourage proper module separation.\n\nšŸ’¼ This rule is enabled in the following configs: 🌐 `all`, 🧮 `complexity`, āœ… `recommended`.\n\n<!-- end auto-generated rule header -->\n\nEnforce a maximum number of lines per file to encourage proper module separation.\n\n## Rule Details\n\nLLMs frequently dump everything into a single file — handlers, utilities, types, constants. This rule limits file length, forcing proper module separation.\n\n## Examples\n\n### Incorrect\n\n```ts\n// god-file.ts — 500 lines of everything\nexport interface User { ... }\nexport interface Order { ... }\nexport const MAX_RETRIES = 3;\nexport const TIMEOUT = 5000;\nexport function validateUser() { ... }\nexport function validateOrder() { ... }\nexport function processUser() { ... }\nexport function processOrder() { ... }\n// ... 400 more lines\n```\n\n### Correct\n\n```ts\n// types.ts\nexport interface User { ... }\nexport interface Order { ... }\n\n// constants.ts\nexport const MAX_RETRIES = 3;\nexport const TIMEOUT = 5000;\n\n// validation.ts\nexport function validateUser() { ... }\nexport function validateOrder() { ... }\n\n// processing.ts\nexport function processUser() { ... }\nexport function processOrder() { ... }\n```\n\n## Options\n\n### `max`\n\nMaximum allowed lines per file. Default: `250`.\n\n### `skipBlankLines`\n\nWhether to skip blank lines when counting. Default: `true`.\n\n### `skipTestFiles`\n\nWhether to skip test files (`.test.ts`, `.spec.ts`, etc.). Default: `true`.\n\nTest files are often longer than source files due to test setup and multiple test cases, so they are excluded by default.\n\n```json\n{\n \"llm-core/max-file-length\": [\n \"error\",\n {\n \"max\": 200,\n \"skipBlankLines\": true,\n \"skipTestFiles\": 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 file line count\n2. **Why** — explains that large files indicate poor module separation\n3. **How to fix** — four strategies: split by responsibility, extract related functions, separate types, separate constants\n",
10
+ "max-function-length": "# llm-core/max-function-length\n\nšŸ“ Enforce a maximum number of lines per function to encourage decomposition.\n\nšŸ’¼ This rule is enabled in the following configs: 🌐 `all`, 🧮 `complexity`, āœ… `recommended`.\n\n<!-- end auto-generated rule header -->\n\nEnforce a maximum number of lines per function to encourage decomposition.\n\n## Rule Details\n\nLLMs frequently generate monolithic functions with 100+ lines. This rule limits function length, forcing the LLM to extract helpers and decompose logic into focused, testable units.\n\n## Examples\n\n### Incorrect\n\n```ts\n// 200-line function doing everything\nfunction processOrder(order: Order) {\n // validate input (20 lines)\n // check inventory (30 lines)\n // calculate pricing (40 lines)\n // apply discounts (25 lines)\n // process payment (35 lines)\n // send confirmation (20 lines)\n // update analytics (30 lines)\n}\n```\n\n### Correct\n\n```ts\nfunction processOrder(order: Order) {\n validateOrder(order);\n const inventory = checkInventory(order.items);\n const pricing = calculatePricing(order, inventory);\n const total = applyDiscounts(pricing, order.coupon);\n await processPayment(order.payment, total);\n await sendConfirmation(order.email, total);\n trackOrderAnalytics(order);\n}\n```\n\n## Options\n\n### `max`\n\nMaximum allowed lines per function. Default: `50`.\n\n### `skipBlankLines`\n\nWhether to skip blank lines when counting. Default: `true`.\n\n### `skipTestFiles`\n\nWhether to skip test files (`.test.ts`, `.spec.ts`, etc.). Default: `true`.\n\nTest functions are intentionally verbose — setup, mocking, assertions — and benefit from being self-contained rather than decomposed.\n\n```json\n{\n \"llm-core/max-function-length\": [\n \"error\",\n {\n \"max\": 40,\n \"skipBlankLines\": true,\n \"skipTestFiles\": 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 function and its line count\n2. **Why** — explains that long functions are hard to understand and test\n3. **How to fix** — three decomposition strategies: extract helpers, single responsibility, separate setup/logic/cleanup\n",
11
+ "max-nesting-depth": "# llm-core/max-nesting-depth\n\nšŸ“ Enforce a maximum nesting depth for control flow statements to reduce cognitive complexity.\n\nšŸ’¼ This rule is enabled in the following configs: 🌐 `all`, 🧮 `complexity`, āœ… `recommended`.\n\n<!-- end auto-generated rule header -->\n\nEnforce a maximum nesting depth for control flow statements to reduce cognitive complexity.\n\n## Rule Details\n\nLLMs frequently generate deeply nested code instead of using guard clauses, early returns, or extracting helper functions. This rule limits how deep control flow can nest, forcing flatter, more readable code.\n\nNesting is counted for: `if`, `for`, `for...in`, `for...of`, `while`, `do...while`, `switch`, and `try`.\n\n**`else if` is not counted as additional nesting** — it reads as a flat chain, not a deeper level.\n\n## Examples\n\n### Incorrect\n\n```ts\n// Depth 4 — exceeds default max of 3\nif (user) {\n if (user.isActive) {\n for (const item of user.items) {\n if (item.isValid) {\n // ← depth 4, error\n process(item);\n }\n }\n }\n}\n```\n\n### Correct\n\n```ts\n// Guard clauses flatten nesting\nif (!user) return;\nif (!user.isActive) return;\n\nfor (const item of user.items) {\n if (item.isValid) {\n process(item);\n }\n}\n\n// Or extract helpers\nfunction processActiveItems(user: User) {\n for (const item of user.items) {\n if (item.isValid) {\n process(item);\n }\n }\n}\n\nif (user?.isActive) {\n processActiveItems(user);\n}\n```\n\n## Options\n\n### `max`\n\nMaximum allowed nesting depth. Default: `3`.\n\n```json\n{ \"llm-core/max-nesting-depth\": [\"error\", { \"max\": 4 }] }\n```\n\n## Error Messages\n\nThe error message teaches three specific refactoring techniques:\n\n1. **Guard clauses** — invert conditions and return early to avoid wrapping\n2. **Extract helpers** — pull nested blocks into named functions\n3. **Flatten chains** — replace nested if/else with early returns or lookups\n\n## Complementary Tools\n\nFor full cognitive complexity scoring (including logical operators, recursion, and break/continue), consider adding [`eslint-plugin-sonarjs`](https://github.com/SonarSource/eslint-plugin-sonarjs) with its `cognitive-complexity` rule.\n",
12
+ "max-params": "# llm-core/max-params\n\nšŸ“ Enforce a maximum number of function parameters to encourage object parameter patterns.\n\nšŸ’¼ This rule is enabled in the following configs: 🌐 `all`, 🧮 `complexity`, āœ… `recommended`.\n\n<!-- end auto-generated rule header -->\n\nEnforce a maximum number of function parameters to encourage object parameter patterns.\n\n## Rule Details\n\nLLMs frequently generate functions with many positional parameters. This makes function calls error-prone — parameter order is easy to confuse, and adding new parameters requires updating every call site.\n\nThis rule limits function parameters (default: 2) and encourages using a single options object with destructuring instead.\n\nConstructors have a separate, higher limit (default: 5) to accommodate dependency injection patterns.\n\n## Examples\n\n### Incorrect\n\n```ts\n// Too many positional parameters\nfunction fetchAttachment(\n url: string,\n logger: Logger,\n maxSizeBytes: number,\n timeoutMs: number,\n allowedDomains: string[],\n regulationId: string,\n) {\n // ...\n}\n```\n\n### Correct\n\n```ts\n// Object parameter with destructuring\ntype FetchOptions = {\n url: string;\n logger: Logger;\n maxSizeBytes: number;\n timeoutMs: number;\n allowedDomains: string[];\n regulationId: string;\n};\n\nfunction fetchAttachment(options: FetchOptions) {\n const { url, logger, maxSizeBytes, timeoutMs, allowedDomains, regulationId } =\n options;\n // ...\n}\n```\n\n## Options\n\n### `max`\n\nMaximum allowed parameters for functions. Default: `2`.\n\n### `maxConstructor`\n\nMaximum allowed parameters for class constructors. Default: `5`.\n\n### `maxInternal`\n\nMaximum allowed parameters for non-exported functions. Default: same as `max`.\n\nThis creates a useful tiering: exported functions (public API) are held to the stricter `max` limit and encouraged to use options objects, while internal helpers like `handleError(error, message, context)` get a relaxed limit.\n\n```json\n{\n \"llm-core/max-params\": [\n \"error\",\n {\n \"max\": 2,\n \"maxConstructor\": 5,\n \"maxInternal\": 3\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 function and parameter count\n2. **Why** — explains that positional parameters are error-prone\n3. **How to fix** — shows the exact transformation to an options object with the function's actual parameter names\n",
13
+ "missing-throw": "# llm-core/missing-throw\n\nšŸ“ Disallow standalone new Error expressions that likely forgot the throw keyword.\n\nšŸ’¼ This rule is enabled in the following configs: 🌐 `all`, šŸ† `best-practices`, āœ… `recommended`.\n\nšŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\n## Rule details\n\nThis rule flags standalone `new Error(...)` expressions that create an Error object without throwing, returning, assigning, or otherwise using it. `new Error(...)` by itself does not stop execution. The autofix inserts the missing `throw` keyword before the Error construction.\n\nFlagged patterns:\n\n```js\nfunction requireUser(user) {\n if (!user) {\n new Error(\"User is required\");\n }\n}\n\nfunction parse(value) {\n if (typeof value !== \"string\") {\n new TypeError(\"Expected string\");\n }\n}\n\nfunction collectErrors(errors) {\n new AggregateError(errors, \"Multiple failures\");\n}\n```\n\nCorrect patterns:\n\n```js\nfunction requireUser(user) {\n if (!user) {\n throw new Error(\"User is required\");\n }\n}\n\nfunction createError() {\n return new Error(\"User is required\");\n}\n\nconst error = new Error(\"User is required\");\n```\n\n## Scope and limitations\n\nThis rule uses AST-only analysis. It flags standalone construction of built-in Error constructors:\n\n- `Error`\n- `AggregateError`\n- `EvalError`\n- `RangeError`\n- `ReferenceError`\n- `SyntaxError`\n- `TypeError`\n- `URIError`\n\nIt intentionally does not flag arbitrary custom classes such as `new ValidationError(...)` because some codebases construct custom error objects as values and the rule cannot prove they are error subclasses without type information.\n",
14
+ "naming-conventions": "# llm-core/naming-conventions\n\nšŸ“ Enforce naming conventions: Base prefix for abstract classes, Error suffix for error classes.\n\nšŸ’¼ This rule is enabled in the following configs: 🌐 `all`, āœ… `recommended`, šŸŽØ `style`.\n\n<!-- end auto-generated rule header -->\n\nEnforce naming conventions: Base prefix for abstract classes, Error suffix for error classes.\n\n## Rule Details\n\nLLMs frequently create abstract classes without a `Base` prefix and error classes without an `Error` suffix. These naming conventions make class purpose immediately clear from the name alone.\n\n### Checks\n\n1. **Abstract classes** must start with `Base` (e.g., `BaseService`, `BaseRepository`)\n2. **Error classes** (extending `Error` or any `*Error` class) must end with `Error` (e.g., `NotFoundError`, `ValidationError`)\n\n## Examples\n\n### Incorrect\n\n```ts\n// Missing Base prefix\nabstract class Service {\n abstract run(): void;\n}\n\n// Missing Error suffix\nclass NotFound extends Error {\n constructor(message: string) {\n super(message);\n }\n}\n\nclass BadRequest extends HttpError {}\n```\n\n### Correct\n\n```ts\n// Base prefix signals abstract class\nabstract class BaseService {\n abstract run(): void;\n}\n\n// Error suffix signals error class\nclass NotFoundError extends Error {\n constructor(message: string) {\n super(message);\n }\n}\n\nclass BadRequestError extends HttpError {}\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 class name and missing prefix/suffix\n2. **Why** — explains what the naming convention signals to readers\n3. **How to fix** — shows the exact rename with the class's actual name\n",
15
+ "no-any-in-generic": "# llm-core/no-any-in-generic\n\nšŸ“ Disallow `any` as a generic type argument in type references, arrays, and other parameterized types.\n\nšŸ’¼ This rule is enabled in the following configs: 🌐 `all`, āœ… `recommended`, āŒØļø `typescript`.\n\n<!-- end auto-generated rule header -->\n\nDisallow `any` as a generic type argument in type references like `Array<any>`, `Record<string, any>`, `Map<string, any>`, and other parameterized types.\n\n## Rule Details\n\nLLMs frequently use `any` inside generic types as a \"good enough\" placeholder. Unlike bare `any` annotations, this pattern is subtler — the code looks typed because there's a generic wrapper, but `any` silently leaks through every access, assignment, and function call that touches the contained values.\n\n## Examples\n\n### Incorrect\n\n```ts\n// any leaks into every array access\nconst items: Array<any> = [];\n\n// any leaks into every property access\nconst cache: Record<string, any> = {};\n\n// any leaks into every map.get() call\nconst lookup: Map<string, any> = new Map();\n\n// any leaks into the resolved value\nconst promise: Promise<any> = fetch(\"/api\");\n\n// any in generic function calls\nconst store = createStore<any>();\n```\n\n### Correct\n\n```ts\n// Specific types\nconst items: Array<User> = [];\nconst cache: Record<string, CacheEntry> = {};\nconst lookup: Map<string, Handler> = new Map();\nconst promise: Promise<Response> = fetch(\"/api\");\n\n// unknown when the type is genuinely dynamic\nconst items: Array<unknown> = [];\nconst cache: Record<string, unknown> = {};\n\n// Narrow unknown values with type guards\nfor (const item of items) {\n if (isUser(item)) {\n process(item); // TypeScript knows it's a User\n }\n}\n```\n\n## What This Rule Catches\n\nThe rule flags `any` used as a type argument in:\n\n1. **Type references** — `Array<any>`, `Record<string, any>`, `Map<any, any>`, `Set<any>`, `Promise<any>`, custom generics\n2. **Generic function calls** — `createStore<any>()`, `useState<any>()`\n3. **Multiple `any` arguments** — `Map<any, any>` reports two errors\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 type assertions (`value as any`) — use `llm-core/no-type-assertion-any` for this\n- Generic type parameter definitions (`interface Foo<T = any>`) — these define defaults, not usage\n\n## Error Messages\n\nThe error message teaches the proper alternative:\n\n1. **What's wrong** — `any` inside a generic disables type checking for contained values\n2. **Why** — the `any` leaks into every access and assignment through the container\n3. **How to fix** — use a specific type, or `unknown` with type guards for dynamic data\n",
16
+ "no-async-array-callbacks": "# llm-core/no-async-array-callbacks\n\nšŸ“ Disallow async callbacks passed to array methods where Promises are silently discarded or misused.\n\nšŸ’¼ This rule is enabled in the following configs: 🌐 `all`, šŸ† `best-practices`, āœ… `recommended`.\n\n<!-- end auto-generated rule header -->\n\nDisallow passing async functions to array methods where the returned Promises are silently discarded or semantically wrong.\n\n## Rule Details\n\nLLMs frequently write `items.forEach(async (item) => { await ... })` or `items.filter(async (item) => { return await check(item) })` assuming the method understands async callbacks. It doesn't — these methods are synchronous and either ignore the return value (forEach) or receive a Promise object where a boolean was expected (filter, some, every).\n\n### Methods that always flag async callbacks\n\n| Method | Why async is wrong |\n| --------- | --------------------------------------------------------------- |\n| `forEach` | Return value is always ignored; Promises are silently discarded |\n| `filter` | Receives a truthy Promise object, not the resolved boolean |\n| `some` | Receives a truthy Promise, always returns `true` |\n| `every` | Receives a truthy Promise, always returns `true` |\n| `reduce` | Accumulator becomes a Promise, chaining breaks silently |\n| `flatMap` | Returns an array of Promises instead of flattened values |\n\n### `map` — conditional flagging\n\n`items.map(async ...)` returns an array of Promises. This rule only allows that pattern when the result is **immediately awaited or returned** through `Promise.all(...)` or `Promise.allSettled(...)`. The rule flags async map callbacks in all other contexts.\n\n## Examples\n\n### Incorrect\n\n```ts\n// forEach: Promises are silently discarded\nitems.forEach(async (item) => {\n await processItem(item);\n});\n\n// filter: resolves to [Promise, Promise, ...] not booleans\nconst active = items.filter(async (item) => {\n return await isActive(item);\n});\n\n// map without Promise.all: returns array of unresolved Promises\nconst results = items.map(async (item) => {\n return await fetchData(item);\n});\n\n// Promise.all without await/return still drops the outer promise\nPromise.all(\n items.map(async (item) => {\n return await fetchData(item);\n }),\n);\n```\n\n### Correct\n\n```ts\n// Sequential with for...of\nfor (const item of items) {\n await processItem(item);\n}\n\n// Parallel with Promise.all — the documented safe pattern for map\nawait Promise.all(\n items.map(async (item) => {\n return await processItem(item);\n }),\n);\n\n// Synchronous callbacks are always fine\nitems.forEach((item) => process(item));\nitems.filter((item) => item.active);\n```\n\n## What This Rule Does NOT Catch\n\n- Named function references (e.g., `forEach(processItem)`) — cannot determine if the referenced function is async without type information\n- Async callbacks on named function references stored elsewhere\n\n## Syntax-Only Limitation\n\nThis rule is intentionally syntax-only. It looks for familiar method names like `.forEach()`, `.filter()`, or `.map()` and does **not** use type information to prove that the receiver is a real JavaScript array.\n\nThat means custom collection APIs that intentionally expose array-like method names may also be flagged. If your codebase relies on async-aware custom collections, scope or disable this rule for those files.\n\n## Error Messages\n\nTwo distinct messages are used:\n\n1. **`noAsyncArrayCallback`** — for forEach, filter, some, every, reduce, flatMap: teaches that Promises are discarded or misused and offers concrete alternatives\n2. **`noAsyncMapCallback`** — for map without immediate await/return through `Promise.all(...)`: teaches the `await Promise.all(items.map(...))` pattern\n",
17
+ "no-commented-out-code": "# llm-core/no-commented-out-code\n\nšŸ“ Disallow commented-out code to keep the codebase clean and reduce noise.\n\nšŸ’¼ This rule is enabled in the following configs: 🌐 `all`, 🧹 `hygiene`, āœ… `recommended`.\n\n<!-- end auto-generated rule header -->\n\nDisallow commented-out code to keep the codebase clean and reduce noise.\n\n## Rule Details\n\nLLMs frequently leave commented-out code in their output — old implementations, \"alternative approaches\", half-removed logic, and abandoned experiments. This is one of the most recognizable LLM code smells. Commented-out code adds noise, creates confusion about intent, and makes files harder to read.\n\nThis rule detects comments that contain code-like patterns (variable declarations, function calls, control flow, imports/exports) and flags them for removal.\n\n## Examples\n\n### Incorrect\n\n```ts\n// const oldConfig = loadLegacyConfig();\n// if (oldConfig.enabled) {\n// processLegacy(oldConfig);\n// }\nfunction loadConfig() {\n return getNewConfig();\n}\n```\n\n```ts\n/* const previousImplementation = async () => {\n const data = await fetchData();\n return transform(data);\n}; */\nasync function fetchAndTransform() {\n return newApproach();\n}\n```\n\n```ts\n// import { oldHelper } from './utils';\n\n// processData(items);\n\nfunction main() {\n newProcess(items);\n}\n```\n\n### Correct\n\n```ts\n// This function handles the new config loading approach\nfunction loadConfig() {\n return getNewConfig();\n}\n```\n\n```ts\n// TODO: add caching support in next sprint\nasync function fetchAndTransform() {\n return newApproach();\n}\n```\n\n```ts\n/**\n * @param items - The items to process\n * @returns The processed result\n */\nfunction main(items: Item[]) {\n return newProcess(items);\n}\n```\n\n## What This Rule Allows\n\nThe following comment patterns are **not** flagged:\n\n- **Prose comments** — explanations, notes, descriptions\n- **TODO/FIXME/HACK/NOTE** — task markers\n- **JSDoc comments** — documentation blocks starting with `/**`\n- **ESLint directives** — `eslint-disable`, `eslint-enable`\n- **TypeScript directives** — `@ts-expect-error`, `@ts-ignore`\n- **URLs** — links to documentation or references\n- **Short comments** — comments under 3 characters\n\n## Detection Heuristic\n\nThe rule uses pattern matching to identify code in comments. It looks for:\n\n- Variable declarations (`const`, `let`, `var`)\n- Function and class declarations\n- Import/export statements\n- Control flow (`if`, `for`, `while`, `switch`, `try/catch`)\n- Function/method calls ending with semicolons\n- Assignments ending with semicolons\n- `await` expressions\n- `return`/`throw` statements\n\nConsecutive line comments (`//`) on adjacent lines are grouped and analyzed together, catching multi-line commented-out code blocks.\n\n## Error Messages\n\nError messages follow a structured teaching format designed for LLM self-correction:\n\n1. **What's wrong** — identifies the commented-out code\n2. **Why** — explains that git preserves history, making commented-out code unnecessary\n3. **How to fix** — instructs to delete the code or link to the relevant commit\n",
18
+ "no-empty-catch": "# llm-core/no-empty-catch\n\nšŸ“ Disallow catch blocks with no meaningful error handling (empty or comment-only blocks).\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 are empty, comment-only, or contain only empty statements such as `;`.\n\n## Rule Details\n\nLLMs frequently generate empty catch blocks or catch blocks with only a comment like `// ignore`. This silently swallows errors, making bugs invisible and systems degrade without warning. Even when error suppression is intentional, an empty block gives future readers no signal that the omission was deliberate.\n\nA catch block is considered **meaningful** when it contains at least one executable statement: a log call, a `throw`, a `return`, a variable assignment, or any other expression statement.\n\nA catch block containing **only comments** (including `// intentional`) is flagged — a comment is documentation, not handling. A block containing only semicolons is also flagged, because `;` performs no handling.\n\n## Examples\n\n### Incorrect\n\n```ts\n// Empty — errors are completely hidden\ntry {\n await fetchData();\n} catch (e) {}\n\n// Comment-only — no actual handling\ntry {\n await saveRecord();\n} catch (e) {\n // TODO: handle this\n}\n\n// Semicolon-only — still no handling\ntry {\n await saveRecord();\n} catch {}\n```\n\n### Correct\n\n```ts\n// Log and continue\ntry {\n await sendNotification(user);\n} catch (e) {\n logger.error(\"notification failed\", e);\n}\n\n// Rethrow with context\ntry {\n await writeFile(path, data);\n} catch (e) {\n throw new Error(`failed to write ${path}`, { cause: e });\n}\n\n// Return a safe default\nfunction safeParse(json: string): unknown {\n try {\n return JSON.parse(json);\n } catch {\n return null;\n }\n}\n\n// Rethrow is meaningful — the catch was used for cleanup\ntry {\n await tx.begin();\n await doWork();\n} catch (e) {\n await tx.rollback();\n throw e;\n}\n```\n\n## What Counts as Meaningful\n\n| Statement in catch body | Meaningful? |\n| ----------------------------------- | ----------- |\n| `throw e` / `throw new Error(...)` | āœ… Yes |\n| `return` / `return null` | āœ… Yes |\n| `logger.error(e)` or any expression | āœ… Yes |\n| `const msg = e.message` | āœ… Yes |\n| `// comment` only | āŒ No |\n| `;` only | āŒ No |\n| empty `{}` | āŒ No |\n\n## Error Messages\n\nThe error message teaches:\n\n1. **What's wrong** — catch block silently swallows errors\n2. **Why** — failures become invisible, debugging is impossible\n3. **How to fix** — log, rethrow, return default, or document with a real statement\n",
19
+ "no-exported-function-expressions": "# llm-core/no-exported-function-expressions\n\nšŸ“ Enforce that exported functions use function declarations instead of function expressions or arrow functions.\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\nEnforce that exported functions use function declarations instead of function expressions or arrow functions.\n\n## Rationale\n\nLLMs consistently default to `export const fn = () => {}` when generating code. Function declarations are preferred because:\n\n- **Hoisting**: Function declarations are hoisted, making code order more flexible\n- **Stack traces**: Named function declarations produce clearer stack traces for debugging\n- **Intent**: Declarations signal \"this is a named function\" more clearly than variable assignments\n- **Consistency**: Enforces a single style across the codebase\n\nThis rule provides structured error messages that teach the LLM the correct pattern. Through iteration, the LLM learns to use function declarations automatically.\n\n## Rule Details\n\nThis rule reports when:\n\n1. A named export uses an arrow function or function expression (`export const fn = () => {}`)\n2. A default export uses an anonymous arrow function (`export default () => {}`)\n3. A default export uses an anonymous function expression (`export default function() {}`)\n\n### What This Rule Catches\n\n| Pattern | Caught? |\n| ---------------------------------------- | ---------- |\n| `export const fn = () => {}` | Yes |\n| `export const fn = async () => {}` | Yes |\n| `export const fn = function() {}` | Yes |\n| `export const fn = async function() {}` | Yes |\n| `export default () => {}` | Yes |\n| `export default function() {}` | Yes |\n| `export function fn() {}` | No (valid) |\n| `export default function fn() {}` | No (valid) |\n| `const fn = () => {}` (non-exported) | No (valid) |\n| `export const value = 42` (non-function) | No (valid) |\n\n## Examples\n\n### Incorrect\n\n```ts\n// Arrow function exports\nexport const fetchData = async () => {\n return await api.get('/data');\n};\n\n// Function expression exports\nexport const processItem = function(item) {\n return item.value * 2;\n};\n\n// Anonymous default exports\nexport default () => <div>Hello</div>;\n\n// Expression body arrows\nexport const double = (x: number) => x * 2;\n```\n\n### Correct\n\n```ts\n// Named function declarations\nexport async function fetchData() {\n return await api.get('/data');\n}\n\nexport function processItem(item) {\n return item.value * 2;\n}\n\n// Named default export\nexport default function HelloComponent() {\n return <div>Hello</div>;\n}\n\nexport function double(x: number) {\n return x * 2;\n}\n\n// Non-exported arrow functions are fine (internal helpers)\nconst helper = () => 'internal';\n```\n\n## Suggestions\n\nThis rule provides manual fix suggestions (not auto-fix) to convert function expressions to declarations. Auto-fix is intentionally avoided because converting arrow functions to declarations can change `this` binding semantics in some contexts.\n\n## No Escape Hatches\n\nFollowing the [deterministic linting approach](https://understandingdata.com/posts/custom-eslint-rules-determinism/), this rule is designed to be used alongside `eslint-comments/no-use` or similar rules that prevent `eslint-disable` comments. This ensures LLM agents cannot bypass the rule and must learn the correct pattern.\n\n## Error Message Format\n\nError messages follow a structured teaching format:\n\n1. **What's wrong**: Clear statement of the violation\n2. **Why**: Explanation of why the pattern is preferred\n3. **How to fix**: Concrete before/after code example\n\nThis format is designed to be parseable by LLMs, enabling self-correction through the lint-fix loop.\n",
20
+ "no-floating-promise": "# llm-core/no-floating-promise\n\nšŸ“ Disallow calling async functions or Promise-returning expressions without awaiting, returning, or explicitly voiding the result.\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\nLLMs frequently call async functions and drop the returned Promise. A floating Promise loses its rejection (turning into an unhandled promise rejection), breaks execution ordering, and is one of the most common async bugs in generated code.\n\nThis rule flags three AST-detectable patterns at statement position, without requiring TypeScript type information:\n\n1. **Async function references** — a call whose callee resolves, in the current file, to an `async function` declaration or a variable initialized to an async arrow/function expression.\n2. **Promise factory calls** — `Promise.all`, `Promise.allSettled`, `Promise.race`, `Promise.any`, `Promise.resolve`, and `Promise.reject`.\n3. **Unhandled `.then()` chains** — a top-level `.then(handler)` call with fewer than two arguments (so the rejection path has no handler).\n\nThe rule fires only when the call is the expression of an `ExpressionStatement`. Calls consumed by `await`, `return`, `void`, a variable declarator, or an assignment are not statements and are never flagged.\n\n## When Not To Use It\n\nThis rule uses AST heuristics, not TypeScript's type checker. If your project enables `@typescript-eslint/no-floating-promises` with full type information, prefer that rule — it catches strictly more floating Promises. Use this rule when a type-aware lint is not available, or as an additional teaching-message layer on top.\n\n## Examples\n\n### Incorrect\n\n```ts\nasync function saveData() {\n /* ... */\n}\n\n// Async function call at statement position — result is discarded\nsaveData();\n\n// Promise factory at statement position\nPromise.all([p1, p2]);\n\n// .then() chain without a rejection handler\nfetchData().then((data) => process(data));\n```\n\n### Correct\n\n```ts\nasync function saveData() {\n /* ... */\n}\n\n// Await the result\nawait saveData();\n\n// Return it to the caller\nasync function run() {\n return saveData();\n}\n\n// Store for later awaiting\nasync function run() {\n const p = saveData();\n await p;\n}\n\n// Explicit fire-and-forget\nvoid saveData();\n\n// Attach a rejection handler\nsaveData().catch((error) => logger.error(\"save failed\", error));\n\n// Two-argument .then() handles both paths\nfetchData().then(handle, onError);\n\n// Chain ends with .catch()\nfetchData()\n .then(handle)\n .catch((error) => logger.error(error));\n```\n\n## Detected Patterns\n\n| Pattern at statement position | Flagged? |\n| ---------------------------------------- | -------- |\n| `asyncFn();` (declared async in scope) | Yes |\n| `Promise.all([p1, p2]);` | Yes |\n| `Promise.resolve(x);` | Yes |\n| `foo().then(handler);` | Yes |\n| `foo().then(handler, onError);` | No |\n| `foo().catch(handler);` | No |\n| `foo().then(handler).catch(onError);` | No |\n| `void asyncFn();` | No |\n| `await asyncFn();` / `return asyncFn();` | No |\n\n## Error Messages\n\nThe error message teaches:\n\n1. **What's wrong** — the returned Promise at statement position is discarded\n2. **Why** — dropped Promises lose their rejection and break execution ordering\n3. **How to fix** — `await`, `return`, `void`, or attach `.catch(...)`\n",
21
+ "no-hallucinated-local-imports": "# llm-core/no-hallucinated-local-imports\n\nšŸ“ Disallow static relative imports and re-exports that reference missing local modules or named exports.\n\nšŸ’¼ This rule is enabled in the 🌐 `all` config.\n\n<!-- end auto-generated rule header -->\n\nDisallow static relative `import` and `export ... from` declarations that point to missing local modules or request named exports that the target module does not directly export.\n\n## Rule Details\n\nLLM coding agents often invent nearby file names or helper export names that sound plausible in the current codebase. These hallucinated imports are high-signal failures: the code usually cannot build, type-check, or run.\n\nThis rule is intentionally narrow for v1:\n\n- checks only relative local paths that start with `./` or `../`\n- checks only static `import` declarations and `export ... from` re-exports\n- resolves explicit files, common JS/TS extensions, and `index` files\n- checks direct named exports in the resolved module\n- ignores package imports, path aliases, dynamic imports, and re-export chains\n\n## Examples\n\n### Incorrect\n\n```ts\nimport { createCache } from \"./cache-utils\";\n// ./cache-utils does not resolve to a local file\n\nimport { createCacheFactory } from \"./cache\";\n// ./cache exists, but it does not directly export createCacheFactory\n\nexport { parseUser } from \"../users/parser\";\n// ../users/parser exists, but parseUser is not directly exported there\n```\n\n### Correct\n\n```ts\nimport { createCache } from \"./cache\";\nimport { parseUserRecord } from \"../users/parser\";\nexport { createCache } from \"./cache\";\n```\n\n## What This Rule Flags\n\n| Import/export shape | Flagged? |\n| ------------------------------------------ | -------- |\n| `import { x } from \"./missing\"` | āœ… Yes |\n| `export { x } from \"./missing\"` | āœ… Yes |\n| `import { missing } from \"./existing\"` | āœ… Yes |\n| `import { existing } from \"./existing\"` | āŒ No |\n| `import pkg from \"package\"` | āŒ No |\n| `import(\"./missing\")` | āŒ No |\n| `import { x } from \"@/aliased/module\"` | āŒ No |\n| indirect export through a barrel/re-export | āŒ No |\n\n## Error Messages\n\nThe teaching messages explain:\n\n1. **What's wrong** — the local module path or named export does not exist\n2. **Why** — hallucinated imports are a common LLM failure mode that break builds\n3. **How to fix** — update the import to an existing local module/export, or add the missing export intentionally\n",
22
+ "no-incorrect-sort": "# llm-core/no-incorrect-sort\n\nšŸ“ Disallow .sort() without a compare function, which coerces elements to strings and produces incorrect numeric ordering.\n\nšŸ’¼ This rule is enabled in the 🌐 `all` config.\n\n<!-- end auto-generated rule header -->\n\n## Rule details\n\nThis rule flags `.sort()` calls that omit a compare function. JavaScript's default sort converts elements to strings before comparing, so `[10, 2, 1].sort()` produces `[1, 10, 2]` instead of `[1, 2, 10]`.\n\nFlagged patterns:\n\n```js\nnums.sort(); // no compare function\narr.sort(undefined); // undefined is not a comparator\narr.sort(void 0); // void 0 is semantically undefined\n```\n\nCorrect patterns:\n\n```js\nnums.sort((a, b) => a - b); // numeric ascending\nnames.sort((a, b) => a.localeCompare(b)); // locale-aware string\nitems.sort((a, b) => a.name.localeCompare(b.name)); // object property\n```\n\n## Scope and limitations\n\nThis rule operates on static AST analysis without type information. It:\n\n- **Skips TypedArray receivers** — `new Int32Array([...]).sort()` is correct because `%TypedArray%.prototype.sort` uses numeric comparison by default.\n- **Does not track variable types** — `const buffer = new Float64Array(data); buffer.sort();` will be flagged because the receiver is a plain identifier, not a `new` expression. For files with known TypedArray variables, disable the rule at the config level:\n ```js\n // eslint.config.mjs\n export default [\n { ignores: [\"src/buffers/*.ts\"] },\n // or: { files: [\"src/buffers/*.ts\"], rules: { \"llm-core/no-incorrect-sort\": \"off\" } },\n ];\n ```\n- **May flag custom APIs** — Methods named `.sort()` on query builders, ORM cursors, or RxJS observables have no relation to `Array.prototype.sort`. Disable the rule in those scopes.\n",
23
+ "no-inline-disable": "# llm-core/no-inline-disable\n\nšŸ“ Disallow eslint-disable comments that suppress lint errors instead of fixing them.\n\nšŸ’¼ This rule is enabled in the following configs: 🌐 `all`, 🧹 `hygiene`, āœ… `recommended`.\n\n<!-- end auto-generated rule header -->\n\nDisallow eslint-disable comments that suppress lint errors instead of fixing them.\n\n## Rule Details\n\nLLMs frequently add `// eslint-disable-next-line` or `/* eslint-disable */` comments to silence lint errors rather than fixing the underlying code. This bypasses all other lint rules, defeating the purpose of linting.\n\nThis rule catches:\n\n- `// eslint-disable-next-line`\n- `// eslint-disable-line`\n- `/* eslint-disable ... */`\n- `/* eslint-enable ... */`\n\n## Examples\n\n### Incorrect\n\n```ts\n// eslint-disable-next-line no-unused-vars\nconst x = 1;\n\n/* eslint-disable no-console */\nconsole.log(\"debug\");\n/* eslint-enable no-console */\n\nconst y = 1; // eslint-disable-line no-unused-vars\n```\n\n### Correct\n\n```ts\n// Fix the code instead of disabling the rule\nconst x = computeValue();\nuseValue(x);\n\n// Use a proper logger instead of console\nlogger.debug(\"debug info\", { context });\n\n// If a rule doesn't apply to certain files, configure it in eslint.config.mjs\n// instead of disabling inline\n```\n\n## Why No Escape Hatches?\n\nWhen an LLM encounters a lint error, it has two options:\n\n1. Fix the code to satisfy the rule\n2. Add a disable comment to suppress the error\n\nOption 2 is always easier, so LLMs default to it. This rule removes that escape hatch, forcing the LLM to actually fix the code — which is the entire point of linting.\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 disable comment\n2. **Why** — explains that suppressing rules hides problems\n3. **How to fix** — three alternatives: fix the code, configure the rule properly, or refactor\n",
24
+ "no-llm-artifacts": "# llm-core/no-llm-artifacts\n\nšŸ“ Disallow common LLM placeholder comments and incomplete code markers that indicate skipped implementation.\n\nšŸ’¼ This rule is enabled in the following configs: 🌐 `all`, 🧹 `hygiene`, āœ… `recommended`.\n\n<!-- end auto-generated rule header -->\n\nDisallow common LLM placeholder comments and stub function bodies that indicate incomplete code generation.\n\n## Rule Details\n\nLLMs frequently save tokens by outputting placeholder comments like `// ... existing code ...`, `// TODO: implement`, or `// rest of the function remains the same` instead of writing the actual code. When applied automatically by an agent, these placeholders silently replace real logic with nothing.\n\nThis rule detects two categories of LLM artifacts:\n\n1. **Placeholder comments** — Comments that indicate skipped or abbreviated code\n2. **Stub function bodies** — Functions whose only statement is `throw new Error(\"Not implemented\")`\n\n## Examples\n\n### Incorrect\n\n```ts\n/* eslint llm-core/no-llm-artifacts: \"error\" */\n\n// ... existing code ...\nfunction processData(items: Item[]) {\n // implementation goes here\n}\n\n// rest of the function remains the same\n\nfunction validate(input: string) {\n throw new Error(\"Not implemented\");\n}\n\n// abbreviated for brevity\n// add error handling as needed\n```\n\n### Correct\n\n```ts\n/* eslint llm-core/no-llm-artifacts: \"error\" */\n\n// This function processes items by filtering and transforming\nfunction processData(items: Item[]) {\n return items.filter(isValid).map(transform);\n}\n\n// TODO(#123): add caching for repeated lookups\nfunction validate(input: string) {\n if (!input.trim()) {\n throw new Error(\"Input must not be empty\");\n }\n return schema.parse(input);\n}\n```\n\n## Detected Patterns\n\n### Placeholder Comments\n\n| Pattern | Example |\n| ---------------------- | ---------------------------------------------------------- |\n| Ellipsis placeholders | `// ... existing code ...`, `// ...rest of the code...` |\n| \"Remains the same\" | `// rest of the function remains the same` |\n| Lazy TODO | `// TODO: implement`, `// TODO implement` |\n| \"Code here\" markers | `// add implementation here`, `// your code here` |\n| \"As needed\" deferrals | `// add error handling as needed` |\n| Brevity markers | `// abbreviated for brevity`, `// omitted for clarity` |\n| Reference to elsewhere | `// similar to above`, `// same as before` |\n| \"Continue here\" | `// continue from here`, `// continue implementation here` |\n| \"See above/below\" | `// see implementation above`, `// see above for details` |\n\n### Stub Function Bodies\n\nFunctions whose only statement is `throw new Error(msg)` where `msg` matches \"not implemented\" or \"TODO: implement\" (case-insensitive).\n\n## What This Rule Allows\n\n- **Specific TODOs** — `// TODO(#123): validate input against schema` (has actionable context)\n- **Prose comments** — `// This handles the edge case where input is empty`\n- **Legitimate throws** — `throw new Error(\"XML format is not supported\")` (real error, not a placeholder)\n- **Functions with real logic** — Even if they contain a \"not implemented\" throw alongside other statements\n\n## Error Messages\n\n### `noLlmArtifact`\n\n> LLM placeholder detected: '...'. Replace with actual implementation.\n\nFired for placeholder comments. The message explains that these comments replace real logic when applied, and suggests writing specific TODOs if work must be deferred.\n\n### `notImplementedStub`\n\n> Function body is a 'not implemented' stub — write the actual implementation.\n\nFired for functions whose sole body is a \"not implemented\" throw. The message explains that these stubs pass type checks but fail at runtime.\n",
25
+ "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",
26
+ "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",
27
+ "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",
28
+ "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",
29
+ "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",
30
+ "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",
31
+ "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",
32
+ "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",
33
+ "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",
34
+ "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",
35
+ };
36
+ 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-commented-out-code", "no-empty-catch", "no-exported-function-expressions", "no-floating-promise", "no-hallucinated-local-imports", "no-incorrect-sort", "no-inline-disable", "no-llm-artifacts", "no-magic-numbers", "no-redundant-logic", "no-swallowed-errors", "no-type-assertion-any", "prefer-early-return", "prefer-nullish-coalescing", "prefer-unknown-in-catch", "structured-logging", "throw-error-objects", "uninvoked-array-callback"];
37
+ //# sourceMappingURL=embedded-docs.js.map
@@ -0,0 +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,uBAAuB,EAAE,8kGAA8kG;IACvmG,gBAAgB,EAAE,kxFAAkxF;IACpyF,kCAAkC,EAAE,6/HAA6/H;IACjiI,qBAAqB,EAAE,4iHAA4iH;IACnkH,+BAA+B,EAAE,qhFAAqhF;IACtjF,mBAAmB,EAAE,gwDAAgwD;IACrxD,mBAAmB,EAAE,w7DAAw7D;IAC78D,kBAAkB,EAAE,63HAA63H;IACj5H,kBAAkB,EAAE,o5GAAo5G;IACx6G,oBAAoB,EAAE,g1HAAg1H;IACt2H,qBAAqB,EAAE,kyFAAkyF;IACzzF,uBAAuB,EAAE,oqEAAoqE;IAC7rE,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,uBAAuB,EAAC,gBAAgB,EAAC,kCAAkC,EAAC,qBAAqB,EAAC,+BAA+B,EAAC,mBAAmB,EAAC,mBAAmB,EAAC,kBAAkB,EAAC,kBAAkB,EAAC,oBAAoB,EAAC,qBAAqB,EAAC,uBAAuB,EAAC,qBAAqB,EAAC,2BAA2B,EAAC,yBAAyB,EAAC,oBAAoB,EAAC,qBAAqB,EAAC,0BAA0B,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { server, startServer } from "./server.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { server, startServer } from "./server.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerRuleResources(server: McpServer): void;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/resources/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIzE,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAG7D"}
@@ -0,0 +1,7 @@
1
+ import { registerRuleDocResource } from "./rule-doc.js";
2
+ import { registerRulesListResource } from "./rules-list.js";
3
+ export function registerRuleResources(server) {
4
+ registerRulesListResource(server);
5
+ registerRuleDocResource(server);
6
+ }
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/resources/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAE5D,MAAM,UAAU,qBAAqB,CAAC,MAAiB;IACrD,yBAAyB,CAAC,MAAM,CAAC,CAAC;IAClC,uBAAuB,CAAC,MAAM,CAAC,CAAC;AAClC,CAAC"}
@@ -0,0 +1,12 @@
1
+ type RuleCategory = "complexity" | "typescript" | "best-practices" | "style" | "hygiene";
2
+ interface RuleListEntry {
3
+ name: string;
4
+ description: string;
5
+ hasInstruction: boolean;
6
+ category: RuleCategory;
7
+ }
8
+ export declare function getRuleNames(): string[];
9
+ export declare function getRuleListEntries(): RuleListEntry[];
10
+ export declare function getCategoryCounts(entries: RuleListEntry[]): Record<RuleCategory, number>;
11
+ export {};
12
+ //# sourceMappingURL=rule-data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-data.d.ts","sourceRoot":"","sources":["../../src/resources/rule-data.ts"],"names":[],"mappings":"AAGA,KAAK,YAAY,GACb,YAAY,GACZ,YAAY,GACZ,gBAAgB,GAChB,OAAO,GACP,SAAS,CAAC;AAEd,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,EAAE,YAAY,CAAC;CACxB;AAuDD,wBAAgB,YAAY,IAAI,MAAM,EAAE,CAIvC;AAED,wBAAgB,kBAAkB,IAAI,aAAa,EAAE,CAcpD;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,aAAa,EAAE,GACvB,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAY9B"}
@@ -0,0 +1,57 @@
1
+ import plugin from "eslint-plugin-llm-core";
2
+ import { getRuleInstruction } from "eslint-plugin-llm-core/instructions";
3
+ const llmCorePlugin = plugin;
4
+ const CATEGORY_CONFIGS = [
5
+ "complexity",
6
+ "typescript",
7
+ "best-practices",
8
+ "style",
9
+ "hygiene",
10
+ ];
11
+ // These rules are currently published only through the broad `all` config. The
12
+ // MCP resource still needs one of the public category labels for every rule.
13
+ const ALL_ONLY_RULE_CATEGORIES = {
14
+ "no-hallucinated-local-imports": "hygiene",
15
+ "no-incorrect-sort": "best-practices",
16
+ };
17
+ function bareRuleId(ruleId) {
18
+ return ruleId.replace(/^llm-core\//, "");
19
+ }
20
+ function rulesInConfig(configName) {
21
+ return new Set((llmCorePlugin.configs[configName] ?? [])
22
+ .flatMap((config) => Object.keys(config.rules ?? {}))
23
+ .map(bareRuleId));
24
+ }
25
+ const ruleCategories = new Map([
26
+ ...CATEGORY_CONFIGS.flatMap((category) => [...rulesInConfig(category)].map((ruleName) => [ruleName, category])),
27
+ ...Object.entries(ALL_ONLY_RULE_CATEGORIES),
28
+ ]);
29
+ export function getRuleNames() {
30
+ return Object.keys(llmCorePlugin.rules).sort((left, right) => left.localeCompare(right));
31
+ }
32
+ export function getRuleListEntries() {
33
+ return getRuleNames().map((name) => {
34
+ const category = ruleCategories.get(name);
35
+ if (!category) {
36
+ throw new Error(`Rule "${name}" is not assigned to a public category.`);
37
+ }
38
+ return {
39
+ name,
40
+ description: llmCorePlugin.rules[name]?.meta?.docs?.description ?? "",
41
+ hasInstruction: getRuleInstruction(name) !== undefined,
42
+ category,
43
+ };
44
+ });
45
+ }
46
+ export function getCategoryCounts(entries) {
47
+ return {
48
+ complexity: entries.filter((entry) => entry.category === "complexity")
49
+ .length,
50
+ typescript: entries.filter((entry) => entry.category === "typescript")
51
+ .length,
52
+ "best-practices": entries.filter((entry) => entry.category === "best-practices").length,
53
+ style: entries.filter((entry) => entry.category === "style").length,
54
+ hygiene: entries.filter((entry) => entry.category === "hygiene").length,
55
+ };
56
+ }
57
+ //# sourceMappingURL=rule-data.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-data.js","sourceRoot":"","sources":["../../src/resources/rule-data.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { type McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerRuleDocResource(server: McpServer): void;
3
+ //# sourceMappingURL=rule-doc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-doc.d.ts","sourceRoot":"","sources":["../../src/resources/rule-doc.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,SAAS,EACf,MAAM,yCAAyC,CAAC;AA2DjD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA+C/D"}
@@ -0,0 +1,80 @@
1
+ import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
2
+ import { ResourceTemplate, } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { readdir, readFile } from "node:fs/promises";
4
+ import { basename, dirname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { getRuleListEntries, getRuleNames } from "./rule-data.js";
7
+ const RULE_DOC_TEMPLATE = "llm-core://rules/{ruleName}";
8
+ let docsCache;
9
+ async function readDocsFromRepo() {
10
+ let current = dirname(fileURLToPath(import.meta.url));
11
+ for (let depth = 0; depth < 8; depth += 1) {
12
+ const docsDir = join(current, "docs", "rules");
13
+ const files = await readdir(docsDir).catch(() => null);
14
+ if (files) {
15
+ const entries = await Promise.all(files
16
+ .filter((file) => file.endsWith(".md"))
17
+ .map(async (file) => [
18
+ basename(file, ".md"),
19
+ await readFile(join(docsDir, file), "utf8"),
20
+ ]));
21
+ return Object.fromEntries(entries);
22
+ }
23
+ const parent = dirname(current);
24
+ if (parent === current) {
25
+ break;
26
+ }
27
+ current = parent;
28
+ }
29
+ return {};
30
+ }
31
+ async function loadDocs() {
32
+ if (!docsCache) {
33
+ docsCache = (async () => {
34
+ const generatedModulePath = "../embedded-docs.js";
35
+ const generated = (await import(generatedModulePath).catch(() => null));
36
+ return generated?.embeddedDocs ?? readDocsFromRepo();
37
+ })();
38
+ }
39
+ return docsCache;
40
+ }
41
+ function ruleNameFromVariables(ruleName) {
42
+ return Array.isArray(ruleName) ? String(ruleName[0] ?? "") : String(ruleName);
43
+ }
44
+ export function registerRuleDocResource(server) {
45
+ const template = new ResourceTemplate(RULE_DOC_TEMPLATE, {
46
+ list: async () => ({
47
+ resources: getRuleListEntries().map((rule) => ({
48
+ uri: `llm-core://rules/${rule.name}`,
49
+ name: rule.name,
50
+ description: rule.description,
51
+ mimeType: "text/markdown",
52
+ })),
53
+ }),
54
+ complete: {
55
+ ruleName: (value) => getRuleNames().filter((ruleName) => ruleName.startsWith(value)),
56
+ },
57
+ });
58
+ server.registerResource("llm-core-rule-doc", template, {
59
+ title: "llm-core Rule Documentation",
60
+ description: "Full markdown documentation for a single llm-core rule.",
61
+ mimeType: "text/markdown",
62
+ }, async (uri, variables) => {
63
+ const ruleName = ruleNameFromVariables(variables.ruleName);
64
+ const docs = await loadDocs();
65
+ const markdown = docs[ruleName];
66
+ if (!markdown) {
67
+ throw new McpError(ErrorCode.InvalidParams, `Unknown llm-core rule "${ruleName}". Available rules: ${getRuleNames().join(", ")}`);
68
+ }
69
+ return {
70
+ contents: [
71
+ {
72
+ uri: uri.toString(),
73
+ mimeType: "text/markdown",
74
+ text: markdown,
75
+ },
76
+ ],
77
+ };
78
+ });
79
+ }
80
+ //# sourceMappingURL=rule-doc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-doc.js","sourceRoot":"","sources":["../../src/resources/rule-doc.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerRulesListResource(server: McpServer): void;
3
+ //# sourceMappingURL=rules-list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules-list.d.ts","sourceRoot":"","sources":["../../src/resources/rules-list.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKzE,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA4BjE"}
@@ -0,0 +1,26 @@
1
+ import { getCategoryCounts, getRuleListEntries } from "./rule-data.js";
2
+ const RULES_URI = "llm-core://rules";
3
+ export function registerRulesListResource(server) {
4
+ server.registerResource("llm-core-rules", RULES_URI, {
5
+ title: "llm-core Rule Listing",
6
+ description: "Lists every eslint-plugin-llm-core rule and its category.",
7
+ mimeType: "application/json",
8
+ }, async () => {
9
+ const rules = getRuleListEntries();
10
+ const payload = {
11
+ total: rules.length,
12
+ categories: getCategoryCounts(rules),
13
+ rules,
14
+ };
15
+ return {
16
+ contents: [
17
+ {
18
+ uri: RULES_URI,
19
+ mimeType: "application/json",
20
+ text: JSON.stringify(payload, null, 2),
21
+ },
22
+ ],
23
+ };
24
+ });
25
+ }
26
+ //# sourceMappingURL=rules-list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules-list.js","sourceRoot":"","sources":["../../src/resources/rules-list.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEvE,MAAM,SAAS,GAAG,kBAAkB,CAAC;AAErC,MAAM,UAAU,yBAAyB,CAAC,MAAiB;IACzD,MAAM,CAAC,gBAAgB,CACrB,gBAAgB,EAChB,SAAS,EACT;QACE,KAAK,EAAE,uBAAuB;QAC9B,WAAW,EAAE,2DAA2D;QACxE,QAAQ,EAAE,kBAAkB;KAC7B,EACD,KAAK,IAAI,EAAE;QACT,MAAM,KAAK,GAAG,kBAAkB,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG;YACd,KAAK,EAAE,KAAK,CAAC,MAAM;YACnB,UAAU,EAAE,iBAAiB,CAAC,KAAK,CAAC;YACpC,KAAK;SACN,CAAC;QAEF,OAAO;YACL,QAAQ,EAAE;gBACR;oBACE,GAAG,EAAE,SAAS;oBACd,QAAQ,EAAE,kBAAkB;oBAC5B,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;iBACvC;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ export declare const server: McpServer;
4
+ export declare function startServer(): Promise<void>;
5
+ export declare function isDirectRun(entryPoint?: string, moduleUrl?: string): boolean;
6
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAcpE,eAAO,MAAM,MAAM,WAGjB,CAAC;AASH,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAGjD;AAUD,wBAAgB,WAAW,CACzB,UAAU,SAAkB,EAC5B,SAAS,SAAkB,GAC1B,OAAO,CAST"}
package/dist/server.js ADDED
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { realpathSync } from "node:fs";
5
+ import { createRequire } from "node:module";
6
+ import path from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { registerRuleResources } from "./resources/index.js";
9
+ import { registerGetActiveInstructions } from "./tools/get-active-instructions.js";
10
+ import { registerLintFile } from "./tools/lint-file.js";
11
+ const require = createRequire(import.meta.url);
12
+ const { version } = require("../package.json");
13
+ export const server = new McpServer({
14
+ name: "eslint-plugin-llm-core-mcp",
15
+ version,
16
+ });
17
+ // Tools
18
+ registerGetActiveInstructions(server);
19
+ registerLintFile(server);
20
+ // Resources
21
+ registerRuleResources(server);
22
+ export async function startServer() {
23
+ const transport = new StdioServerTransport();
24
+ await server.connect(transport);
25
+ }
26
+ function realPathOrResolved(filePath) {
27
+ try {
28
+ return realpathSync(filePath);
29
+ }
30
+ catch {
31
+ return path.resolve(filePath);
32
+ }
33
+ }
34
+ export function isDirectRun(entryPoint = process.argv[1], moduleUrl = import.meta.url) {
35
+ if (!entryPoint) {
36
+ return false;
37
+ }
38
+ return (realPathOrResolved(entryPoint) ===
39
+ realPathOrResolved(fileURLToPath(moduleUrl)));
40
+ }
41
+ // Only start when run directly (not imported in tests).
42
+ if (isDirectRun()) {
43
+ startServer().catch((err) => {
44
+ console.error(err);
45
+ process.exit(1);
46
+ });
47
+ }
48
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AACnF,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExD,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,iBAAiB,CAAwB,CAAC;AAEtE,MAAM,CAAC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAClC,IAAI,EAAE,4BAA4B;IAClC,OAAO;CACR,CAAC,CAAC;AAEH,QAAQ;AACR,6BAA6B,CAAC,MAAM,CAAC,CAAC;AACtC,gBAAgB,CAAC,MAAM,CAAC,CAAC;AAEzB,YAAY;AACZ,qBAAqB,CAAC,MAAM,CAAC,CAAC;AAE9B,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,SAAS,kBAAkB,CAAC,QAAgB;IAC1C,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAC5B,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG;IAE3B,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,CACL,kBAAkB,CAAC,UAAU,CAAC;QAC9B,kBAAkB,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAC7C,CAAC;AACJ,CAAC;AAED,wDAAwD;AACxD,IAAI,WAAW,EAAE,EAAE,CAAC;IAClB,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;QACnC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerGetActiveInstructions(server: McpServer): void;
3
+ //# sourceMappingURL=get-active-instructions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-active-instructions.d.ts","sourceRoot":"","sources":["../../src/tools/get-active-instructions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAYzE,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAmCrE"}
@@ -0,0 +1,37 @@
1
+ import { z } from "zod";
2
+ // ESM can import CJS modules via default-export interop; the subpath export
3
+ // resolves to dist/instructions/index.js which is CommonJS.
4
+ import { generateInstructions } from "eslint-plugin-llm-core/instructions";
5
+ const inputSchema = {
6
+ configPath: z
7
+ .string()
8
+ .optional()
9
+ .describe("Optional path to an ESLint config file"),
10
+ };
11
+ export function registerGetActiveInstructions(server) {
12
+ server.registerTool("get_active_instructions", {
13
+ title: "Get Active Lint Instructions",
14
+ description: "Returns the full active rule guidance markdown for the current project. " +
15
+ "Call once at session start to orient without keeping rules in the prompt every turn.",
16
+ inputSchema,
17
+ }, async ({ configPath }) => {
18
+ const result = await generateInstructions(configPath ? { configPath } : undefined);
19
+ const scopeSummary = [
20
+ `allFilesRules: ${result.allFilesRules.length}`,
21
+ `javascriptRules: ${result.javascriptRules.length}`,
22
+ `typescriptRules: ${result.typescriptRules.length}`,
23
+ ].join(", ");
24
+ const text = `${result.content}\n\n<!-- scope counts: ${scopeSummary} -->`;
25
+ return {
26
+ _meta: {
27
+ scopeCounts: {
28
+ allFilesRules: result.allFilesRules.length,
29
+ javascriptRules: result.javascriptRules.length,
30
+ typescriptRules: result.typescriptRules.length,
31
+ },
32
+ },
33
+ content: [{ type: "text", text }],
34
+ };
35
+ });
36
+ }
37
+ //# sourceMappingURL=get-active-instructions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-active-instructions.js","sourceRoot":"","sources":["../../src/tools/get-active-instructions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,4EAA4E;AAC5E,4DAA4D;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAE3E,MAAM,WAAW,GAAG;IAClB,UAAU,EAAE,CAAC;SACV,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,wCAAwC,CAAC;CACtD,CAAC;AAEF,MAAM,UAAU,6BAA6B,CAAC,MAAiB;IAC7D,MAAM,CAAC,YAAY,CACjB,yBAAyB,EACzB;QACE,KAAK,EAAE,8BAA8B;QACrC,WAAW,EACT,0EAA0E;YAC1E,sFAAsF;QACxF,WAAW;KACZ,EACD,KAAK,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE;QACvB,MAAM,MAAM,GAAG,MAAM,oBAAoB,CACvC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,SAAS,CACxC,CAAC;QAEF,MAAM,YAAY,GAAG;YACnB,kBAAkB,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE;YAC/C,oBAAoB,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE;YACnD,oBAAoB,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE;SACpD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEb,MAAM,IAAI,GAAG,GAAG,MAAM,CAAC,OAAO,0BAA0B,YAAY,MAAM,CAAC;QAE3E,OAAO;YACL,KAAK,EAAE;gBACL,WAAW,EAAE;oBACX,aAAa,EAAE,MAAM,CAAC,aAAa,CAAC,MAAM;oBAC1C,eAAe,EAAE,MAAM,CAAC,eAAe,CAAC,MAAM;oBAC9C,eAAe,EAAE,MAAM,CAAC,eAAe,CAAC,MAAM;iBAC/C;aACF;YACD,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,CAAC;SAC3C,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ /**
3
+ * Options that configure the lint_file tool at registration time. Kept separate
4
+ * from the per-call `path` input so tests can target a self-contained fixture
5
+ * project (ESLint cwd + sandbox root) and exercise the directory cap.
6
+ */
7
+ export interface LintFileOptions {
8
+ /** Project root used as the ESLint cwd and sandbox boundary. */
9
+ projectRoot?: string;
10
+ /**
11
+ * Maximum number of lintable files a directory target may contain before the
12
+ * tool refuses and asks the caller to narrow the path (FR-13). Defaults to
13
+ * {@link DEFAULT_MAX_FILES}.
14
+ */
15
+ maxFiles?: number;
16
+ }
17
+ export declare function registerLintFile(server: McpServer, options?: LintFileOptions): void;
18
+ //# sourceMappingURL=lint-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lint-file.d.ts","sourceRoot":"","sources":["../../src/tools/lint-file.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA2GzE;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AA2HD,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,EACjB,OAAO,GAAE,eAAoB,GAC5B,IAAI,CA6FN"}
@@ -0,0 +1,225 @@
1
+ import path from "node:path";
2
+ import { readdir, realpath, stat } from "node:fs/promises";
3
+ import { loadESLint } from "eslint";
4
+ import { z } from "zod";
5
+ // ESM importing the CJS plugin's ./instructions subpath via interop.
6
+ import { getRuleInstruction } from "eslint-plugin-llm-core/instructions";
7
+ // Default export = the plugin object; plugin.rules[name].defaultOptions is the
8
+ // public source of a rule's defaults, merged in so option-template placeholders
9
+ // resolve even when a project enables a rule without explicit options.
10
+ import plugin from "eslint-plugin-llm-core";
11
+ const LLM_CORE_PREFIX = "llm-core/";
12
+ // Default directory-lint cap (Assumption A1); validated/tuned by sub-task 3.7.
13
+ const DEFAULT_MAX_FILES = 200;
14
+ // Extensions ESLint may lint; used to estimate a directory's size for the cap.
15
+ const LINTABLE_EXTENSIONS = new Set([
16
+ ".ts",
17
+ ".tsx",
18
+ ".js",
19
+ ".jsx",
20
+ ".mjs",
21
+ ".cjs",
22
+ ]);
23
+ // Directories excluded from the file-count estimate (never linted in practice).
24
+ const SKIPPED_DIRECTORIES = new Set([
25
+ "node_modules",
26
+ ".git",
27
+ "dist",
28
+ "coverage",
29
+ ]);
30
+ const NO_CONFIG_MESSAGE = [
31
+ "No ESLint configuration was discovered for this path.",
32
+ "",
33
+ "lint_file lints using your project's own ESLint config; in v1 it does not",
34
+ "fall back to a built-in config. To use it, install and configure",
35
+ "eslint-plugin-llm-core in your project:",
36
+ "",
37
+ " npm install --save-dev eslint eslint-plugin-llm-core",
38
+ "",
39
+ "Then add it to your flat config (eslint.config.js), for example:",
40
+ "",
41
+ ' import llmCore from "eslint-plugin-llm-core";',
42
+ " export default [...llmCore.configs.recommended];",
43
+ ].join("\n");
44
+ function isNoConfigError(error) {
45
+ const maybeEslintError = error;
46
+ return (error instanceof Error &&
47
+ (maybeEslintError.messageTemplate === "config-file-missing" ||
48
+ /could not find config file/i.test(error.message)));
49
+ }
50
+ /** True when `absoluteTarget` is the project root or nested inside it. */
51
+ function isWithinRoot(absoluteTarget, root) {
52
+ const relative = path.relative(root, absoluteTarget);
53
+ return (relative === "" ||
54
+ (!relative.startsWith("..") && !path.isAbsolute(relative)));
55
+ }
56
+ /**
57
+ * Counts lintable source files under `dir`, short-circuiting once the count
58
+ * exceeds `limit`. A conservative estimate for the directory guard — it ignores
59
+ * ESLint's own ignore rules, so it may over-count, which only makes the guard
60
+ * safer.
61
+ */
62
+ async function countSourceFiles(dir, limit) {
63
+ let count = 0;
64
+ const pending = [dir];
65
+ while (pending.length > 0) {
66
+ const current = pending.pop();
67
+ const entries = await readdir(current, { withFileTypes: true });
68
+ for (const entry of entries) {
69
+ if (entry.isDirectory()) {
70
+ if (!SKIPPED_DIRECTORIES.has(entry.name)) {
71
+ pending.push(path.join(current, entry.name));
72
+ }
73
+ }
74
+ else if (entry.isFile() &&
75
+ LINTABLE_EXTENSIONS.has(path.extname(entry.name))) {
76
+ count += 1;
77
+ if (count > limit) {
78
+ return count;
79
+ }
80
+ }
81
+ }
82
+ }
83
+ return count;
84
+ }
85
+ const coreRules = plugin.rules;
86
+ const inputSchema = {
87
+ path: z
88
+ .string()
89
+ .describe("Path to a file or directory to lint, within the project root"),
90
+ };
91
+ async function createFlatESLint(cwd) {
92
+ // ESLint's exported types do not surface the flat-config overload; the cast
93
+ // matches the runtime shape (mirrors src/instructions/config-resolver.ts).
94
+ const loadFlatESLint = loadESLint;
95
+ const ESLint = await loadFlatESLint({ useFlatConfig: true });
96
+ return new ESLint({ cwd });
97
+ }
98
+ function isPlainObject(value) {
99
+ return typeof value === "object" && value !== null && !Array.isArray(value);
100
+ }
101
+ /** A rule's default options object from the public plugin export. */
102
+ function getRuleDefaultOptions(bareName) {
103
+ const first = coreRules[bareName]?.defaultOptions?.[0];
104
+ return isPlainObject(first) ? { ...first } : {};
105
+ }
106
+ /**
107
+ * Resolves the options a rule effectively fired with: the rule's defaults
108
+ * merged with whatever the project's config configured. Mirrors the core
109
+ * plugin's own option resolution so instructions interpolate identically.
110
+ */
111
+ function resolveRuleOptions(ruleId, configEntry) {
112
+ const bareName = ruleId.slice(LLM_CORE_PREFIX.length);
113
+ const defaults = getRuleDefaultOptions(bareName);
114
+ if (Array.isArray(configEntry)) {
115
+ const configured = configEntry[1];
116
+ return isPlainObject(configured)
117
+ ? { ...defaults, ...configured }
118
+ : { ...defaults };
119
+ }
120
+ return { ...defaults };
121
+ }
122
+ async function toViolations(results, eslint) {
123
+ const violations = [];
124
+ for (const result of results) {
125
+ const hasLlmCore = result.messages.some((m) => m.ruleId?.startsWith(LLM_CORE_PREFIX));
126
+ // Resolve the file's config once (only when it has llm-core diagnostics) so
127
+ // option-template placeholders interpolate against the configured options.
128
+ const fileRules = hasLlmCore
129
+ ? ((await eslint.calculateConfigForFile(result.filePath)).rules ?? {})
130
+ : {};
131
+ for (const message of result.messages) {
132
+ const ruleId = message.ruleId;
133
+ if (!ruleId || !ruleId.startsWith(LLM_CORE_PREFIX)) {
134
+ continue;
135
+ }
136
+ const options = resolveRuleOptions(ruleId, fileRules[ruleId]);
137
+ violations.push({
138
+ ruleId,
139
+ line: message.line,
140
+ column: message.column,
141
+ severity: message.severity,
142
+ message: message.message,
143
+ instruction: getRuleInstruction(ruleId, options),
144
+ });
145
+ }
146
+ }
147
+ return violations;
148
+ }
149
+ export function registerLintFile(server, options = {}) {
150
+ const projectRoot = path.resolve(options.projectRoot ?? process.cwd());
151
+ const maxFiles = options.maxFiles ?? DEFAULT_MAX_FILES;
152
+ server.registerTool("lint_file", {
153
+ title: "Lint a File for llm-core Violations",
154
+ description: "Lints a file (or directory) with the project's own ESLint config and " +
155
+ "returns each eslint-plugin-llm-core violation with its what/why/how-to-fix " +
156
+ "instruction attached. Call after editing a file to self-correct.",
157
+ inputSchema,
158
+ }, async ({ path: targetPath }) => {
159
+ const absoluteTarget = path.resolve(projectRoot, targetPath);
160
+ const realProjectRoot = await realpath(projectRoot).catch(() => projectRoot);
161
+ if (!isWithinRoot(absoluteTarget, projectRoot)) {
162
+ return {
163
+ isError: true,
164
+ content: [
165
+ {
166
+ type: "text",
167
+ text: `Refusing to lint "${targetPath}": it resolves outside the ` +
168
+ `project root (${projectRoot}). Pass a path within the project root.`,
169
+ },
170
+ ],
171
+ };
172
+ }
173
+ const targetStat = await stat(absoluteTarget).catch(() => null);
174
+ const realTarget = targetStat
175
+ ? await realpath(absoluteTarget).catch(() => absoluteTarget)
176
+ : absoluteTarget;
177
+ if (targetStat && !isWithinRoot(realTarget, realProjectRoot)) {
178
+ return {
179
+ isError: true,
180
+ content: [
181
+ {
182
+ type: "text",
183
+ text: `Refusing to lint "${targetPath}": it resolves outside the ` +
184
+ `project root (${projectRoot}). Pass a path within the project root.`,
185
+ },
186
+ ],
187
+ };
188
+ }
189
+ if (targetStat?.isDirectory()) {
190
+ const fileCount = await countSourceFiles(realTarget, maxFiles);
191
+ if (fileCount > maxFiles) {
192
+ return {
193
+ content: [
194
+ {
195
+ type: "text",
196
+ text: `"${targetPath}" contains more than ${maxFiles} lintable files. ` +
197
+ `Narrow the path to a specific file or subdirectory, or raise the ` +
198
+ `configured maxFiles cap.`,
199
+ },
200
+ ],
201
+ };
202
+ }
203
+ }
204
+ const eslint = await createFlatESLint(projectRoot);
205
+ let results;
206
+ try {
207
+ results = await eslint.lintFiles([absoluteTarget]);
208
+ }
209
+ catch (error) {
210
+ if (isNoConfigError(error)) {
211
+ return {
212
+ content: [{ type: "text", text: NO_CONFIG_MESSAGE }],
213
+ };
214
+ }
215
+ throw error;
216
+ }
217
+ const violations = await toViolations(results, eslint);
218
+ return {
219
+ content: [
220
+ { type: "text", text: JSON.stringify(violations, null, 2) },
221
+ ],
222
+ };
223
+ });
224
+ }
225
+ //# sourceMappingURL=lint-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lint-file.js","sourceRoot":"","sources":["../../src/tools/lint-file.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "eslint-plugin-llm-core-mcp",
3
+ "version": "0.25.2",
4
+ "description": "MCP server for just-in-time linting guidance from eslint-plugin-llm-core",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/pertrai1/eslint-plugin-llm-core",
9
+ "directory": "packages/mcp-server"
10
+ },
11
+ "type": "module",
12
+ "main": "dist/index.js",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js"
17
+ }
18
+ },
19
+ "bin": {
20
+ "llm-core-mcp": "dist/server.js"
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "engines": {
26
+ "node": ">=20"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public",
30
+ "provenance": true,
31
+ "registry": "https://registry.npmjs.org/"
32
+ },
33
+ "scripts": {
34
+ "embed": "tsx scripts/embed-rule-docs.ts",
35
+ "prebuild": "npm run embed",
36
+ "build": "tsc -p tsconfig.build.json",
37
+ "prepare": "npm run embed",
38
+ "prepublishOnly": "npm run build",
39
+ "test": "vitest run"
40
+ },
41
+ "dependencies": {
42
+ "@modelcontextprotocol/sdk": "^1.29.0",
43
+ "@typescript-eslint/parser": "^8.0.0",
44
+ "eslint": "^10.1.0",
45
+ "eslint-plugin-llm-core": "0.25.2",
46
+ "zod": "^3.23.0"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "^25.5.0",
50
+ "tsx": "^4.21.0",
51
+ "typescript": "^6.0.2",
52
+ "vitest": "^4.1.2"
53
+ }
54
+ }