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.5

Published

ESLint plugin that guards AI-agent-written TypeScript against common sloppy patterns: async/Promise/.then chains, bare catches, unsafe casts, raw SQL, manual enum casts, mocks in integration tests, and hardcoded secrets.

Readme

agent-code-guard

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

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

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. Twenty-three rules under the recommended preset (twenty errors, three warns), plus an integrationTests preset that forbids mocks in the files that are supposed to be integration tests.

| Rule | Catches | |---|---| | agent-code-guard/async-keyword | async functions outside Effect/Kysely patterns | | agent-code-guard/as-unknown-as | as unknown as cast chains that bypass type checking | | agent-code-guard/promise-type | Promise<T> return types that erase the error channel | | agent-code-guard/then-chain | .then(...) chains that hide error propagation | | agent-code-guard/bare-catch | try { ... } catch {} that swallows the error silently | | agent-code-guard/effect-promise | Effect.promise(...) calls that turn rejections into defects | | agent-code-guard/effect-error-erasure | Effect.fail(new Error(...)) and similar generic error wrapping inside the Effect channel | | agent-code-guard/either-discriminant | Either.isLeft(...), Either.isRight(...), and _tag === "Left" / "Right" | | agent-code-guard/manual-result | Reusable hand-rolled Result / Either algebras instead of Either / Effect | | agent-code-guard/manual-option | Reusable hand-rolled Option / Maybe algebras instead of Option | | agent-code-guard/manual-brand | Hand-rolled nominal brands that should use Brand.nominal(...) or Schema.brand(...) (warn) | | agent-code-guard/manual-tagged-error | Hand-rolled tagged error classes and error unions that should use Data.TaggedError(...) | | agent-code-guard/no-unbounded-concurrency | Effect.*(..., { concurrency: "unbounded" }) fan-out with no visible bound | | agent-code-guard/no-process-env-at-runtime | Runtime process.env access instead of reading config once at the boundary | | agent-code-guard/record-cast | as Record<string, unknown> and similar unsafe casts | | agent-code-guard/no-raw-sql | Raw SQL strings that bypass the typed query builder | | agent-code-guard/no-manual-enum-cast | as "a" \| "b" string-union casts that should be generated unions | | agent-code-guard/no-hardcoded-secrets | AWS/GCP/Azure keys, API tokens, passwords — see doc for patterns and entropy thresholds | | agent-code-guard/no-raw-throw-new-error | throw new Error(...) outside tests — return a tagged error instead | | agent-code-guard/no-test-skip-only | .skip / .only / xit / xdescribe in committed test files | | agent-code-guard/no-coverage-threshold-gate | coverageThreshold gates in jest/vitest/vite configs (warn) | | agent-code-guard/no-hardcoded-assertion-literals | Hardcoded string/number literals in test assertions (warn) | | agent-code-guard/tag-discriminant | Manual _tag checks on tagged errors instead of Effect.catchTag(...) | | agent-code-guard/no-vitest-mocks | vi.mock(...) inside files that match the integration-tests glob |

Each rule ships a Before/After doc at the GitHub link above and locally at node_modules/eslint-plugin-agent-code-guard/docs/rules/<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 guard from "eslint-plugin-agent-code-guard";
import tsParser from "@typescript-eslint/parser";

export default [
  // Application source — prod rules, test files excluded
  {
    files: ["src/**/*.ts"],
    ignores: ["**/*.test.ts", "**/*.spec.ts"],
    languageOptions: {
      parser: tsParser,
      parserOptions: { ecmaVersion: 2022, sourceType: "module" },
    },
    plugins: { "agent-code-guard": guard },
    rules: guard.configs.recommended.rules,
  },

  // Test files — only the test-hygiene rule fires here
  {
    files: ["**/*.test.ts", "**/*.spec.ts", "**/test/**/*.ts", "**/tests/**/*.ts"],
    languageOptions: {
      parser: tsParser,
      parserOptions: { ecmaVersion: 2022, sourceType: "module" },
    },
    plugins: { "agent-code-guard": guard },
    rules: {
      "agent-code-guard/no-test-skip-only": "error",
      "agent-code-guard/no-hardcoded-assertion-literals": "warn",
    },
  },

  // Config files — coverage-gate lint (warn)
  {
    files: ["**/jest.config.*", "**/vitest.config.*", "**/vite.config.*"],
    languageOptions: {
      parser: tsParser,
      parserOptions: { ecmaVersion: 2022, sourceType: "module" },
    },
    plugins: { "agent-code-guard": guard },
    rules: {
      "agent-code-guard/no-coverage-threshold-gate": "warn",
    },
  },

  // Integration tests: no mocks allowed
  {
    files: ["**/*.integration.test.ts"],
    languageOptions: {
      parser: tsParser,
      parserOptions: { ecmaVersion: 2022, sourceType: "module" },
    },
    plugins: { "agent-code-guard": guard },
    rules: guard.configs.integrationTests.rules,
  },
];

Peer dependencies: eslint ≥ 9, typescript ≥ 5.

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.rules — application source. All rules except no-vitest-mocks.
  • <import>.configs.integrationTests.rules — integration-test glob only. Enforces no-vitest-mocks so integration tests actually hit real dependencies.

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>. The namespace matches the companion Claude Code plugin (the ceiling), not the npm package (the floor). Users who install both see a consistent mental model: agent-code-guard is the philosophy family; agent-code-guard is the npm distribution of the lint half.

Companion

floor — this ESLint plugin (lint-time checks). Catches patterns your agent must not ship: throw new Error(...), as Record<string, unknown>, bare catch {}, etc.

ceiling — the agent-code-guard Claude Code plugin. A Claude Code plugin is a skill that instruments the Claude Code IDE and directs the coding agent at write-time, before code is committed. This plugin recalibrates the agent in-band when it writes TypeScript, using the floor rules as a teaching signal.

Install both for the full calibration loop:

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

# The ceiling (Claude Code skills + binaries):
mkdir -p ~/.claude/skills
git clone --single-branch --depth 1 --branch v0.0.5 \
  https://github.com/chughtapan/agent-code-guard.git \
  ~/.claude/skills/agent-code-guard
cd ~/.claude/skills/agent-code-guard && pnpm install

Alternatively, invoke the /safer:setup skill to automate both steps on your behalf (wires floor → eslint.config.js, installs ceiling skill).

Development

pnpm install
pnpm build
pnpm test

Each rule has an independent test file under tests/. The test harness uses @typescript-eslint/rule-tester.

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. Expected wall-clock varies with changed files and cache warmth: a small incremental rerun is usually a few minutes, while a broad sweep is closer to 15-30 minutes on a laptop.

License

MIT.