Thanks to visit codestin.com
Credit goes to github.com

Skip to content

ayushtom/rot-scan

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

rot-scan

Language-agnostic code-rot scanner powered by tree-sitter. No AI, no cloud, no language-specific toolchain on your machine. JS, TS, TSX, Python.

Install

Run without installing:

npx -y rot-scan

Or install globally:

npm install -g rot-scan
rot-scan

Node 20+. No auth, no postinstall step — the 4 tree-sitter wasm grammars ship inside the package.

Use with coding agents

rot-scan ships an MCP server — works in Claude Code, Cursor, and Windsurf natively:

{
  "mcpServers": {
    "rot-scan": {
      "command": "npx",
      "args": ["-y", "rot-scan", "mcp"]
    }
  }
}

Drop this into .claude/settings.json / .cursor/mcp.json / ~/.codeium/windsurf/mcp_config.json. See docs/agents/ for per-agent details.

Your agent can then ask for a health score (rot_scan_summary), list findings (rot_scan_run), or fetch rule docs (rot-scan://rules/{name}). Every finding carries an explicit suggestedAction (remove-export, delete-file, remove-dependency, etc.) so agents can drive cleanups directly.

Unlike Knip's MCP (JS/TS only), rot-scan's MCP handles JS + TS + TSX + Python in one server with zero config.

Usage

# Score the current directory
rot-scan

# Score specific paths
rot-scan src/ packages/

# CI gate — fail if health score drops below 80
rot-scan --min-score 80

# JSON output
rot-scan --format json

# Scope to a language subset
rot-scan --lang ts,tsx src/

# Disable a rule for this run
rot-scan --rule no-unused-locals=off

Every run renders a health score banner:

72 / 100  Good
██████████████░░░░░░
47 findings across 183 files in 2.1s

src/services/auth.ts
  unused-export:23:14  Exported "authKeys" is used inside this file but
                       imported by nothing — remove the `export` keyword.
...

Grades: Healthy 90+, Good 75-89, Warning 50-74, Poor 25-49, Critical <25.

Exit codes: 0 pass, 1 findings or score below --min-score, 2 internal error (unparseable files).

Flags

rot-scan [paths...] [options]

Positionals:
  paths                       One or more directories or files. Default: cwd.

Rules & detection:
  --rule NAME=SEVERITY        Per-rule override, repeatable. SEVERITY is error|warn|off.
  --detect <list>             [legacy] Comma-separated rule kinds: exports,files,locals,unreachable.
  --no-string-refs            Disable the dynamic-dispatch heuristic.
  --no-path-refs              Disable the cross-language path-literal heuristic.

Scope:
  --lang <list>               Restrict to languages: js,ts,tsx,py (or full names).
  --ignore <glob>             Extra ignore pattern, repeatable.
  --exclude-tests             Skip common test-file conventions.
  --entry-points <files>      Comma-separated entry points in addition to auto-detect.

Score & CI:
  --min-score <n>             Fail if health score < n (0-100).
  --no-score                  Hide the score banner.

Output:
  --format <fmt>              text | json (default text).

Config:
  --config <path>             Path to .rot-scanrc.json.
  --no-ignore-comments        Disable `// rot-scan:ignore` comment support.

Config

Drop .rot-scanrc.json at the repo root:

{
  "presets": ["vite", "nestjs"],
  "entryPoints": ["src/index.ts", "scripts/cli.ts"],
  "ignore": ["src/generated/**"],
  "ignoreSymbols": ["publicAPI"],
  "dynamicPatterns": ["**/routes/**"],
  "rules": {
    "no-unused-exports": "error",
    "no-unused-locals": "off"
  },
  "heuristics": { "stringRefs": true, "pathRefs": true }
}

Rules (see docs/rules/ for each):

Rule Category Weight What it flags
no-unused-exports correctness ×1 Exported symbols with no importers
no-unused-files correctness ×2 Orphan files (no importers, not entry points)
no-unused-locals suspicious ×0.5 File-local declarations never referenced
no-unreachable-code correctness ×2 Statements after return/throw/break/continue
no-unused-deps correctness ×1 package.json entries never imported

Presets expand into entryPoints + dynamicPatterns for common frameworks: nextjs, vite, remix, nestjs, storybook, vitest, drizzle, tsup, sveltekit, astro, hono, solid, nuxt.

Inline ignores:

// rot-scan:ignore
export function registeredByDecorator() {}

// rot-scan:ignore-file   (put at top of file to skip it entirely)

Limitations

rot-scan reads your source code statically — it never runs anything. That's fast and safe, but a few patterns are genuinely invisible to a tool that only reads. Here's what to watch for and how to work around each one.

Calling functions by string name

const handlers = { handleClick, handleSubmit };
handlers[eventName]();   // which one gets called? depends on runtime

rot-scan can't follow handlers[eventName]() to a specific function, so handleClick and handleSubmit look unused. As a safety net, the tool scans string literals in your code — so if the string "handleClick" appears anywhere, the function stays marked as used. But if the name is assembled at runtime ("handle" + event), it's invisible.

What to do: list the file in dynamicPatterns (stops the tool from flagging its exports), or list specific names in ignoreSymbols.

Code stripped by your bundler

if (process.env.NODE_ENV !== "production") {
  debugOnlyThing();   // bundlers delete this in prod
}

rot-scan reads your source, not your built bundle. Code inside dev-only branches looks alive to the tool even though your production build has it removed. The symmetric case: something only called inside a dev branch looks "used," but really isn't in prod.

What to do: if you want a "what's dead in production" report, point rot-scan at your build output (e.g. dist/) instead of src/.

Framework files loaded by filename

Some frameworks load files by convention, not by import:

  • Rails autoloads UsersController from users_controller.rb
  • Next.js makes every pages/foo.tsx a route
  • NestJS collects controllers/services by decorator at app startup

rot-scan doesn't see these files as imported, so they look orphaned.

What to do: add a preset — nextjs, nestjs, remix, vite, rails, storybook, vitest, drizzle, tsup. Each one teaches rot-scan about that framework's conventions. For custom conventions, extend entryPoints + dynamicPatterns yourself.

Scripts invoked across languages

spawn("python", ["scripts/train.py"]);

rot-scan analyzes each language's imports separately. It won't know that a JS file invokes a Python script. As a fallback, it looks at string literals — if the exact path "scripts/train.py" appears, the Python file stays marked as used. But runtime-assembled paths ("scripts/" + name + ".py") are invisible.

What to do: add the target script to entryPoints.

Shadowed variables in nested scopes

If you reuse a variable name in nested scopes (an outer x and an inner x), rot-scan's scope tracker counts inner uses as also referring to the outer x. The inner use "leaks" into the outer scope's count. Usually harmless; in pathological cases it may mask an unused outer variable.

What to do: if you hit it, file a fixture and we'll extend the scope walker to handle shadowing correctly.

Default exports

export default function greet() {}

The no-unused-exports rule skips export default entirely. Matching a default export to its importer reliably requires tracking each file's default binding through renames (import foo from "./x" is really importing default), which needs type-level info that tree-sitter queries don't provide.

What to do: nothing — these just won't be flagged. If default-only dead code is a concern, convert the exports to named form.

Side-effect-only imports

import "./init";   // runs the file for its side effects, no binding

rot-scan currently only tracks imports that have a named or namespace binding. A bare side-effect import doesn't mark the target as imported, so it may get flagged as an orphan file.

What to do: add the file to entryPoints.

About

Language-agnostic dead code analyzer powered by tree-sitter

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors