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

tscanner

v0.1.3

Published

Code quality scanner for the AI-generated code era

Downloads

2,343

Readme

🎺 Overview

Define what "good code" means for your project. Scan your codebase from the terminal. Run before commits, in CI pipelines, or as part of your build. 38 ready-to-use rules + custom rules via regex, scripts, or AI.

⭐ Features

  • Your Rules, Enforced - 38 built-in checks + define your own with regex, scripts, or AI
  • Community Rules - Install pre-built rules from registry or share your own with the world
  • Multiple Scan Modes - Whole codebase, branch changes, uncommitted changes, or staged changes
  • Sub-second Scans - Rust engine processes hundreds of files in <1s, with smart caching
  • Not a Blocker - Issues are warnings by default; set as errors to fail CI/lint-staged

❓ Motivation

AI generates code fast, but it doesn't know your project's conventions, preferred patterns, or forbidden shortcuts. You end up reviewing the same issues over and over.

TScanner lets you define those rules once. Every AI-generated file, every PR, every save: automatically checked against your standards.

🔀 Workflow

Vision: Go fast with AI and know exactly what to fix before shipping. Detect bad patterns while reviewing code? Ask AI to create regex, script, or AI rules to catch it forever. Use the VSCode extension's "Copy Issues" button to get a ready-to-paste prompt and let your favorite AI tool fix everything. Before merging, see all issues at a glance in a PR comment from your CI/CD: nothing blocks by default, you decide what matters.

  • Code Editor: See issues in real-time while coding. Add to lint-staged to prevent committing errors.
  • Before PR: Check all issues in your branch compared to origin/main and fix them before opening a PR.
  • CI/CD: Every push to a PR is checked automatically. Get a single comment with clickable links to the exact lines.
  • Go fast with confidence: Know exactly what issues to fix before committing or merging.
  • Zero rejected PRs: Over time, eliminate PR rejections due to styling or poor code quality patterns.
  • AI-powered quality: Use AI rules to detect patterns that traditional linters miss, and let AI help fix AI-generated code.
  • Your job: Observe code patterns to enforce/avoid and add TScanner rules for that.

We use TScanner to maintain this very codebase. Here's our setup:

Built-in rules (34 enabled): Standard code quality checks like no-explicit-any, prefer-const, no-console, etc.

Regex rules (3):

  • no-rust-deprecated: Block #[allow(deprecated)] in Rust code
  • no-rust-dead-code: Block #[allow(dead_code)] - remove unused code instead
  • no-process-env: Prevent direct process.env access

Script rules (8):

AI rules (2):

TIP: Check the .tscanner/ folder to see the full config and script implementations.

I basically observe code patterns to enforce/avoid and add custom rules, here are my current rules:

regex rules:

"regex": {
  "no-nestjs-logger": {
    "pattern": "import\\s*\\{[^}]*Logger[^}]*\\}\\s*from\\s*['\"]@nestjs/common['\"]",
    "message": "Do not use NestJS Logger. Import from custom logger instead"
  },
  "no-typeorm-for-feature": {
    "pattern": "TypeOrmModule\\.forFeature\\(",
    "message": "Use api/src/way-type-orm.module.ts instead"
  },
  "avoid-typeorm-raw-queries": {
    "pattern": "await this\\.([^.]+)\\.manager\\.query\\(",
    "message": "Avoid using RawQueryBuilder. Use the repository instead",
    "severity": "error"
  },
  "no-static-zod-schema": {
    "pattern": "static\\s+zodSchema\\s*=",
    "message": "Remove 'static zodSchema' from class. The schema is already passed to createZodDto() and this property is redundant"
  }
}

script rules:

"script": {
  "entity-registered-in-typeorm-module": {
    "command": "npx tsx script-rules/entity-registered-in-typeorm-module.ts",
    "message": "Entity must be registered in way-type-orm.module.ts",
    "severity": "error",
    "include": ["api/src/**/*.entity.ts", "api/src/way-type-orm.module.ts"]
  },
  "entity-registered-in-setup-nest": {
    "command": "npx tsx script-rules/entity-registered-in-setup-nest.ts",
    "message": "Entity must be registered in setup-nest.ts for tests",
    "severity": "error",
    "include": ["api/src/**/*.entity.ts", "api/test/helpers/setup-nest.ts"]
  },
  "no-long-files": {
    "command": "npx tsx script-rules/no-long-files.ts",
    "message": "File exceeds 600 lines limit",
    "include": ["**/*.ts"]
  }
}

ai rules:

soon!

Note: my rules at work are not commited to the codebase, so I basically installed tscanner globally and move the .tscanner folder into the .gitignore file

🚀 Quick Start

  1. Install locally
npm install -D tscanner
  1. Initialize configuration
npx tscanner init

TIP: Use npx tscanner init --full for a complete config with example regex, script, and AI rules.

After that you can already use the CLI:

  1. Check via terminal
# Scan workspace
npx tscanner check

# Scan uncommitted changes (staged + unstaged)
npx tscanner check --uncommitted

# Scan only changed files vs branch
npx tscanner check --branch origin/main
  1. Integrate with lint-staged (optional)
// .lintstagedrc.json
{
  "*": ["npx tscanner check --staged"]
}

📖 Usage

⚙️ Configuration

To scan your code, you need to set up the rules in the TScanner config folder.

{
  "$schema": "https://unpkg.com/tscanner@latest/schema.json",
  "rules": {
    "builtin": {
      "consistent-return": {},
      "max-function-length": {},
      "max-params": {},
      "no-absolute-imports": {},
      "no-alias-imports": {},
      "no-async-without-await": {},
      "no-console": {},
      "no-constant-condition": {},
      "no-default-export": {},
      "no-duplicate-imports": {},
      "no-dynamic-import": {},
      "no-else-return": {},
      "no-empty-class": {},
      "no-empty-function": {},
      "no-empty-interface": {},
      "no-explicit-any": {},
      "no-floating-promises": {},
      "no-forwarded-exports": {},
      "no-implicit-any": {},
      "no-inferrable-types": {},
      "no-nested-require": {},
      "no-nested-ternary": {},
      "no-non-null-assertion": {},
      "no-relative-imports": {},
      "no-return-await": {},
      "no-shadow": {},
      "no-single-or-array-union": {},
      "no-todo-comments": {},
      "no-unnecessary-type-assertion": {},
      "no-unreachable-code": {},
      "no-unused-vars": {},
      "no-useless-catch": {},
      "no-var": {},
      "prefer-const": {},
      "prefer-interface-over-type": {},
      "prefer-nullish-coalescing": {},
      "prefer-optional-chain": {},
      "prefer-type-over-interface": {}
    },
    "regex": {
      "example-no-console-log": {
        "pattern": "console\\.log",
        "message": "Remove console.log before committing"
      }
    },
    "script": {
      "example-no-long-files": {
        "command": "npx tsx script-rules/example-no-long-files.ts",
        "message": "File exceeds 300 lines limit",
        "include": ["packages/**/*.ts", "packages/**/*.rs"]
      }
    }
  },
  "aiRules": {
    "example-find-enum-candidates": {
      "prompt": "example-find-enum-candidates.md",
      "mode": "agentic",
      "message": "Type union could be replaced with an enum for better type safety",
      "severity": "warning",
      "include": ["**/*.ts"]
    }
  },
  "ai": {
    "provider": "claude"
  },
  "files": {
    "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "**/*.mjs", "**/*.cjs"],
    "exclude": ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**"]
  },
  "codeEditor": {
    "highlightErrors": true,
    "highlightWarnings": true,
    "highlightInfos": true,
    "highlightHints": true,
    "autoScanInterval": 0,
    "autoAiScanInterval": 0,
    "startupScan": "cached",
    "startupAiScan": "off"
  }
}
{
  "$schema": "https://unpkg.com/tscanner@latest/schema.json",
  "rules": {
    "builtin": {
      "no-explicit-any": {}
    },
    "regex": {},
    "script": {}
  },
  "aiRules": {},
  "files": {
    "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "**/*.mjs", "**/*.cjs"],
    "exclude": ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**"]
  }
}

Per-rule file patterns: Each rule can have its own include/exclude patterns:

{
  "rules": {
    "builtin": {
      "no-console": { "exclude": ["src/logger.ts"] },
      "max-function-length": { "include": ["src/core/**/*.ts"] }
    }
  }
}

Inline disables:

// tscanner-ignore-next-line no-explicit-any
const data: any = fetchData();

// tscanner-ignore
// Entire file is skipped

📋 Rules

Rules are the core of TScanner. They define what to check, where to check, and how to report issues. Mix built-in rules with custom ones to enforce your team's standards.

Type Safety (6)

Code Quality (13)

Bug Prevention (4)

Variables (3)

Imports (8)

Style (4)

Define patterns to match in your code using regular expressions:

Config (.tscanner/config.jsonc):

{
  "rules": {
    "regex": {
      "no-rust-deprecated": {
        "pattern": "allow\\(deprecated\\)",
        "message": "No deprecated methods",
        "include": ["packages/rust-core/**/*.rs"]
      },
      "no-process-env": {
        "pattern": "process\\.env",
        "message": "No process env"
      },
      "no-debug-logs": {
        "pattern": "console\\.(log|debug|info)",
        "message": "Remove debug statements",
        "exclude": ["**/*.test.ts"]
      }
    }
  }
}

Run custom scripts in any language (TypeScript, Python, Rust, Go, etc.) that reads JSON from stdin and outputs JSON to stdout.

Input contract (received via stdin):

{
  "files": [
    {
      "path": "src/utils.ts",
      "content": "export function add(a: number, b: number)...",
      "lines": ["export function add(a: number, b: number)", "..."]
    }
  ],
  "options": { "maxLines": 300 },
  "workspaceRoot": "/path/to/project"
}

Output contract (expected via stdout):

{
  "issues": [
    { "file": "src/utils.ts", "line": 10, "message": "Issue description" }
  ]
}

Config (.tscanner/config.jsonc):

{
  "rules": {
    "script": {
      "no-long-files": {
        "command": "npx tsx script-rules/no-long-files.ts",
        "message": "File exceeds 300 lines limit",
        "include": ["**/*.ts", "**/*.rs", "**/*.py", "**/*.go"]
      }
    }
  }
}
#!/usr/bin/env npx tsx
import { stdin } from 'node:process';

async function main() {
  let data = '';
  for await (const chunk of stdin) data += chunk;

  const input = JSON.parse(data);
  const issues = [];

  for (const file of input.files) {
    if (file.lines.length > 300) {
      issues.push({ file: file.path, line: 301, message: `File exceeds 300 lines` });
    }
  }

  console.log(JSON.stringify({ issues }));
}
main().catch((err) => {
  console.error(err);
  process.exit(1);
});
#!/usr/bin/env python3
import json, sys

def main():
    input_data = json.loads(sys.stdin.read())
    issues = []

    for file in input_data["files"]:
        if len(file["lines"]) > 300:
            issues.append({"file": file["path"], "line": 301, "message": "File exceeds 300 lines"})

    print(json.dumps({"issues": issues}))

if __name__ == "__main__":
    main()
#!/usr/bin/env rust-script
use std::io::{self, Read};
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct ScriptFile { path: String, lines: Vec<String> }

#[derive(Deserialize)]
struct ScriptInput { files: Vec<ScriptFile> }

#[derive(Serialize)]
struct ScriptIssue { file: String, line: usize, message: String }

#[derive(Serialize)]
struct ScriptOutput { issues: Vec<ScriptIssue> }

fn main() -> io::Result<()> {
    let mut data = String::new();
    io::stdin().read_to_string(&mut data)?;
    let input: ScriptInput = serde_json::from_str(&data).unwrap();
    let mut issues = Vec::new();

    for file in input.files {
        if file.lines.len() > 300 {
            issues.push(ScriptIssue { file: file.path, line: 301, message: "File exceeds 300 lines".into() });
        }
    }

    println!("{}", serde_json::to_string(&ScriptOutput { issues }).unwrap());
    Ok(())
}

💡 See real examples in the .tscanner/script-rules/ and registry/script-rules/ folders.

Use AI prompts (markdown files) to perform semantic code analysis. Works with any AI provider (Claude, OpenAI, Ollama, etc.).

Modes - How files are passed to the AI: | Mode | Description | Best for | |------|-------------|----------| | paths | Only file paths (AI reads files via tools) | Large codebases, many files | | content | Full file content in prompt | Small files, quick analysis | | agentic | Paths + AI can explore freely | Cross-file analysis, complex patterns |

Placeholders - Use in your prompt markdown: | Placeholder | Replaced with | |-------------|---------------| | {{FILES}} | List of files to analyze (required) | | {{OPTIONS}} | Custom options from config (optional) |

Output contract - AI must return JSON:

{
  "issues": [
    { "file": "src/utils.ts", "line": 10, "column": 1, "message": "Description" }
  ]
}

Config (.tscanner/config.jsonc):

{
  "aiRules": {
    "find-enum-candidates": {
      "prompt": "find-enum-candidates.md",
      "mode": "agentic",
      "message": "Type union could be replaced with an enum",
      "severity": "warning",
      "include": ["**/*.ts", "**/*.tsx", "**/*.rs"]
    },
    "no-dead-code": {
      "prompt": "no-dead-code.md",
      "mode": "content",
      "message": "Dead code detected",
      "severity": "error",
      "include": ["**/*.rs"],
      "options": { "allowTestFiles": true }
    }
  },
  "ai": {
    "provider": "claude"
  }
}
# Enum Candidates Detector

Find type unions that could be replaced with enums.

## What to look for

1. String literal unions: \`type Status = 'pending' | 'active'\`
2. Repeated string literals across files
3. Type unions used as discriminators

## Exploration hints

- Check how the type is used across files
- Look for related constants

---

## Files

{{FILES}}
# Dead Code Detector

Detect dead code patterns.

## Rules

1. No \`#[allow(dead_code)]\` attributes
2. No unreachable code after return/break

## Options

{{OPTIONS}}

## Files

{{FILES}}

💡 See real examples in the .tscanner/ai-rules/ and registry/ai-rules/ folders.

📦 Registry

The registry is a collection of community rules ready to install with a single command.

npx tscanner registry                     # List all available rules (and you chose the ones you want to install)
npx tscanner registry no-long-files       # Install a specific rule
npx tscanner registry --kind script       # Filter by type (ai, script, regex)
npx tscanner registry --category security # Filter by category
npx tscanner registry --latest            # Use rules from main branch instead of current version

Available rules (5)

Want to share your rule? Open a PR adding your rule to the registry/ folder. Once merged, everyone can install it with npx tscanner registry your-rule-name.

💡 Inspirations

  • Biome - High-performance Rust-based linter and formatter for web projects
  • ESLint - Find and fix problems in your JavaScript code
  • Vitest - Next generation testing framework powered by Vite
  • VSCode Bookmarks - Bookmarks Extension for Visual Studio Code