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

eslint-plugin-agent-code-guard

v0.0.21

Published

Normative ESLint floor for agent-written TypeScript: Google TypeScript Style plus stricter safety, Effect, testing, and tooling rules.

Readme

agent-code-guard

ESLint plugin that catches the patterns your coding agent must not ship.

Install

npm  install --save-dev eslint-plugin-agent-code-guard
pnpm add -D eslint-plugin-agent-code-guard

Hello world

Drop this into eslint.config.js:

import { plugin as guard } from "eslint-plugin-agent-code-guard";

export default [
  {
    files: ["**/*.{ts,tsx,mts,cts}"],
    ...guard.configs.recommended,
  },
];

Then run your default lint script. The preset bundles its TypeScript parser, maintained rule plugins, and type-aware projectService setup; the project must have a tsconfig.json. Its default lint path must run ESLint over the repository's intended source surface and run Knip. Keep type checking as an explicit part of the repository's quality pipeline; the preset does not prescribe a particular command string or task orchestrator.

What it catches

Your coding agent is miscalibrated. It was trained on human-written TypeScript — decades of it — written under one constraint that does not apply to it: typing was expensive for humans. That is why its training corpus is saturated with throw new Error("bad"), as Record<string, unknown>, try { ... } catch {}, Promise<T> return types, process.env.FOO!, raw SQL strings, and vi.mock inside integration tests. Those were the compromises humans made when keyboard time was scarce. An agent does not pay the scarcity; it inherits the patterns anyway.

This plugin is the floor. The recommended preset makes the Google TypeScript Style Guide the minimum, layers stricter Agent Code Guard doctrine over it, and bundles the maintained TypeScript-ESLint, import, directive-comment, JSDoc, and SonarJS rule sets needed to enforce that contract. Every enabled rule is an error. The strict preset adds tight complexity budgets on top. An integrationTests preset forbids mocks in files that are supposed to be integration tests.

The clause-by-clause mapping—including the few honest review-only boundaries—is in the Google TypeScript Style coverage ledger. Formatting is deliberately absent from every preset and CI gate; use Oxfmt or another formatter independently.

Google TypeScript semantics

| Rule | Catches | |---|---| | component-function-naming | UpperCamelCase TSX components and lowerCamelCase ordinary functions | | no-nullish-type-aliases | Type aliases that spread null or undefined through layers | | prefer-optional-over-undefined-union | Fields and parameters spelled as T \| undefined instead of ? | | no-enum-boolean-coercion | Explicit or implicit boolean coercion of enum values | | no-static-this | Dynamic this dispatch from static class members | | no-private-identifiers | JavaScript #private fields and methods | | no-redundant-public | Explicit public where TypeScript already defaults to public | | no-unsafe-parameter-defaults | Effectful defaults, non-empty destructuring defaults, and deep/computed parameter destructuring | | no-unsafe-numeric-coercion | Unary +, parseFloat, and decimal or radix-less parseInt | | no-prototype-manipulation | Prototype assignment and mutation APIs | | no-define-property-accessors | Descriptor accessors that hide getters/setters from TypeScript | | require-assertion-rationale | Type and non-null assertions without an adjacent safety rationale |

Async flow

| Rule | Catches | |---|---| | async-keyword | async functions outside Effect/Kysely patterns | | promise-type | Promise<T> return types that erase the error channel | | then-chain | .then(...) chains that hide error propagation | | bare-catch | try { ... } catch {} that swallows the error silently | | no-conditional-chaining | Optional/nullish parameters accepted outside explicit parser/normalizer boundaries | | no-unbounded-concurrency | Effect.*(..., { concurrency: "unbounded" }) fan-out with no visible bound |

Effect

| Rule | Catches | |---|---| | effect-promise | Effect.promise(...) calls that turn rejections into defects | | effect-error-erasure | Effect.fail(new Error(...)) and similar generic error wrapping inside the Effect channel | | either-discriminant | Either.isLeft(...), Either.isRight(...), and _tag === "Left" / "Right" | | tag-discriminant | Manual _tag checks on Effect-flavored tagged unions (Effect, Either, Option, Cause, Exit, Data.TaggedError, …); type-aware through the main presets | | no-effect-error-coalescing | Effect.mapError / catchAll wrappers that collapse typed error variants into one broad error |

Manual algebra

| Rule | Catches | |---|---| | manual-result | Reusable hand-rolled Result / Either algebras instead of Either / Effect | | manual-option | Reusable hand-rolled Option / Maybe algebras instead of Option | | manual-tagged-error | Hand-rolled tagged error classes and error unions that should use Data.TaggedError(...) | | manual-brand | Hand-rolled nominal brands that should use Brand.nominal(...) or Schema.brand(...) | | no-manual-brand-constructor | Cast helpers such as asUserId / makeUserId that manually construct branded values | | no-exported-brand-constructor | Exported brand or schema constructors instead of local constructors plus exported boundary functions/types | | no-manual-enum-cast | as "a" \| "b" string-union casts that should be generated unions |

Safety

| Rule | Catches | |---|---| | as-unknown-as | as unknown as cast chains that bypass type checking | | record-cast | as Record<string, unknown> and similar unsafe casts | | no-process-env-at-runtime | Runtime process.env access instead of reading config once at the boundary | | no-raw-sql | Raw SQL strings that bypass the typed query builder | | no-raw-throw-new-error | throw new Error(...) outside tests — return a tagged error instead | | max-non-trivial-classes-per-file | More than one logic-bearing class per file; classes that extend a configured tag-class factory (default: Data.TaggedError, Context.Tag, Effect.Service, …) are exempt regardless of body |

Testing

| Rule | Catches | |---|---| | no-test-skip-only | .skip / .only / xit / xdescribe in committed test files | | no-example-only-tests | Test scopes with multiple examples but no property/generative invariant test | | no-coverage-threshold-gate | coverageThreshold gates in jest/vitest/vite configs | | no-hardcoded-assertion-literals | Hardcoded string/number literals in test assertions | | no-vitest-mocks | vi.mock(...) inside files that match the integration-tests glob |

Tooling

| Rule | Catches | |---|---| | require-knip-in-lint | package.json default quality scripts that omit Knip |

Vertical organization

These public rules ship disabled in recommended and strict in PR 1. A reviewed follow-up enables them and performs the repository migration.

| Rule | Catches | |---|---| | no-vacuous-jsdoc | A closed grammar of placeholder and mechanically-derived JSDoc filler | | require-stable-file-shell | Missing canonical file overviews, interrupted import shells, and unreasoned bare side-effect imports | | prefer-stepdown-function-order | Story-band and same-band caller-before-callee navigation violations |

Documentation

JSDoc lint comes from bundled eslint-plugin-jsdoc; consumers do not install it separately. recommended and strict require JSDoc on every top-level exported interface, type alias, enum, function, class, and variable, including parameter, property, return, and description content. Type annotations in JSDoc remain forbidden because TypeScript already supplies the types. The standalone documentation preset exposes that same contract for special composition needs. No JSDoc indentation, alignment, tag-line, or other formatter rule is enabled.

Rule IDs in your config are namespaced as agent-code-guard/<rule>. Each rule ships a Before/After doc at the link above and locally at node_modules/eslint-plugin-agent-code-guard/docs/rules/<family>/<rule-name>.md.

Configure

This plugin uses ESLint flat config (required; ESLint ≥ 9). If you have a legacy .eslintrc, migrate to flat config first; see ESLint migration guide.

Flat config:

// eslint.config.js
import { plugin as guard } from "eslint-plugin-agent-code-guard";

export default [
  // Application source: typed Google Style, ACG doctrine, and complexity.
  {
    files: ["**/*.{ts,tsx,mts,cts}"],
    ignores: ["**/*.test.ts", "**/*.spec.ts"],
    ...guard.configs.strict,
  },

  // Test files: the same blocking contract.
  {
    files: ["**/*.test.ts", "**/*.spec.ts", "**/test/**/*.ts", "**/tests/**/*.ts", "**/test-support/**/*.ts"],
    ...guard.configs.strict,
    rules: {
      ...guard.configs.strict.rules,
      "agent-code-guard/no-test-skip-only": "error",
      "agent-code-guard/no-hardcoded-assertion-literals": "error",
    },
  },

  // Matching flat-config blocks merge, so integration tests also forbid mocks.
  {
    files: ["**/*.integration.test.ts"],
    ...guard.configs.integrationTests,
  },
];

Peer dependencies are eslint ≥ 9 and TypeScript >=5 <6.1.0. typescript-eslint, SonarJS, JSDoc, Import-X, and ESLint directive-comment enforcement are runtime dependencies of this package, so preset consumers do not install them separately. Knip is bundled as the agent-code-guard-knip bin.

Presets

The import alias (e.g., guard in the example above) is your choice; adjust the <import>.configs.* path accordingly. Access presets via your import identifier:

  • <import>.configs.recommended — type-aware application source. Flat-config fragment with bundled parser, projectService, plugins, settings, and blocking rules. Enforces Google TypeScript Style, stricter Agent Code Guard rules, export JSDoc, and SonarJS.
  • <import>.configs.strictrecommended plus strict complexity budgets (complexity, max-depth, max-lines, max-lines-per-function, max-statements, cognitive complexity, nested control flow, and related limits).
  • <import>.configs.integrationTests.rules — integration-test glob only. Enforces no-vitest-mocks so integration tests actually hit real dependencies.
  • <import>.configs.documentation — composable export-documentation fragment. The same full top-level export JSDoc contract already appears in both main presets.

Migrating from 0.0.15

The package no longer has a default export. Replace import guard from "eslint-plugin-agent-code-guard" with import { plugin as guard } from "eslint-plugin-agent-code-guard". Main presets now include the parser and typed language options, so remove manual parser wiring and spread the preset directly. Typed lint requires a tsconfig.json. Keep type checking in the repository's explicit quality pipeline, and keep ESLint plus Knip in its default lint path; no particular command string or task orchestrator is required.

Disabling a rule

If a rule is wrong for your codebase, disable it in flat config:

rules: {
  ...guard.configs.recommended.rules,
  "agent-code-guard/async-keyword": "off",
}

Every disable in source should carry a written reason via @eslint-community/eslint-plugin-eslint-comments and the require-description rule. The companion Claude Code skill (see below) wires that pairing automatically.

Name notes

  • npm package: eslint-plugin-agent-code-guard.
  • Rule namespace: agent-code-guard/<rule> — used by both this package and the LSP servers safer-by-default declares (agent-code-guard-syntax, agent-code-guard-architecture). One namespace across the floor (lint), the editor (LSP), and the agent loop keeps the mental model consistent.

Companion

floor — this ESLint plugin (lint-time checks). Catches per-file patterns your agent must not ship: throw new Error(...), as Record<string, unknown>, bare catch {}, etc. Every rule's meta.docs.url points at the corresponding heading in safer-by-default/PRINCIPLES.md, so any ESLint LSP renders a codeDescription.href link straight from each diagnostic to the underlying doctrine.

ceilingsafer-by-default, a Claude Code skill plugin. It calibrates the coding agent at write-time before code is committed, and its .claude-plugin/plugin.json declares two lspServers that auto-start when an LSP-aware editor (or the Claude Code agent loop) opens a TypeScript file:

  • agent-code-guard-syntax — wraps upstream vscode-eslint-language-server to surface every rule from this plugin with its rationale + PRINCIPLES.md link.
  • agent-code-guard-architecture — runs a custom architecture analyzer (folder graph, public surface, vendor type leaks, cycle detection). Architecture rules used to live in this repo; they moved to safer-by-default to keep the npm package pure-syntax. See safer-by-default's ARCHITECTURE.md → LSP integration.

Install both for the full calibration loop:

# The floor (this repo) — lint checks via npm:
pnpm add -D eslint-plugin-agent-code-guard@latest

# The ceiling (Claude Code skill plugin + LSPs) — via Claude Code:
# In a Claude Code session:
/plugin marketplace add chughtapan/safer-by-default
/plugin install safer@safer-by-default

Alternatively, invoke /safer:setup in any TypeScript repo to automate both steps (wires this floor into eslint.config.js, flips tsconfig strict flags, installs the integration-tests preset, and the two LSPs auto-register from the Claude plugin).

Development

pnpm install
pnpm build
pnpm lint
pnpm test

Tests live next to the code they verify. Rule-family tests are under src/rules/<family>/*.test.ts, and shared fixtures live under local test-support/ folders. tsconfig.json excludes *.test.ts and test-support/ from the production build, while Vitest still discovers them.

Mutation testing

Scope: src/**/*.ts (every rule, utility, and the plugin entry). Run:

pnpm mutation

Stryker (with the vitest runner and typescript checker) mutates every source file and replays the vitest suite against each mutant. The default thresholds apply: high 80, low 60, break 50. A run that drops the overall score below 50 exits non-zero.

Mutation testing is a required CI gate. Every PR runs pnpm mutation; dropping below the break threshold fails the check. If you weaken a test, Stryker catches it before the lint rule ships.

Runs are incremental on PR and a full sweep runs nightly. Stryker persists state to .stryker-tmp/incremental.json, cached in CI across runs and refreshed in this repo when a release-quality mutation pass lands.

Changelog

See CHANGELOG.md for release notes.

License

MIT.