Turning Your Internal Code Review Checklist Into a Paid Linting Ruleset

June 08, 2026 7 min read 41 views
A stylized checklist converting into code rule nodes and brackets, representing automated linting in a clean editorial illustration

Every team has that one senior engineer who leaves the same five comments on every pull request. The comments are correct, they're valuable, and they're completely ignored until the third review cycle. If that's your team, you already have the raw material for a product.

Packaging your code review knowledge into a distributable linting ruleset is a concrete way to stop repeating yourself, help other teams, and charge for something you've already built. Here's how to do it.

What You'll Learn

  • How to extract repeatable patterns from your existing review process
  • How to encode those patterns as custom lint rules (ESLint as the primary example)
  • How to package and version the ruleset for external distribution
  • How to price and sell it without a full SaaS infrastructure
  • Common mistakes that sink linting products before they find users

Prerequisites

This article uses ESLint as the main example because it has the best-documented plugin API and the largest JavaScript/TypeScript audience. The same business logic applies to Pylint plugins, RuboCop extensions, or any other language's static analysis framework. You should be comfortable writing a Node.js module and have a rough sense of how ASTs (Abstract Syntax Trees) work. You do not need to have shipped a commercial product before.

Step 1: Mine Your Review History

Before writing a single line of rule code, spend two hours in your pull request history. Export or scroll through the last six months of review comments and look for anything that appears more than three times. Copy each one into a plain text file.

You're looking for three categories:

  • Style and consistency β€” naming patterns, file organization, import ordering
  • Safety and correctness β€” null checks, error handling, forbidden patterns specific to your stack
  • Architecture and coupling β€” dependency direction rules, forbidden cross-module imports, layer violations

The third category is where the real money is. Style rules already exist in eslint-config-airbnb or prettier. The rules that encode your team's specific architectural decisions are the ones nobody else has published.

Aim for a list of 15–30 candidate rules. You won't ship them all, but you need a large enough pool to pick the ones that are genuinely enforceable by a static tool.

Step 2: Decide What a Tool Can Actually Enforce

Not every review comment translates to a lint rule. "This function is doing too much" requires human judgment. "You should never call fetchUser directly from a UI component" is a rule a tool can check.

Run each candidate through this filter: can I write a condition that is always true or always false for this pattern, with no context from outside the file? If yes, it's a candidate. If it needs runtime information or semantic understanding of business logic, cut it.

A practical heuristic: if you can demonstrate the violation with a 10-line code example, you can probably write the rule. If you need a paragraph of background, you cannot.

Step 3: Write Your First Custom ESLint Rule

ESLint rules are functions that receive an AST node and return visitor methods. The learning curve is real, but the first rule is the hardest. Here's a minimal example that enforces a common pattern: preventing direct console.log calls in production service files.

// rules/no-console-in-services.js

module.exports = {
  meta: {
    type: "suggestion",
    docs: {
      description: "Disallow console.log in service layer files",
      recommended: true,
    },
    schema: [],
  },
  create(context) {
    const filename = context.getFilename();
    // Only apply in files under a /services/ directory
    if (!filename.includes("/services/")) {
      return {};
    }
    return {
      CallExpression(node) {
        if (
          node.callee.type === "MemberExpression" &&
          node.callee.object.name === "console" &&
          node.callee.property.name === "log"
        ) {
          context.report({
            node,
            message:
              "Use a structured logger instead of console.log in service files.",
          });
        }
      },
    };
  },
};

The meta block is documentation and configuration metadata. The create function returns an object whose keys are AST node types. ESLint calls your visitor function whenever it encounters that node type during traversal.

To explore the AST for any snippet of code, use astexplorer.net. Paste the code you want to flag, select the ESLint parser, and inspect the tree to find the node type and property names you need to match against.

Step 4: Structure Your Plugin for Distribution

A single rule isn't a product. A curated, versioned, documented plugin is. Here's a minimal directory layout for an ESLint plugin:

eslint-plugin-yourname/
  lib/
    rules/
      no-console-in-services.js
      no-direct-db-in-controllers.js
      require-error-boundaries.js
  index.js
  package.json
  README.md
  CHANGELOG.md

The index.js file registers all your rules and exports named configurations:

// index.js
const noConsoleInServices = require("./lib/rules/no-console-in-services");
const noDirectDbInControllers = require("./lib/rules/no-direct-db-in-controllers");

module.exports = {
  rules: {
    "no-console-in-services": noConsoleInServices,
    "no-direct-db-in-controllers": noDirectDbInControllers,
  },
  configs: {
    recommended: {
      plugins: ["yourname"],
      rules: {
        "yourname/no-console-in-services": "warn",
        "yourname/no-direct-db-in-controllers": "error",
      },
    },
  },
};

Ship a recommended config so buyers can get started with a single line in their .eslintrc. Offer a strict config that turns warnings into errors for teams that want zero tolerance.

Step 5: Write Rule Documentation That Sells

Your documentation is your sales page. Every rule should have its own markdown file with three sections: what the rule catches, why it matters, and an example of the bad pattern versus the good pattern.

Bad documentation:

This rule disallows console.log in service files.

Good documentation:

Service layer functions run in production request paths where unstructured log output causes noise in log aggregation systems and leaks sensitive request data. This rule forces you to use your structured logger, which tags output with request IDs and redacts configured fields automatically.

The second version is defensible in a code review, convincing to a skeptical senior engineer, and it answers the question "why should I care?" before the buyer even asks it.

Step 6: Choose a Pricing and Distribution Model

You have several realistic options, each with different trade-offs.

Open Core (Free base, paid extensions)

Publish your most generic rules for free on npm under a public license. Keep the domain-specific or architectural rules in a separate private package that requires a license key or npm token. This builds credibility and top-of-funnel awareness while gating the most valuable content.

Flat-fee npm token

Host the full package on a private npm registry (npm itself supports private packages with a paid plan, or you can self-host with Verdaccio). Sell a time-limited or team-scoped access token. Tools like Gumroad or Lemon Squeezy handle payment and can deliver the token via a post-purchase webhook. This requires no custom backend.

One-time purchase with a license file

The simplest model. Buyer pays, receives a zip file with the plugin source and a LICENSE file scoped to their organization. You lose upgrade distribution but you also lose subscription churn. Good for a first version while you validate demand.

For pricing, benchmark against what a developer costs per hour. If your ruleset saves a team one hour of review comments per week, it pays for itself at almost any price point below a few hundred dollars annually per seat. Start higher than feels comfortable. You can always discount; raising prices on existing customers is harder.

Step 7: Validate Before You Over-Build

Don't spend three months polishing 40 rules before you talk to a potential buyer. Write five to eight of your strongest rules, put together a README and a short video walkthrough (even a screen recording), and post it in two or three developer communities where your target audience hangs out.

Ask for feedback, not sales, in the first round. You want to hear which rules people find valuable and which ones they'd never turn on. That feedback shapes your roadmap faster than any amount of solo building.

A pre-sale β€” collecting payment before the product is fully built β€” is a legitimate and useful signal. If nobody will pay for a description of the product, they won't pay for the product itself.

Common Pitfalls

  • Rules that are too opinionated for strangers. Your internal rules encode your team's context. Rules like "all functions must be under 30 lines" will get turned off immediately by external teams. Focus on rules that encode patterns with clear, objective failure modes.
  • No autofix support. Rules that report an error but can't fix it automatically feel punishing. Where possible, implement the fixable property in your rule's meta block and provide a fixer function. Autofixable rules get adopted; error-only rules get disabled.
  • Skipping tests. ESLint provides RuleTester for writing unit tests against your rules. Ship tests. Buyers running your plugin on a large codebase will find edge cases you didn't expect, and you need a test harness to fix them without breaking the cases you already handle.
  • Under-scoping the package name. Pick a plugin name that is specific to your brand or niche. Generic names like eslint-plugin-best-practices compete with hundreds of existing packages and signal nothing about your expertise.
  • Ignoring TypeScript-specific AST shapes. If your target audience uses TypeScript, test your rules with @typescript-eslint/parser as well as the default parser. Type annotations change the AST shape in ways that break rules written only against plain JavaScript.

Next Steps

You have a path from sticky-note checklist to a product someone will pay for. Here's what to do this week:

  1. Pull your last 90 days of review comments and tag any that appear three or more times. That's your candidate list.
  2. Install the ESLint plugin starter (npm init @eslint/plugin) and implement your top three rules with RuleTester coverage.
  3. Write the documentation first for each rule, explaining the why before the how. If you can't explain why in two sentences, the rule isn't strong enough to sell.
  4. Share the plugin with one external team β€” a friend's company, a former employer, a community Slack β€” and ask for 30 minutes of honest feedback.
  5. Set a price and a launch date before the plugin feels ready. Shipping a real but imperfect product beats indefinitely polishing something nobody has seen.

πŸ“€ Share this article

Sign in to save

Comments (0)

No comments yet. Be the first!

Leave a Comment

Sign in to comment with your profile.

πŸ“¬ Weekly Newsletter

Stay ahead of the curve

Get the best programming tutorials, data analytics tips, and tool reviews delivered to your inbox every week.

No spam. Unsubscribe anytime.