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

@zinwave/memlint

v0.1.1

Published

Lint your AI agent memory files. Finds stale paths, dead commands, contradictions and duplication in CLAUDE.md / AGENTS.md / .cursorrules by checking them against your actual repo. Offline, deterministic, zero network.

Readme

memlint

Your CLAUDE.md is lying to your agent.

AI coding agents load CLAUDE.md, AGENTS.md and .cursorrules at the start of every session and believe every word. Those files rot: paths get renamed, scripts get deleted, teams migrate off a library, two files end up contradicting each other. A stale memory file is worse than no memory file — the agent acts confidently on something that stopped being true six months ago.

memlint checks what your memory files claim against what is actually in your repository. Fully offline, deterministic, and it never executes anything it finds.

CI npm license

Quickstart

npx @zinwave/memlint

No install, no config, no account. It finds your memory files, checks them, and tells you what is no longer true.

Here is memlint running on its own repository, before the memory files were cleaned up:

$ npx @zinwave/memlint

AGENTS.md
    7:1  warn   This rule is worded differently in CLAUDE.md (58% similar).  drift
         →      Keep the wording in AGENTS.md and reference it from CLAUDE.md, so there is one source of truth.
         ↳      the other wording: "Run `npm run typecheck` and `npm test` before committing an…" CLAUDE.md:23:1
         ⤷      fixable with memlint fix
   11:3  warn   This rule is worded differently in CLAUDE.md (62% similar).  drift
         →      Keep the wording in AGENTS.md and reference it from CLAUDE.md, so there is one source of truth.
         ↳      the other wording: "Precision over recall: when the extractor is not confident,…" CLAUDE.md:31:3
         ⤷      fixable with memlint fix
  17:41  error  Path `docs/adrs` does not exist.  broken-path
         →      Did you mean `docs/adr`?
         ⤷      fixable with memlint fix

CLAUDE.md
  11:5  error  Path `src/core/loader.ts` does not exist.  broken-path
        →      Did you mean `src/core/load.ts`?
        ⤷      fixable with memlint fix
  42:1  info   This entry is marked as temporary ("For now").  stale-entry
        →      Temporary notes outlive their reason. Confirm it is still true.

✖ 2 errors, 2 warnings, 1 info in 2 memory files
  4 issues are auto-fixable — run memlint fix to preview.

Every one of those was a real mistake. src/core/loader.ts had been renamed to load.ts; docs/adrs was really docs/adr; the two files had drifted apart saying the same thing two ways.

What it checks

memlint reads your memory files, extracts typed claims from them — paths, commands, dependencies, versions, plain assertions — and verifies each one against the repository.

| Rule | Default | Fixable | What it catches | | -------------------------------------------------------- | ------- | ------- | --------------------------------------------------------------------- | | broken-path | error | yes | A referenced file or directory does not exist | | dead-command | error | yes | A referenced script, make target or just recipe does not exist | | phantom-dependency | warn | no | A package the memory file tells the agent to use is not installed | | version-mismatch | warn | no | A stated Node version or default branch disagrees with the repository | | contradiction | error | no | Two entries give opposing instructions about the same thing | | duplicate | warn | yes | The same instruction appears more than once | | drift | warn | yes | Two files state the same rule in different words | | stale-entry | info | no | An entry marked temporary, or dated, that has not been revisited | | oversize | warn | no | A memory file is large enough to cost real context every session | | import-cycle | error | no | An @import chain loops back on itself | | external-import | info | no | An @import points outside the repository | | possible-secret | warn | no | A memory file line looks like it contains a credential | | parse-error | error | no | A memory file could not be parsed | | unused-suppression | info | no | A memlint-disable comment silences nothing |

The table above is generated from the rules' own metadata by npm run docs:rules, and CI fails if it drifts. A tool that lectures you about stale documentation should not ship stale documentation.

Surfaces it finds

Automatically, anywhere in the repository:

| Surface | Notes | | --------------------------------- | ------------------------------------------ | | CLAUDE.md | root and every subdirectory | | CLAUDE.local.md | deprecated upstream, still in the wild | | AGENTS.md | root and every subdirectory | | .claude/**/*.md | auto-memory — scanned even when gitignored | | .cursor/rules/*.md, *.mdc | Cursor rules | | .cursorrules | legacy Cursor rules | | .github/copilot-instructions.md | GitHub Copilot | | ~/.claude/CLAUDE.md | only with --include-user |

Claude Code @path imports are followed transitively (max depth 5, cycles detected). Add your own with surfaces in the config, or --surface <glob>.

Your home directory is never read without --include-user. Nothing outside the repository root is ever read or written.

Commands

memlint                        # check the current repository (default)
memlint packages/api           # check one subtree
memlint fix                    # preview deterministic fixes as diffs
memlint fix --write            # apply them
memlint list                   # what your agent loads, and what it costs
memlint rules                  # the rule reference
memlint init --github          # write config and a CI workflow
memlint summary                # one line, for editor and agent hooks

Useful flags

| Flag | Effect | | ------------------------------ | ----------------------------------------------------- | | --format pretty\|json\|sarif | output format; json is a stable versioned schema | | --fail-on error\|warn\|never | lowest severity that exits non-zero (default error) | | --only <ids> | run only these rules | | --ignore-rule <ids> | turn these rules off | | --max-warnings <n> | exit non-zero above this many warnings | | --surface <glob> | restrict the scan (repeatable) | | --include-user | also scan ~/.claude/CLAUDE.md | | --no-color | disable colour (NO_COLOR is honoured too) | | --debug | verbose progress on stderr, never stdout |

Exit codes

| Code | Meaning | | ---- | --------------------------------------------------------- | | 0 | no issues at or above --fail-on | | 1 | issues found | | 2 | memlint itself failed — bad config, unreadable repository |

memlint list

The question nobody can currently answer: what is my agent actually loading, and what does it cost me on every single request?

$ npx @zinwave/memlint list

FILE       LINES  SIZE  TOKENS  MODIFIED
CLAUDE.md     46  1.7K    ~440  2026-07-23  imported by AGENTS.md:6
AGENTS.md     13  0.4K    ~104  2026-07-23

2 surfaces, 2.1K, ~544 tokens loaded per session (estimated at 4 chars/token)
Listed in load order: files further down take precedence when rules disagree.

Fixing

memlint fix is a dry run. It prints a unified diff of what it would change and writes nothing until you pass --write.

It only fixes things with exactly one correct answer: a path with a single candidate at edit distance ≤ 2, an exact duplicate, a case mismatch. Anything requiring judgement stays a suggestion — see ADR 003 for why. Applying twice is a no-op, and that is a test, not a promise.

Configuration

.memlint.json at the repository root, or a memlint key in package.json.

{
  "$schema": "https://unpkg.com/@zinwave/memlint/schema.json",
  "surfaces": ["docs/ai/*.md"], // extra globs to treat as memory files
  "ignore": ["**/vendor/**"], // exclude from discovery and indexing
  "staleDays": 180, // when a dated entry is worth revisiting
  "failOn": "error", // error | warn | never
  "rules": {
    "stale-entry": "off",
    "oversize": { "severity": "error", "maxLines": 150 },
  },
}

Unknown keys are errors, not warnings. A misspelled key that gets silently ignored is how you end up believing a rule is off when it is not. CLI flags override the config file.

Run memlint init to write a starter config, memlint rules to see every rule and its options.

Suppressions

<!-- memlint-disable-next-line broken-path -->

The archive lives in `legacy/notes.md`.

<!-- memlint-disable duplicate -->

…intentionally repeated block…
<!-- memlint-enable -->

<!-- memlint-disable-file stale-entry -->

Omit the rule ids to suppress everything. A suppression that stops silencing anything is itself reported, as unused-suppression — a stale suppression is the same problem memlint exists to find.

CI

memlint init --github

writes a workflow that uploads SARIF, so findings appear as annotations on the memory files in a pull request. Actions are pinned by commit SHA.

Or, minimally:

- run: npx --yes @zinwave/memlint --format sarif --fail-on never > memlint.sarif
- uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
  with:
    sarif_file: memlint.sarif

Claude Code hook

memlint init --claude-hook

adds a SessionStart hook that prints one line at the top of each session:

memlint: 2 broken-path, 1 dead-command in CLAUDE.md — run npx @zinwave/memlint

Pre-commit

memlint installs nothing for you. Add to .husky/pre-commit:

npx --no-install memlint --fail-on error

or, with pre-commit:

repos:
  - repo: local
    hooks:
      - id: memlint
        name: memlint
        entry: npx --yes @zinwave/memlint --fail-on error
        language: system
        pass_filenames: false

Privacy and the no-network guarantee

memlint makes zero network calls. No telemetry, no analytics, no update check, no error reporting. Your memory files and source code never leave your machine.

That is not a promise, it is a build gate: scripts/no-network-gate.mjs scans the source for network primitives and HTTP clients and fails CI on a hit, and also refuses any runtime dependency with an install script. You can verify the claim yourself in about thirty seconds.

memlint also never executes anything it finds. dead-command verifies npm run build by reading package.json as text, not by running it — the naive implementation of that rule is a remote code execution vector triggered by cloning a repository and typing npx @zinwave/memlint.

The only subprocess memlint ever spawns is git, via execFile with a fixed argument array and no shell.

Security

memlint reads untrusted repository content. Reads are confined to the repository root (the sole exception being ~/.claude/CLAUDE.md under --include-user, which is read-only); writes are confined with no exception. Path traversal, symlink escapes, ReDoS and resource exhaustion are all bounded and tested. possible-secret never prints the value it matched.

Full threat model and reporting policy: SECURITY.md.

Programmatic use

import { runMemlint } from 'memlint';

const report = await runMemlint({ cwd: process.cwd() });
for (const issue of report.issues) {
  console.log(`${issue.file}:${issue.line} ${issue.ruleId} ${issue.message}`);
}

The --format json payload carries schemaVersion (currently 1.0) and is a stable contract: fields are never removed or repurposed within a schema major.

FAQ

Why not just ask the agent to clean up its own memory? Because the agent trusts the memory. It has no independent way to know that src/routes.ts was renamed — the file that would have told it is the file that is wrong. External, deterministic verification is the entire point.

Does it send my code anywhere? No, and you do not have to take our word for it. See the no-network gate above.

Is this a memory store, or an MCP server? No. memlint has no server, no accounts, no storage, and does not manage memory. It is a linter. It complements tools that write agent memory by checking that what they wrote is still true.

Will it delete things I want to keep? Only fix --write modifies anything, only for issues with exactly one correct answer, and every change is printed as a diff first. Use suppression comments for deliberate exceptions.

Why is my legitimate path reported? That is a bug, and the highest-priority kind. Please open a false-positive report.

Roadmap

  • v1.1 — opt-in --ai semantic pass (bring your own key, off by default, strictly outside the offline core)
  • v1.2memlint gc, interactive consolidation of bloated memory files
  • Full Python, Go and Rust manifest support (the DependencyProvider interface is in place; only npm/pnpm/yarn are implemented today)
  • Editor extension with inline diagnostics

Contributing

Adding a rule is a one-file pull request. See CONTRIBUTING.md for the walkthrough, and docs/adr/ for the decisions behind the design.

License

MIT