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

@devmedic/ignore-engine

v0.1.0

Published

Merges every ignore source a project has (.devmedicignore, .gitignore, workspace defaults, plugin declarations, CLI flags) into one gitignore-correct matcher, and scans a directory tree pruning ignored directories before descending into them — ignore matc

Downloads

30

Readme

@devmedic/ignore-engine

Merges every ignore source a project can have — .devmedicignore, .gitignore, workspace defaults, plugin declarations, a --ignore CLI flag — into one real, gitignore-correct matcher (**, *, ?, [...] character classes, ! negation, directory-only trailing-/ patterns), and scans a directory tree pruning an ignored directory before ever reading its contents. This is the one ignore engine every scanner should use, replacing each package's own separate, inevitably-inconsistent list.

import { createIgnoreService } from '@devmedic/ignore-engine';

const ignoreService = await createIgnoreService({
  projectRoot: '/path/to/project',
  workspaceIgnore: ['**/coverage/**'], // @devmedic/config's DEFAULT_IGNORE + config.ignore
  pluginIgnore: ['__tests__', 'fixtures'], // aggregated from every loaded plugin's manifest
  cliIgnore: ['!keep-this-one.log'], // a --ignore flag — applied last, can override everything above
});

ignoreService.isIgnored('/path/to/project/src/App.ts'); // false
ignoreService.isIgnored('/path/to/project/node_modules', true); // true — isDirectory matters for trailing-/ patterns

const files = await ignoreService.scan(); // readonly string[], absolute paths, sorted

.gitignore/.devmedicignore are read straight from projectRoot (pass useIgnoreFiles: false to skip that, e.g. in tests). ignoreService.sources lists every source that was merged, even ones with no patterns, in the order they were applied — useful for a devmedic doctor-style diagnostic of "why was this file ignored."

Merge order

Later sources can !re-include a path an earlier one excluded — real gitignore precedence, applied across the whole merged set, not per-source:

  1. workspace@devmedic/config's DEFAULT_IGNORE + a project's own config.ignore.
  2. gitignore.gitignore, if present.
  3. devmedicignore.devmedicignore, if present — DevMedic-specific, so it can override .gitignore (e.g. un-ignore something git ignores but DevMedic still wants to see).
  4. plugin — aggregated from every loaded plugin's manifest-declared ignoredDirectories/ignoredFiles/ignoredPatterns.
  5. cli — a --ignore flag — the most specific, most immediate source, applied last so it has the final say.

Pattern syntax

Real .gitignore semantics, via the ignore package rather than a hand-rolled matcher — negation-under-an-excluded-directory and anchoring rules are exactly the kind of thing worth not reinventing:

| Syntax | Meaning | | --------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | * | Any characters within one path segment (*.log matches debug.log, not src/debug.log unless the pattern itself has no leading /) | | ** | Any number of directory levels (**/generated/** matches at any depth) | | ? | Exactly one character | | [abc]/[a-z] | A character set or range | | [!abc] | A negated character set | | !pattern | Re-includes a path an earlier pattern excluded | | dir/ | Directory-only (matches only when isIgnored(path, true) is called) | | No leading / | Matches at any depth — node_modules matches both node_modules/ and packages/foo/node_modules/ |

A real git limitation, faithfully preserved: !negation can re-include a file an earlier file pattern excluded (*.log then !keep.log), but cannot resurrect a file whose parent directory was excluded (scratch then !scratch/notes.ts does nothing — notes.ts stays excluded). This is git's own documented behavior, not a bug in this package; if you need to keep one file, target the file pattern directly rather than the containing directory.

Performance: ignore matching before scanning

scanDirectory never calls readdir on an ignored directory — it prunes before descending, so a huge ignored directory (node_modules, a build output folder) costs one directory-entry check, never a walk of its contents. src/scan.bench.ts (pnpm bench) quantifies this against a naive "list everything, then filter" walker on the same tree (20 relevant files + a 10,000-file ignored node_modules):

| Benchmark | Result | | -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | scanDirectory: 1,000 vs. 10,000 ignored files | No meaningful difference — both sub-millisecond, confirming the walk doesn't scale with ignored-directory size | | scanDirectory (pruned) vs. naive scan-then-filter, same tree | ~3,600x faster — the naive walker still reads every one of the 10,000 ignored files' directory entries |

Numbers are from one real run and will vary by machine — re-run locally (pnpm --filter @devmedic/ignore-engine run bench) for your own baseline.

Used by every scanner

  • apps/cli's workspace file discovery (discoverWorkspaceFiles) — the primary integration: what IgnoreService.scan() returns is exactly what @devmedic/rule-engine#RuleEngine.analyze() receives.
  • @devmedic/project-detection-engine's gatecreateProjectDetectionGate({ isIgnored }) accepts IgnoreService.isIgnored as an additional check, alongside (not replacing) that package's own simpler built-in ignore list.
  • @devmedic/project-scanner's language-file counting is deliberately left as-is — it already has its own small, correct, narrow-purpose ignore list for a fast-glob-based scan; unifying it would mean converting gitignore-style bare names (tests, matches any depth) into fast-glob's different pattern dialect (**/tests/**), which is easy to get subtly wrong for arbitrary caller-supplied patterns. Not attempted — a deliberate scope decision, not an oversight.

Depends on

  • ignore — real, well-established gitignore-semantics matching.