npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

eslint-plugin-ai-rules

v0.2.1

Published

ESLint plugin com regras úteis para projetos com ESLint - AI Rules

Readme

eslint-plugin-ai-rules

npm version license: MIT node >=18 eslint ^9

Flat-config only ESLint plugin that bundles useful rules for AI-generated code. Ships a recommended preset with all rules enabled as error — zero config for consumers.

ESLint 9+ only. Uses flat config (eslint.config.{ts,js}) and eslint/config defineConfig. No legacy .eslintrc support.

Features

  • AI Rules rules — catches typical AI drift: wrong filename casing, unsorted/multiline imports
  • Bundled preset — aiRules.configs.recommended enables custom + import-x, @stylistic, typescript-eslint rules out of the box
  • Autofix — sort-imports and no-multiline-imports are fixable: code (eslint --fix)
  • Dual build — dist/index.js (ESM) + dist/index.cjs (CJS) via tsup, single-file bundle, type: module

Requirements

  • Node >=18 (package.json engines)
  • ESLint ^9.0.0 (peer) — flat config only
  • TypeScript >=5.0.0 (peer)

@eslint/js, eslint-plugin-import-x, @stylistic/eslint-plugin, typescript-eslint, globals are already dependencies — you don't need to install them.

Installation

# npm
npm i -D eslint-plugin-ai-rules eslint typescript

# yarn
yarn add -D eslint-plugin-ai-rules eslint typescript

# pnpm
pnpm add -D eslint-plugin-ai-rules eslint typescript

# bun
bun add -d eslint-plugin-ai-rules eslint typescript

Quick Start

eslint.config.ts (recommended) — you define files

recommended is file-agnostic (only plugins + settings + rules) and does not configure a parser. Pair it with typescript-eslint (or @typescript-eslint/parser) and define files:

import aiRules from 'eslint-plugin-ai-rules'
import tseslint from 'typescript-eslint'

export default [
  ...tseslint.configs.recommended,
  { files: ['**/*.{ts,tsx}'], ...aiRules.configs.recommended[0] },
]

Alternative with defineConfig (ESLint >= 9.22):

import { defineConfig } from 'eslint/config'
import aiRules from 'eslint-plugin-ai-rules'
import tseslint from 'typescript-eslint'

export default defineConfig([
  ...tseslint.configs.recommended,
  { files: ['**/*.{ts,tsx}'], extends: aiRules.configs.recommended },
])

eslint.config.js / eslint.config.mjs

import aiRules from 'eslint-plugin-ai-rules'
import tseslint from 'typescript-eslint'

export default [
  ...tseslint.configs.recommended,
  { files: ['**/*.{js,ts,tsx}'], ...aiRules.configs.recommended[0] },
]

With overrides

import aiRules from 'eslint-plugin-ai-rules'
import { defineConfig } from 'eslint/config'

export default defineConfig([
  { files: ['**/*.{ts,tsx}'], ...aiRules.configs.recommended[0] },
  {
    // turn off filename check for tests or generated code
    files: ['**/*.test.{ts,tsx}', '**/*.spec.{ts,tsx}', 'scripts/**/*'],
    rules: {
      'ai-rules/filename-pascal-case': 'off',
    },
  },
  {
    // or disable a bundled rule
    rules: {
      '@stylistic/semi': 'off',
    },
  },
])

Run:

npx eslint .
npx eslint --fix .   # autofix for sort-imports / no-multiline-imports

Configuration

recommended preset

src/configs/recommended.ts exports defineConfig([...]) with only plugins + settings + rules (file-agnostic) — you define files/ignores/languageOptions:

  • plugins: { '@stylistic': stylistic, 'import-x': importX, 'ai-rules': plugin }
  • settings: { 'import-x/resolver-next': ... } with extensions: ['.ts','.tsx','.d.ts','.js','.jsx']
  • Rules (all error unless noted):

| Rule | Default | Notes | |------|---------|-------| | ai-rules/filename-pascal-case | error | File-agnostic: checks every file ESLint lints. See below | | ai-rules/sort-imports | error | Groups + length desc, collapses multiline | | ai-rules/no-multiline-imports | error | Single-line imports | | import-x/no-duplicates | error | | | import-x/no-default-export | off | | | max-len | off | | | @stylistic/semi | ['error','never'] | No semicolons | | @typescript-eslint/consistent-type-imports | ['error',{prefer:'type-imports'}] | | | @typescript-eslint/no-unused-vars | ['error',{argsIgnorePattern:'^_',varsIgnorePattern:'^_'}] | | | @typescript-eslint/naming-convention | ['error', {interface:'PascalCase' prefix I}, {typeAlias:'PascalCase' prefix T}] | Disable per-project for **/*.d.ts via your own override if needed |

recommended has no files/ignores — add them in your eslint.config.* (see Quick Start). This repo's eslint.config.mjs adds globalIgnores, files: ['**/*.{ts,tsx}'] with extends: [js.configs.recommended, tseslint.configs.recommended] + globals.browser locally.

Custom setup (without recommended)

import { plugin } from 'eslint-plugin-ai-rules'
import { defineConfig } from 'eslint/config'

export default defineConfig([
  {
    files: ['**/*.{ts,tsx}'],
    plugins: { 'ai-rules': plugin },
    rules: {
      'ai-rules/sort-imports': 'error',
      'ai-rules/no-multiline-imports': 'warn',
      'ai-rules/filename-pascal-case': 'off',
    },
  },
])

You can also import rules individually:

import { filenamePascalCase, sortImports, noMultilineImports } from 'eslint-plugin-ai-rules'

Rules

Brief overview. See source in src/rules/ for full logic.

ai-rules/filename-pascal-case

Enforces PascalCase for file names. The rule is file-agnostic: it validates every file ESLint decides to lint, following the flat-config files/ignores defined in the consumer's config (same pattern as other filename rules).

  • Type: suggestion, no autofix, messageId: invalid
  • Ignores: index.{ts,tsx,js,jsx}, *.d.ts, *.css, dotfiles
  • Checks: path.basename from getPhysicalFilename() / context.filename, ^[A-Z][a-zA-Z0-9]*$, kebab/snake -> PascalCase conversion
  • Options:
    • ignore — array of regex strings matched against the basename and the path relative to cwd (same idea as unicorn/filename-case). Use it for tool-mandated names like *.config.*, tests/specs, generated or vendored code.
    • ignoreMiddleExtensions — boolean, default true. When true, only the base name before the first dot is validated, so ValidationRanges.test.ts is valid and my-component.test.ts is renamed to MyComponent.test.ts. When false, every dot-separated segment must be PascalCase, so ValidationRanges.test.ts is renamed to ValidationRanges.Test.ts (same idea as unicorn/filename-case).
// my-component.ts         ❌  Rename to "MyComponent.ts"
// ValidationRanges.test.ts  ✅ (ignoreMiddleExtensions: true, default)
// ValidationRanges.test.ts  ❌  Rename to "ValidationRanges.Test.ts" (ignoreMiddleExtensions: false)
// MyComponent.ts          ✅
'ai-rules/filename-pascal-case': [
  'error',
  {
    ignore: ['\\.config\\.', '^generated/'],
    ignoreMiddleExtensions: false,
  },
]

To scope or disable it, use your own config (as this repo does in eslint.config.mjs):

import { defineConfig } from 'eslint/config'
import { recommended } from './src/configs/recommended'

export default defineConfig([
  ...recommended,
  { files: ['src/**/*'], rules: { 'ai-rules/filename-pascal-case': 'off' } },
])

ai-rules/no-multiline-imports

Disallows multiline ImportDeclaration.

  • Type: layout, fixable: code, messageId: multilineImport
  • Fix: collapses {, }, , and whitespace to single line: import { b, a } from 'lodash'
// ❌
import {
  b,
  a
} from 'lodash'

// ✅ after --fix
import { b, a } from 'lodash'

Note: ai-rules/sort-imports also collapses multiline; both fixers may run, but sort-imports handles the full Program sort.

ai-rules/sort-imports

Sorts imports by group and line-length descending, collapsing multiline imports.

  • Type: layout, fixable: code (Program fixer), messageId: sortRequired
  • Groups (in order, each separated by one blank line):
    1. Directives ('use client', 'use server', 'use strict')
    2. External (react first, then other externals sorted by line.length desc → localeCompare)
    3. Internal (., @/ sorted same)
    4. Re-exports (export { ... } from, export * from sorted same)
  • Rest: code after imports, joined with \n\n\n (two blank lines between imports and code)
  • Directives: preserved at top while canAcceptDirectives is true
// ❌ before --fix
import { local } from './local'
import { a } from 'lodash'
import React from 'react'

// ✅ after --fix
import React from 'react'
import { a } from 'lodash'

import { local } from './local'

Supports side-effect import 'polyfill', import type, export * from, export { ... } from.

TypeScript

Plugin itself uses module: ESNext + moduleResolution: bundler (tsconfig.json) and no .js extensions in source imports (e.g., from './plugin'). Output is bundled to single dist/index.js via tsup, so consumers don't need to handle internal paths. For your project, any TS config works; the preset sets languageOptions.globals: globals.browser and import-x/resolver-next.

Troubleshooting

Missing dist before lint (repo uses eslint.config.mjs → dist)

Error: Cannot find module './dist/index.js'

Fix: npm run build first. This repo's eslint.config.mjs imports from dist (no jiti), so prelint runs build automatically (package.json prelint).

File ignored because outside of base path You passed an absolute path outside the config's basePath. Run npx eslint . from project root or use npx eslint --no-config-lookup -c ./eslint.config.js ./src/file.ts with correct cwd.

Flat config only This plugin exports flat config arrays. It does not support legacy .eslintrc. Use eslint.config.{js,ts}.

Filename rule too strict for tests/configs The rule checks every linted file. Test/spec files with a PascalCase base (e.g. ValidationRanges.test.ts) pass by default via ignoreMiddleExtensions: true. For tool-mandated names like *.config.*, generated or vendored code, pass the ignore option ({ ignore: ['\\.config\\.', '^generated/'] }) or disable ai-rules/filename-pascal-case for those files via an override (see Quick Start).

Development

git clone <repo>
cd eslint-ai-guard
npm install

npm run typecheck  # tsc --noEmit
npm test           # node:test + dist build
npm run lint       # eslint .
npm run lint -- --fix
npm run build      # tsup -> dist/index.js + dist/index.cjs + dist/index.d.ts
npm run dev        # tsup --watch
npm run verify     # typecheck + test + lint + pack
npm pack --dry-run # verify files: dist only

Project structure

eslint-ai-guard/  # package: eslint-plugin-ai-rules
├── src/
│   ├── index.ts              # re-exports plugin + recommended
│   ├── plugin.ts             # plugin.meta + rules
│   ├── configs/
│   │   ├── recommended.ts    # flat-config preset (bundled rules)
│   │   └── index.ts
│   └── rules/
│       ├── FilenamePascalCase.ts
│       ├── NoMultilineImports.ts
│       ├── SortImports.ts    # also exports getSortedContent
│       └── index.ts
├── test/                     # node:test suites (run against dist)
├── eslint/                   # deprecated original implementations
├── eslint.config.mjs         # dogfooding: ...aiRules.configs.recommended from dist
├── tsconfig.json             # ESNext/bundler, no .js extensions
├── tsup.config.ts            # entry src/index.ts, format cjs+esm, dts, splitting:false
├── package.json              # type module, exports ., files [dist]
└── dist/                     # build output (gitignored)

Adding a new rule

  1. Create src/rules/MyRule.ts (Rule.RuleModule, meta.messages, fixable if needed)
  2. Export it from src/rules/index.ts
  3. Register in src/plugin.ts (rules: { 'my-rule': myRule })
  4. Enable in src/configs/recommended.ts ('ai-rules/my-rule': 'error')
  5. Add a RuleTester case in test/ (add a fixture if needed)
  6. npm run verify

Publishing

npm version patch|minor|major
npm publish --access public  # prepublishOnly runs verify (typecheck+test+lint+pack)
# or manually: npm run verify && npm publish --access public

Legacy

eslint/ keeps the three original rule files before packaging (deprecated). Source of truth is now src/rules/.

License

MIT