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

prune-os

v0.8.0

Published

Code simplification tool - same functionality, less complexity

Downloads

1,056

Readme

PruneOS

Same code, less of it.

PruneOS is a fast, zero-config code simplifier for TypeScript and JavaScript. It removes verbose patterns that AI-generated code (and humans) tend to write, without changing behaviour: redundant === true, x !== null && x !== undefined, nested if/else chains that should be guard clauses, unused imports, duplicate same-source imports, and more.

It has two modes:

  • pruneos <files>: per-file simplifier. Removes line-level verbosity. Fast.
  • pruneos audit: repo-wide deletion-first audit. Finds unused exports, single-use abstractions, overengineering patterns (*Manager, *Factory, empty interfaces, one-method classes), AI slop (// TODO, mock data, empty rethrows), and over-fragmented component directories.

It also ships a Claude Code skill so AI agents working inside your project run both modes automatically. See For AI agents below.

npm version license

Example

A real function before and after pruneos -a --fix, 49 lines down to 42, six distinct transformations across the diff. Behaviour is identical.

// before  (49 lines)
import { fetchUser } from './api';
import { logEvent } from './log';
import { logError } from './log';
import { Cache } from './cache';

const cache = new Cache<string, string>();

export async function getDisplayName(id: string, fallback: string): Promise<string> {
  if (id === null || id === undefined || id === '') {
    return fallback;
  }

  const cached = cache.get(id);
  if (cached !== null && cached !== undefined) {
    return cached;
  }

  const user = await fetchUser(id);
  if (user === null || user === undefined) {
    logEvent({ type: 'user.miss', id: id, fallback: fallback });
    return fallback;
  }

  let display: string;
  if (user.name !== null && user.name !== undefined && user.name !== '') {
    display = user.name;
  } else {
    if (user.email !== null && user.email !== undefined) {
      display = user.email;
    } else {
      display = fallback;
    }
  }

  cache.set(id, display);
  return display;
}
// after  (42 lines)
import { fetchUser } from './api';
import { logEvent } from './log';
import { Cache } from './cache';

const cache = new Cache<string, string>();

export async function getDisplayName(id: string, fallback: string): Promise<string> {
  if (id == null || id === '') return fallback;

  const cached = cache.get(id);
  if (cached != null) return cached;

  const user = await fetchUser(id);
  if (user == null) {
    logEvent({ type: 'user.miss', id, fallback });
    return fallback;
  }

  let display: string;
  if (user.name != null && user.name !== '') {
    display = user.name;
  } else if (user.email != null) {
    display = user.email;
  } else {
    display = fallback;
  }

  cache.set(id, display);
  return display;
}

What changed: unused logError import dropped, triple-empty checks collapsed, dual !== null && !== undefined collapsed to != null, single-statement if blocks made one-line, } else { if (...) } flattened to else if, object property shorthand applied. Six rules; one diff; nothing else touched.

Install

# global
npm install -g prune-os

# or one-shot
npx prune-os src/ -r --fix

Requires Node 18+.

For AI agents

If you let an AI agent write code in your project, install the Claude Code skill that ships inside prune-os:

npx prune-os install-skill          # writes .claude/skills/pruneos/SKILL.md
npx prune-os install-skill --global # writes ~/.claude/skills/pruneos/SKILL.md

The skill tells the agent: after writing or editing any TS/JS file in this project, run npx prune-os <those files> --check, and apply --fix if it reports changes.

The effect: the agent's first draft still looks like an LLM wrote it (defensive null checks, === true, redundant ternaries, single-statement if blocks), but the agent strips that boilerplate before you ever read the diff. You get the cleaner version on the first pass.

The skill is auto-discovered by Claude Code from .claude/skills/. It works the same as any other skill: the agent reads its description, decides when to apply it, and runs the documented commands.

You don't need the skill to use prune-os. The CLI works standalone.

60-second tour

pruneos app.ts                # simplify one file in place
pruneos src/ -r --fix         # simplify everything under src/
pruneos app.ts --diff         # preview the diff, don't write
pruneos src/ -r --check       # CI mode, exit 1 if anything would change
pruneos src/ -r --json        # machine-readable report
pruneos audit                 # cross-file audit (unused exports, slop, ...)
pruneos audit --fix --json    # apply safe auto-fixes, emit JSON
pruneos init                  # write pruneos.config.ts
pruneos install-skill         # install the Claude Code skill

Audit (cross-file deletion-first scan)

pruneos audit walks the repo, builds an export/import graph, and reports five categories of findings:

| Category | What it catches | Auto-fix | | -------------------- | ---------------------------------------------------------------------------- | ----------------- | | unused-export | Exports nobody imports anywhere | Yes (--fix) | | single-use-export | Exports used by exactly one file. Inline candidate. | No (advisory) | | overengineering | *Factory, *Manager, *Provider, *Adapter with 0-1 uses. Empty interfaces. One-method classes with no state. | No (review) | | slop | // TODO, // FIXME, // AI generated markers. mockX/dummyX/fakeX in non-test files. Empty rethrows. Empty catch {}. | Rethrows yes | | fragmentation | Foo/{Foo.tsx, Foo.types.ts, Foo.utils.ts, index.ts} patterns. Suggests collapsing into one file. | No |

Example output on a deliberately-bad fixture:

PruneOS audit v0.8.0
────────────────────────────────────────────────

Unused exports (dead code) (8)
  lib/format.ts:12       `unusedHelper` is exported but never imported anywhere
  lib/UserManager.ts:2   `UserManager` is exported but never imported anywhere
  ...

Single-use exports (consider inlining) (2)
  lib/format.ts:7        `capitalize` is exported but used by exactly one file

Overengineering patterns (4)
  lib/format.ts:12       `unusedHelper` uses the `Helper` pattern but has no callers
  lib/UserManager.ts:2   `UserManager` uses the `Manager` pattern but has no callers
  lib/UserManager.ts:9   `interface EmptyConfig` is empty
  lib/UserManager.ts:2   `class UserManager` has exactly one method and no state

AI slop / leftover markers (6)
  lib/api.ts:3           try/catch that rethrows the same error unchanged
  lib/slop.ts:1          Leftover marker comment: // TODO: replace with real logic
  ...

File fragmentation (1)
  components/Button/Button.tsx:1  `Button/` is split across 4 files. Consider one file.

Total: 21 findings across 10 files.

The audit respects package.json main/module/types/exports/bin and any src/index.ts at the root. Exports re-exported through those entry points are considered public API and never flagged. Add --only unused-export,slop to filter categories, --json for machine-readable output, --fix to apply the safe transforms (delete unused exports, remove empty rethrows).

Limitations: regex parsing, not a TS AST. Doesn't follow dynamic import('./x') or tsconfig paths aliases. Symbols used only via those patterns may show up as unused. Fix by adding a static import or moving them into the public entrypoint.

What it changes

| Pattern | Becomes | | -------------------------------------------------- | -------------------------------- | | x === true / x !== false | x | | Boolean(x) / !!x | x | | x !== null && x !== undefined | x != null | | x === null \|\| x === undefined \|\| x === '' | x == null \|\| x === '' | | x !== null ? x : y | x ?? y | | if (cond) { return X; } else { return Y; } | if (cond) return X; return Y; | | if (cond) { return X; } | if (cond) return X; | | (args) => { return EXPR; } | (args) => EXPR | | { foo: foo, bar: bar } | { foo, bar } | | } else { if (...) { ... } } | } else if (...) { ... } | | void 0 | undefined | | Multiple import { a } from 'x' from same module | One merged import | | Unused imports / unused locals | Removed | | Runs of blank lines, trailing whitespace | Collapsed |

Everything that's removed or rewritten is reported as a change in the JSON output, so it's auditable.

What it preserves

  • Functions, exports, types, interfaces, JSDoc.
  • Public API of every module.
  • Comments (unless they are commented-out code on their own line).
  • The runtime behaviour of the original file.

If a transform isn't provably safe with a simple text rewrite, PruneOS doesn't do it. The goal is "I'd be happy to merge this diff," not "trust me."

Benchmark

npm run bench runs PruneOS over fixtures in bench/fixtures/. The fixtures are real AI-generated code: full React components with hooks and JSX, plus standalone utility modules.

Median of 5 runs, Node 24, Windows x64. Counts only real removals. Blank lines that were already in the source are preserved.

A full mini SaaS website

bench/fixtures/website/ is a small Next.js-style landing site: 14 files, 898 lines. Layout, two pages (home + pricing), nine components (header, footer, hero, feature grid, pricing card, testimonial, newsletter form, FAQ list, cookie banner) and a lib/ folder.

pruneos bench/fixtures/website -r -a --fix
→ 14 files, 66 lines saved, 61 changes, 12ms (7.3% reduction)

Per-file range is 0-36%. Heaviest wins on utility files (lib/format.ts at 36%, lib/api.ts at 15%) and the pricing card / hero components.

React components (smaller scope)

| Fixture | Lines | After | Saved | % | Changes | Time | | ---------------------------- | ----- | ----- | ----- | ----- | ------- | ---- | | components/UserDashboard.tsx | 167 | 148 | 19 | 11.4% | 15 | 1ms | | components/TodoList.tsx | 202 | 176 | 26 | 12.9% | 7 | 1ms | | components/SearchBar.tsx | 190 | 176 | 14 | 7.4% | 5 | 1ms | | React subtotal | 559 | 500 | 59 | 10.6% | 27 | 3ms |

Functional components with useState/useEffect/useCallback/useMemo, conditional JSX, event handlers, and the verbose patterns AI likes to emit (x !== null && x !== undefined, if (cond) { return X; } else { return Y; }, { foo: foo }). PruneOS removes the patterns it can prove are safe and leaves the rest. Numbers count line reductions only; they do not include in-line shortenings (e.g. a 5-line if becoming a 1-line guard) which dominate the actual diff.

Other patterns

| Fixture | Lines | After | Saved | % | Changes | Time | | ---------------------- | ------ | ------ | ----- | ----- | ------- | ---- | | boolean-noise.ts | 307 | 215 | 92 | 30.0% | 61 | 3ms | | react-patterns.ts | 1,673 | 1,664 | 9 | 0.5% | 19 | 8ms | | notification-system.ts | 2,094 | 2,092 | 2 | 0.1% | 4 | 14ms | | state-store.ts | 2,695 | 2,692 | 3 | 0.1% | 15 | 13ms | | data-pipeline.ts | 3,765 | 3,752 | 13 | 0.3% | 16 | 23ms | | Total (all) | 11,991 | 11,747 | 244 | 2.0% | 201 | 70ms |

boolean-noise.ts is a synthetic upper bound on what's possible when the input is dense with patterns PruneOS knows. The four utility files (react-patterns, notification-system, state-store, data-pipeline) are 70%+ JSDoc and type definitions; PruneOS preserves both, so the percentage there is small by design.

If you're running PruneOS on actual component code, expect line reductions in the 7-15% range with zero behavioural risk. The diff itself is larger than the line count suggests, because most transforms shorten lines rather than removing them. Throughput is around 170k lines/second on commodity hardware.

The bench/RESULTS.md file is regenerated on every npm run bench and contains the full table plus per-issue detection counts.

Reproduce

git clone https://github.com/Hi9841/PruneOS.git
cd PruneOS
npm install
npm run bench

Set PRUNE_BENCH_ITERATIONS=20 npm run bench for a more stable median.

Comparative benchmark: deletion-first vs default AI

The CLI benchmark above measures one thing: how much PruneOS shrinks a file after the code is written. The comparative benchmark in benchmarks/ measures something different and more interesting: how big is the diff an AI produces in the first place when deletion-first constraints are active.

For each scenario the same task is solved twice from the same starting files. Baseline is a default AI session. PruneOS is the same model with the deletion-first system prompt and the PruneOS Claude Code skill active. The runner then diffs each output against the starting fixture with git diff --no-index --numstat.

Results

Three scenarios. Single-model self-play (Claude Opus 4.7). Behaviour verified by reading the diff, not by running tests. See caveats below before quoting these numbers.

| Scenario | Baseline files / new / net LOC | PruneOS files / new / net LOC | | ------------------------ | -----------------------------: | ----------------------------: | | complex-conditional | 2 / 1 / +26 | 1 / 0 / -19 | | duplicate-helper-cleanup | 4 / 2 / +7 | 3 / 1 / -5 | | minimal-bug-fix | 1 / 0 / +17 | 1 / 0 / +0 | | Suite total | 7 / 3 / +50 | 5 / 1 / -24 |

In every scenario the PruneOS run touched fewer or equal files, introduced fewer new files, and produced a smaller net diff. On minimal-bug-fix the baseline fixed the bug and refactored the surrounding function; the PruneOS run changed the one line that contained the bug.

What the runs actually did

  • complex-conditional. Baseline extracted a PermissionRule interface and a PERMISSION_RULES array in a new file. PruneOS rewrote the function as guard clauses in place. Same boolean output for the same inputs.
  • duplicate-helper-cleanup. Baseline created a utils/ directory with a barrel re-export and JSDoc. PruneOS added one date.ts exporting one function.
  • minimal-bug-fix. Baseline rewrote the loop as reduce, extracted a lineTotal helper, and added JSDoc. PruneOS changed item.price to item.price * item.quantity.

Caveats

  • Self-play bias. Both sides are the same model. A different model, or a real engineer driving the baseline, may close the gap or invert it.
  • Small fixtures. 10–25 LOC of starting code per scenario, chosen to be situations where AI overengineers. Real engineering work is bigger and messier.
  • No automated correctness check. Behaviour preservation was verified by reading the diff. Not the same as running a test suite.
  • LOC is not quality. Smaller diffs are reported as smaller diffs. A PruneOS run that won on lines by breaking behaviour would be a loss.

The full per-run breakdown, methodology, and a script to reproduce the numbers live in benchmarks/RESULTS.md and benchmarks/README.md. To re-run:

node benchmarks/measure.mjs

CLI reference

| Flag | Description | | --------------------- | ----------------------------------------------------- | | -r, --recursive | Recurse into directories | | -a, --aggressive | Run multiple simplification passes | | -f, --fix | Write changes to disk (on by default unless --dry-run) | | -d, --dry-run | Preview without writing | | --diff | Show a unified diff per file (implies --dry-run) | | -c, --check | CI mode - exit 1 if any change would be made | | --json | Emit a machine-readable JSON report on stdout | | -v, --verbose | Per-file output | | -s, --silent | Suppress all non-error output | | -e, --extensions | Comma-separated extensions, defaults to .ts,.js,... | | --ignore | Comma-separated dirs to skip | | -h, --help | Show help | | -V, --version | Show version |

Programmatic API

import { analyze, simplify } from 'prune-os';

const code = await fs.promises.readFile('app.ts', 'utf-8');

const a = analyze(code);
console.log(a.metrics.cyclomaticComplexity, a.issues.length);

const r = simplify(code, { aggressive: true });
console.log(`saved ${-r.metrics.netChange} lines, ${r.changes.length} changes`);
await fs.promises.writeFile('app.ts', r.simplified);

simplify returns { original, simplified, changes, metrics, issues }. analyze returns the same metrics PruneOS uses internally (LOC, cyclomatic and cognitive complexity, Halstead volume, maintainability index, unused imports/vars, deep nesting, magic numbers).

CI integration

GitHub Actions:

- run: npx prune-os src/ -r --check

Pre-commit hook:

npx prune-os "$(git diff --cached --name-only --diff-filter=AM | grep -E '\.(ts|tsx|js|jsx)$' | xargs)" --fix

Configuration

pruneos init writes a starter pruneos.config.ts. Override the defaults you want, leave the rest:

export default {
  rules: {
    'no-unused-imports': { enabled: true, severity: 'warning' },
    'no-magic-numbers':  { enabled: false },
  },
  thresholds: {
    maxLineLength: 120,
    maxFunctionLength: 40,
    maxComplexity: 15,
  },
  ignore: ['node_modules', 'dist', '.next'],
};

Status

PruneOS is at v0.5. It's safe to run on real codebases (every transform is text-level and reversible via git), but the rule set is still growing. If you hit a pattern that should be simplified but isn't, open an issue with a minimal repro.

License

MIT - see LICENSE.

Links