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

axiom-scan

v3.3.0

Published

Find the invariants your codebase assumes but never tests

Downloads

5,300

Readme

axiom-scan

Find the invariants your codebase assumes but never tests.

85% line coverage doesn't mean your code is safe. It means 85% of your lines ran during tests — not that the assumptions those lines make have ever been challenged.

Every non-trivial codebase is full of implicit invariants:

  • user.subscription is never null before getBillingPlan() is called
  • initDB() always runs before query()
  • mu.Lock() is always paired with a deferred mu.Unlock()
  • errors from network calls are always surfaced, never silently dropped

These assumptions are true by convention, not by contract. Nobody wrote them down. Nobody tests them. When they break — due to a refactor, a new code path, a missing defer — production breaks and the test suite stays green.

AXIOM reads your code statically, infers what it assumes to be true, diffs that against your test suite, and hands you a ranked list of the bets you're making on code that's never been verified.


Install

npm install -g axiom-scan

Or run without installing:

npx axiom-scan scan ./src

Demo

$ axiom scan ./src

  AXIOM v2.0.0  —  static invariant analysis
  target: /your/project

  ▸ TypeScript/JS   142 source files
  ▸ Go               91 source files
  ▸ Python           38 source files

  271 source files  ·  running inference...

  invariants found:
    null          47
    concurrency   12
    resource       9
    swallowing     8

  ━━━ UNTESTED INVARIANTS  (ranked by blast radius)  ━━━

  ● CRITICAL  src/api/payments.ts:88  [high confidence]
    user.address assumed non-null
    ├─ relied on by 14 call sites
    ├─ nearest public entry: POST /checkout  (2 hops)
    └─ tests covering null path: 0

  ● CRITICAL  src/workers/job.go:134  [high confidence]
    `mu.Lock()` is not deferred — `Unlock` will not run on early return or panic
    ├─ relied on by 3 call sites
    ├─ nearest caller: processJob
    └─ tests covering null path: 0

  ◐ HIGH  src/payments/stripe.py:61  [medium confidence]
    error logged but not re-raised in `except StripeError` block
    ├─ relied on by 6 call sites
    ├─ nearest public entry: POST /subscribe  (1 hop)
    └─ tests covering null path: 0

  ─────────────────────────────────────────────────────
  76 invariants  ·  scanned 312 files in 2.4s

  14 critical  ·  25 high  ·  37 medium
  null ×47  concurrency ×12  resource ×9  swallowing ×8

  axiom explain <file:line>  →  remediation hint

What it detects

| Type | Languages | What it catches | |------|-----------|-----------------| | null | All 8 | Property access on values that could be null/nil/undefined with no guard | | ordering | All 8 | Fields or variables read before they are guaranteed to be initialized | | shape | All 8 | UUID/type format assumed at point of use without validation | | swallowing | All 8 | Errors caught and silently discarded — empty catch, log-only catch | | concurrency | Go, Java, Python, C#, Ruby, Rust | Mutex leaks, unjoined threads, fire-and-forget tasks | | resource | All 8 | File handles, DB connections, sockets opened without guaranteed close — including inter-procedural: wrapper functions that return handles are tracked across call sites |

Concurrency patterns

Gomu.Lock() without defer mu.Unlock(); wg.Add() without wg.Wait()

Javalock.lock() without finally { unlock() }; executor.submit() without shutdown()

Pythonlock.acquire() without finally: lock.release() (prefer with lock:); Thread.start() without Thread.join()

C#Monitor.Enter() without finally { Monitor.Exit() }; SemaphoreSlim.Wait() without finally { Release() }; Task.Run() result discarded (fire-and-forget)

RubyThread.new without .join; mutex.lock without .unlock (prefer mutex.synchronize {})

Rustlet _ = mutex.lock() (guard dropped immediately, lock released at end of statement); thread::spawn handle not joined


Inter-procedural resource tracking (v2.0)

Resource analysis crosses function boundaries. If a function wraps os.Open and returns the handle, every call site that doesn't close the returned value is flagged:

// opener.go
func openConfig(path string) *os.File {
    f, _ := os.Open(path)
    return f               // ← marked as resource-returning wrapper
}

// handler.go
func handleRequest(path string) {
    f := openConfig(path)  // ← FLAGGED: `defer f.Close()` missing
    parseConfig(f)
}

Wrapper functions are not flagged themselves. Two-hop chains are resolved (openRaw → openWrapped → caller). Works across files in the same scan.


Languages

| Language | Parser | |----------|--------| | TypeScript / JavaScript | @typescript-eslint/typescript-estree | | Python | tree-sitter (WASM) | | Go | tree-sitter (WASM) | | Ruby | tree-sitter (WASM) | | Java | tree-sitter (WASM) | | Rust | tree-sitter (WASM) | | C# | tree-sitter (WASM) | | PHP | tree-sitter (WASM) |


Usage

# Scan current directory
axiom scan

# Scan a specific path
axiom scan ./src

# JSON output for CI pipelines
axiom scan --json > axiom-report.json

# SARIF output for GitHub code scanning
axiom scan --sarif > results.sarif

# Only show critical and high severity
axiom scan --min-severity high

# Exit with code 1 if any critical finding exists
axiom scan --fail-on critical

# Only scan files changed since a branch or commit
axiom scan --since main
axiom scan --since HEAD~5

# Watch mode
axiom scan --watch

# Explain a specific invariant with remediation hints
axiom explain src/billing.ts:47

Inline suppressions

Add axiom-ignore on the flagged line or the line above:

// axiom-ignore
const plan = user.subscription.plan;

Works in all supported languages using that language's comment syntax.


LSP server

axiom-lsp is included. It surfaces findings as diagnostics in VS Code, Neovim, Emacs, and any LSP-compatible editor with no extension required. Validates on file open and save.

Neovim (nvim-lspconfig):

require('lspconfig').axiom.setup({
  cmd = { 'axiom-lsp', '--stdio' },
  filetypes = { 'typescript', 'javascript', 'python', 'go', 'ruby', 'java', 'rust', 'cs', 'php' },
})

Ranking

Each invariant is scored by blast radius:

score = (call_sites × 2)
      + entrypoint_proximity   // closer to HTTP handler = higher score
      + type_weight            // concurrency=4, null=3, resource=3, ordering=2, swallowing=2, shape=1
      - test_coverage_discount

Severity buckets: CRITICAL (>20) · HIGH (10–20) · MEDIUM (5–10) · LOW (<5)


Configuration

Create .axiomrc.json in your project root:

{
  "ignore": ["**/*.generated.ts", "src/migrations/**"],
  "minScore": 5,
  "maxResults": 100,
  "patterns": {
    "nullMethods": ["findBySlug", "fetchLatest"],
    "nullFunctions": ["my_custom_fetch"],
    "lifecycleMethods": ["onBoot", "warmUp"],
    "assertionFunctions": ["invariant", "ensure", "checkNotNull"]
  }
}

| Field | Description | |-------|-------------| | ignore | Glob patterns to exclude | | minScore | Minimum blast-radius score to include | | maxResults | Cap total results (highest score first) | | patterns.nullMethods | Extra method names that return null/nil/undefined | | patterns.nullFunctions | Extra function names that return null | | patterns.lifecycleMethods | Extra methods treated as secondary constructors for ordering checks | | patterns.assertionFunctions | Functions that guarantee non-null (invariant, assert, etc.) |


CI / GitHub Actions

- name: Run AXIOM
  run: npx axiom-scan scan --sarif > axiom.sarif

- name: Upload to GitHub Code Scanning
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: axiom.sarif

# Gate on critical findings
- name: Fail on critical
  run: npx axiom-scan scan --fail-on critical

Diff mode for PRs — only scan changed files:

- name: AXIOM diff scan
  run: npx axiom-scan scan --since origin/main --fail-on critical

Why not...

| Tool | Gap | |------|-----| | Istanbul / V8 coverage | Measures line execution, not behavioral assumptions | | TypeScript strict mode | Catches declared-type nulls only, not behavioral invariants | | ESLint | Rule-based — you write the rules; AXIOM infers them | | Semgrep | Pattern matching — you write the patterns; AXIOM infers them | | Mutation testing | Slow, requires tests to exist, doesn't find untested assumptions |


License

MIT