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

@alint-js/core

v0.6.0

Published

Public rule and plugin SDK and run engine for alint

Readme

@alint-js/core

The public SDK and run engine for alint.

What it does

This package provides the core SDK and run engine APIs used by plugins, rules, language processors, embedding tools, and agent adapters:

  • definePlugin and defineRule
  • runAlint
  • rule registry and flat config normalization
  • source runtime helpers
  • built-in JavaScript source extraction, with additional language support for others
  • model resolution by size and capability
  • diagnostics and progress payload types
  • framework-neutral agent contracts under @alint-js/core/agent
  • tool-call structured output under @alint-js/core/structured-output
  • config DSL and types for advanced SDK consumers

How to use

Write a rule:

import { defineRule } from '@alint-js/core'

export const rule = defineRule({
  create: ctx => ({
    async onTargetFile(target) {
      const model = await ctx.model({ size: 'small' })
      const file = await ctx.src.readFile(target.file)

      ctx.report({
        filePath: target.file.path,
        loc: target.loc,
        message: `reviewed ${file.lines.length} lines with ${model.id}`,
      })
    },
  }),
})

Source and project rules receive compact descriptors. Source handlers should pass target.file to ctx.src.readFile() so core can detect changes since planning. Read source only when needed:

import { defineRule } from '@alint-js/core'

export const projectRule = defineRule({
  create: ctx => ({
    async onTargetProject(project) {
      for (const entry of project.files) {
        const file = await ctx.src.readFile(entry.path)
        if (file.text.includes('deprecated-api'))
          ctx.report({ filePath: entry.path, message: 'deprecated API used' })
      }
    },
  }),
})

PlannedSourceTarget, ProjectFileEntry, and ProjectTargetEntry intentionally omit source text. Calls to ctx.src.readFile() are explicit and plugin-owned, so a rule controls which source files it loads and how long it retains them. Cache hits do not invoke the handler and therefore do not perform its execution-time read.

Use the agent contract for tool-using rules:

import { requireAgent } from '@alint-js/core/agent'

const agent = requireAgent(ctx)

Ask a model for one validated, typed result with @alint-js/core/structured-output. It forces the model to call a single reporting tool whose arguments match a valibot schema, validates them, and retries with the validation error fed back to the model:

import { generateStructured } from '@alint-js/core/structured-output'
import { array, description, object, pipe } from 'valibot'

const responseSchema = pipe(
  object({ findings: array(findingSchema) }),
  description('Report findings for this file.'),
)

const { findings } = await generateStructured({
  createMessages: retryFeedback => [
    { content: prompt, role: 'system' },
    ...(retryFeedback ? [{ content: retryFeedback, role: 'user' as const }] : []),
    { content: numberedSource, role: 'user' },
  ],
  logger: ctx.logger,
  metering: ctx.metering,
  model: await ctx.model(),
  operation: 'my-rule-judge',
  schema: responseSchema,
})

The reporting tool is named reportFindings by default (toolName overrides it) and its description defaults to the schema's valibot description(...). toolParametersFromSchema, formatSourceWithLineNumbers, and formatOutputLanguageInstruction are exported for callers that build their own tools or prompts. Use ctx.agent instead when the model needs to explore with tools before answering, because a forced tool call is a single shot, not a loop.

Languages

Core parses JavaScript and TypeScript. Everything else is registered by a plugin, so a rule says which languages it can read and core decides what to hand it:

defineRule({
  create: () => ({ onTargetFunction: (target) => { /* ... */ } }),
  languages: 'any',
})

| languages | the rule receives | a named language nothing registered | | --- | --- | --- | | omitted | file targets only, never functions or classes | not applicable | | 'any' | every language except plaintext | never fails | | ['go', 'rust'] | those languages only | run fails, alint/missing-language | | { ids: ['go'], skipMissing: true } | those languages only | skipped quietly |

'any' excludes plaintext on purpose. Plain text is what a file falls back to when no language claims its extension, so a rule that asked for a language would otherwise be handed unparsed text. When that happens the run reports alint/unregistered-language once per extension — a warning by default, configurable through linterOptions.reportUnregisteredLanguages.

Declaring a list is the stricter choice. It fails the run when the user has not installed a pack that provides one of them, rather than letting the rule match nothing and look like a pass.

A plugin registers a language by describing how to turn a file into targets:

definePlugin({
  languages: {
    zig: {
      extensions: ['.zig'],
      extract: file => [/* SourceTarget[] */],
      name: 'zig',
    },
  },
})

Ids are the identifiers editors use — go, python, typescript, plaintext. Registering fails on a duplicate name or extension, so two plugins can never claim the same language.

Put a FunctionInfo under metadata.function on each function target, and the file's call sites under metadata.calls on the file target. Those two keys are what let a rule read any language without a parser of its own.

@alint-js/languages provides Go, Python and Rust this way.

To parse a file the run was not asked to lint, such as when building a workspace-wide index, use ctx.src.extract(path). It resolves that file's own config and language, and returns nothing for a file the config ignores rather than throwing.

When to use

  • You are writing an alint plugin or rule package.
  • You are adding a language processor or source extractor.
  • You are embedding alint in another tool.
  • You are implementing an AgentAdapter.
  • You need project-wide analysis that can consume compact descriptors and load source lazily.

When not to use

  • Use @alint-js/cli for command-line usage and ordinary alint.config.* files.
  • Use @alint-js/config for setup TOML, config loading, and config-file tooling only.
  • A plugin that needs a persistent repository database should build and inject that database. Do not retain every SourceFile returned by ctx.src.readFile() as a substitute for one.

Memory boundaries

alint bounds concurrent source planning reads and parser work. Planning releases rich extractor values as soon as compact jobs are admitted; queued jobs do not retain source text. It cannot bound source files retained by plugin code after ctx.src.readFile() returns.

A valid cache written by the same alint version is still read from one monolithic JSON document. An extremely large cache may therefore exhaust available memory.

After each cacheable rule job completes, alint writes a cache checkpoint before releasing its scheduler slot. Each checkpoint atomically replaces the monolithic cache file. An interrupted run can therefore reuse every result that had already become durable, but large caches can cause substantial disk writes during runs with many cache misses. Cache hits, skipped jobs, failed jobs, and rules that opt out of caching do not add checkpoint writes. A checkpoint or final cache write error is fatal and causes the run to fail.