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

@ontrails/warden

v0.2.3

Published

AST-based code convention rules for Trails. Built-in lint rules catch contract violations at development time, alongside lock drift detection and CI formatters.

Readme

@ontrails/warden

AST-based code convention rules for Trails. Built-in lint rules catch contract violations at development time, alongside lock drift detection and CI formatters.

Structural checks (compose target existence, declared resource existence, recursive composition, example schema validation) live in validateTopo() from @ontrails/core. Warden handles the code-level rules that need AST analysis.

For rule-home boundaries and authoring doctrine, see the Warden guide and Warden Rules.

Usage

From the Trails CLI:

bunx trails warden # Run all checks

Or programmatically:

import { runWarden, formatWardenReport } from '@ontrails/warden';

const report = await runWarden({ topo: graph });
console.log(formatWardenReport(report));

Rules

Built-in rules are registered in wardenRules and wardenTopoRules; use those registries or wardenTopo.ids() for the current rule list instead of copying a static table into docs.

Rules cover several families:

  • implementation and Result contract checks
  • compose, fire, resource, and detour declaration drift
  • draft-state containment
  • source-static guardrails such as surface-type leakage
  • topo-aware checks that need the resolved graph or resource mock shape

When adding or auditing rules, follow Warden Rules: name the invariant, import owner-held framework data, choose the narrowest Warden tier, and collapse families only when the data model, traversal, and diagnostic shape are shared.

Project-local rules

Projects can carry local Warden rules in .trails/rules.ts or direct .trails/rules/*.ts files. runWarden() and trails warden load those files by default for lint runs, then run those rules alongside the built-in registries. Drift-only runs do not import project-local rule modules. Embedders that need only built-in or explicitly provided rules can pass projectRules: false.

Warden does not recursively discover nested .trails/rules files. Use nested files as private helpers and re-export from a direct entrypoint when a local rule grows. Rule ids must be unique across every project-local module. The retired trails/warden/rules location reports a migration diagnostic instead of loading.

Warden uses the shared Trails project-root resolver when a caller does not pass rootDir or --root-dir, so commands launched from nested directories still load the nearest root trails.config.* and its .trails/rules* files. An explicit root always wins over discovery.

This is the right home for repo-specific migration checks or governance that has not earned a place in @ontrails/warden itself.

Scope

Use warden.scope.exclude in trails.config.* to keep generated, scratch, or local planning paths outside Warden governance while leaving durable project paths in scope.

{
  "warden": {
    "scope": {
      "exclude": [".scratch/**", ".agents/notes/**"]
    }
  }
}

For a single run, pass one or more root-relative globs:

warden --scope-exclude '.scratch/**' --scope-exclude '.agents/notes/**'

Scope is a governance boundary, not a migration scan boundary. Regrade uses its own scope.exclude / --exclude controls for migration plans.

A rule module may export rule, rules, sourceRule, sourceRules, topoRule, or topoRules. Rules without explicit metadata receive default repo-local source-static or topo-aware metadata so short migration rules can run without extra ceremony. Project-aware source rules that provide checkWithContext() default to repo-local project-static metadata.

export const rule = {
  name: 'local-contract-check',
  severity: 'error',
  description: 'Local contract examples keep their migration marker.',
  check(sourceCode, filePath) {
    return sourceCode.includes('deprecatedMarker')
      ? [
          {
            filePath,
            line: 1,
            message: 'Replace deprecatedMarker before release.',
            rule: 'local-contract-check',
            severity: 'error',
          },
        ]
      : [];
  },
};

Drift detection

Warden integrates with @ontrails/topography to detect when the topo has changed without updating the lock file:

import { checkDrift } from '@ontrails/warden';

const drift = await checkDrift(process.cwd(), graph);
if (drift.stale) {
  console.log('lock file is stale -- regenerate with `trails compile`');
}

CI integration

Add to lefthook for pre-push enforcement:

pre-push:
  commands:
    warden:
      run: bunx trails warden
      tags: governance

CI formatters for structured output:

import {
  formatGitHubAnnotations,
  formatJson,
  formatSummary,
} from '@ontrails/warden';

Reusable source-code parser helpers now live in @ontrails/source:

import { findStringLiterals, parse, walk } from '@ontrails/source';

Trail-based API

Every built-in warden rule is also available as a composable trail. This makes rules queryable, testable, and invocable through any Trails surface.

import {
  runTopoAwareWardenTrails,
  runWardenTrails,
  wardenTopo,
} from '@ontrails/warden';

// Inspect the warden rule trails
console.log(wardenTopo.ids()); // ['warden.rule.no-throw-in-implementation', ...]

// Run all rule trails against a source file
const diagnostics = await runWardenTrails(filePath, sourceCode, {
  knownTrailIds: myApp.ids(),
  knownResourceIds: myApp.resourceIds(),
});

// Run built-in topo-aware rule trails once against the resolved graph
const topoDiagnostics = await runTopoAwareWardenTrails(myApp);

To wrap a custom rule as a trail, import wrapRule from the root package entrypoint:

import { wrapRule } from '@ontrails/warden';

This is the same factory used internally to build all built-in rule trails.

API

| Export | What it does | | --- | --- | | runWarden(options?) | Run all rules and drift checks, return a report | | formatWardenReport(report) | Human-readable report | | checkDrift(rootDir, topo?, options?) | Check if the lock file matches the current topo; pass { overlays } from the app module so the comparison graph carries the overlay content compile embeds. Stale results name driftedOverlayNamespaces when overlays diverge | | wardenRules | Registry of all built-in rules | | builtinWardenRuleMetadata | Tier, scope, lifecycle, and invariant metadata for built-in rules | | getWardenRuleMetadata(ruleOrName) | Resolve inline or built-in metadata for a Warden rule | | listWardenRuleMetadata() | List built-in rule metadata entries | | wardenTopo | Topo of all built-in rule trails (one per rule) | | runWardenTrails(filePath, sourceCode, options?) | Dispatch file-scoped rule trails for a file, collect diagnostics | | runTopoAwareWardenTrails(topo) | Dispatch built-in topo-aware rule trails once for a resolved topo | | loadProjectWardenRules(rootDir) | Load rule modules from .trails/rules.ts or direct .trails/rules/*.ts children | | formatGitHubAnnotations(report) | GitHub Actions annotation format | | formatJson(report) | Machine-readable JSON | | formatSummary(report) | Compact summary line | | wrapRule(rule) | Wrap a custom rule as a trail (same factory used for all built-in rule trails) |

Source-code parser helpers are owned by @ontrails/source, not the Warden root runtime barrel.

runWarden({ tier }) can narrow a run to source-static, project-static, topo-aware, drift, or advisory. Omit tier for the default full run.

See the API Reference for the full list.

Installation

These installation examples target Trails 0.2.1 on the normal npm release line.

bun add --exact -d @ontrails/[email protected]