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

@silupanda/prompt-lint

v0.2.2

Published

Static analysis for LLM prompts

Downloads

14

Readme

prompt-lint

Static analysis for LLM prompts. Catch clarity, security, structure, efficiency, and best-practice issues before your prompts reach production.

npm version npm downloads license node types


Description

prompt-lint is a zero-dependency, deterministic linting engine for LLM prompts. It parses prompt text -- whether a plain string, an OpenAI-style message array, or an Anthropic-style prompt object -- into a structured intermediate representation, then evaluates it against a configurable set of rules. Rules detect vague instructions, prompt injection vulnerabilities, missing output format specifications, contradictory directives, template variable errors, excessive length, and repeated instructions.

The result is a structured LintReport with per-rule diagnostics, severity levels, source locations, and fix suggestions -- suitable for programmatic use in CI/CD pipelines, code review tooling, and prompt management systems.

No LLM API calls are made. Analysis runs entirely offline in milliseconds.


Installation

npm install prompt-lint

Requires Node.js >= 18.


Quick Start

import { lint } from 'prompt-lint';

const report = lint({
  source: 'Be helpful and try to answer the user question.',
  preset: 'recommended',
});

console.log(report.passed);
// false -- vague instructions detected

console.log(report.summary);
// { total: 2, errors: 0, warnings: 2, infos: 0 }

console.log(report.diagnostics[0]);
// {
//   ruleId: 'vague-instruction',
//   severity: 'warning',
//   category: 'clarity',
//   message: 'Vague instruction: "be helpful". Be explicit about what is required.',
//   suggestion: 'Replace "be helpful" with a specific, measurable directive.',
//   location: { startLine: 1, startColumn: 1, ... }
// }

Features

  • 10 built-in rules covering clarity, security, structure, efficiency, and best practices
  • Multiple prompt formats -- plain text, OpenAI message arrays, and Anthropic prompt objects
  • Template variable detection -- {{var}}, ${var}, {var}, and %(var)s syntaxes
  • Configurable presets -- recommended, strict, security-only, minimal, and off
  • Per-rule severity overrides -- promote, demote, or disable individual rules
  • Structured output -- every diagnostic includes rule ID, severity, category, source location, message, and optional suggestion
  • Zero dependencies -- uses only Node.js built-ins at runtime
  • TypeScript-first -- full type definitions included, all exports are strongly typed
  • Fast -- deterministic, pattern-based analysis runs in milliseconds with no API calls

API Reference

lint(options: LintOptions): LintReport

The primary entry point. Parses the prompt source, resolves rule configuration from the selected preset and any overrides, runs all enabled rules, and returns a structured report.

import { lint } from 'prompt-lint';

const report = lint({
  source: 'Summarize the following document in JSON format.',
  preset: 'strict',
  rules: {
    'missing-examples': 'off',
  },
});

parse(source: PromptSource): PromptDocument

Parses a prompt source into a PromptDocument without running any lint rules. Useful for inspecting the parsed structure, extracting variables, or building custom analysis on top of the parsed representation.

import { parse } from 'prompt-lint';

const doc = parse('Answer the question: {{user_query}}');

console.log(doc.format);          // 'plain-text'
console.log(doc.variables);       // [{ name: 'user_query', syntax: '{{}}', occurrences: [...] }]
console.log(doc.estimatedTokens); // 9
console.log(doc.roles);           // [{ role: 'system', content: '...', location: {...} }]

Types

All types are exported from the package root.

LintOptions

interface LintOptions {
  source: PromptSource;
  preset?: 'recommended' | 'strict' | 'security-only' | 'minimal' | 'off';
  rules?: Record<string, { severity?: Severity } | Severity>;
  variables?: Record<string, { required?: boolean }>;
}

| Field | Type | Default | Description | |---|---|---|---| | source | PromptSource | (required) | The prompt to lint. See Prompt Source Formats below. | | preset | string | 'recommended' | Base rule configuration preset. | | rules | Record | undefined | Per-rule severity overrides applied on top of the preset. | | variables | Record | undefined | Declared variables for the undefined-variable rule. |

PromptSource

type PromptSource = string | PromptMessage[] | AnthropicPrompt | { file: string };

Accepted input formats:

| Format | Example | |---|---| | Plain text string | 'Analyze the following document.' | | OpenAI message array | [{ role: 'system', content: '...' }, { role: 'user', content: '...' }] | | Anthropic prompt object | { system: '...', messages: [{ role: 'user', content: '...' }] } | | File reference | { file: './prompt.txt' } |

PromptMessage

interface PromptMessage {
  role: 'system' | 'user' | 'assistant' | 'developer';
  content: string;
}

AnthropicPrompt

interface AnthropicPrompt {
  system: string;
  messages: Array<{ role: 'user' | 'assistant'; content: string }>;
}

LintReport

interface LintReport {
  passed: boolean;          // true when there are zero errors (warnings and infos do not fail)
  timestamp: string;        // ISO 8601 timestamp
  durationMs: number;       // analysis duration in milliseconds
  diagnostics: LintDiagnostic[];
  summary: LintSummary;
  document: PromptDocument; // the parsed prompt representation
  preset: string;           // the preset that was used
}

LintDiagnostic

interface LintDiagnostic {
  ruleId: string;
  severity: 'error' | 'warning' | 'info';
  category: RuleCategory;
  location: SourceLocation;
  message: string;
  suggestion?: string;
}

LintSummary

interface LintSummary {
  total: number;
  errors: number;
  warnings: number;
  infos: number;
}

PromptDocument

interface PromptDocument {
  source: string;
  format: 'plain-text' | 'message-array' | 'anthropic';
  roles: Array<{ role: string; content: string; location: SourceLocation }>;
  variables: Array<{ name: string; syntax: string; occurrences: SourceLocation[] }>;
  estimatedTokens: number;
  characterCount: number;
}

SourceLocation

interface SourceLocation {
  startOffset: number;
  endOffset: number;
  startLine: number;
  startColumn: number;
  endLine: number;
  endColumn: number;
}

Severity

type Severity = 'error' | 'warning' | 'info' | 'off';

RuleCategory

type RuleCategory = 'clarity' | 'security' | 'structure' | 'efficiency' | 'best-practice';

RuleContext

interface RuleContext {
  report(diagnostic: Omit<LintDiagnostic, 'ruleId' | 'severity' | 'category'>): void;
  locationFromOffset(start: number, end: number): SourceLocation;
}

RuleDefinition

interface RuleDefinition {
  id: string;
  category: RuleCategory;
  defaultSeverity: Severity;
  description: string;
  check(document: PromptDocument, context: RuleContext, options?: LintOptions): void;
}

Configuration

Presets

Presets define the base severity for all 10 built-in rules. Select a preset with the preset option.

recommended (default)

Balanced configuration for production use. All rules are enabled at reasonable severity levels.

| Rule | Severity | |---|---| | vague-instruction | warning | | missing-output-format | warning | | missing-task-definition | warning | | contradictory-directives | error | | undelimited-variable | warning | | prompt-injection-risk | error | | excessive-length | warning | | missing-examples | info | | undefined-variable | warning | | repeated-instructions | info |

strict

All rules elevated to error or warning. Suitable for enforcing high-quality standards in CI.

| Rule | Severity | |---|---| | vague-instruction | error | | missing-output-format | error | | missing-task-definition | error | | contradictory-directives | error | | undelimited-variable | error | | prompt-injection-risk | error | | excessive-length | error | | missing-examples | warning | | undefined-variable | error | | repeated-instructions | warning |

security-only

Only security-focused rules are enabled. All other rules are disabled.

| Rule | Severity | |---|---| | undelimited-variable | error | | prompt-injection-risk | error | | All others | off |

minimal

Only the most critical rules. Catches contradictions and injection risks.

| Rule | Severity | |---|---| | contradictory-directives | error | | prompt-injection-risk | error | | All others | off |

off

All rules disabled. Useful as a base when enabling only specific rules via overrides.

Per-Rule Overrides

Override individual rule severities on top of any preset using the rules option. Accepts either a severity string or an object with a severity field.

lint({
  source: '...',
  preset: 'recommended',
  rules: {
    'vague-instruction': 'error',             // string form
    'missing-examples': { severity: 'off' },  // object form
    'repeated-instructions': 'warning',
  },
});

Variable Declarations

Pass known template variables via the variables option to enable the undefined-variable rule. Any variable used in the prompt that is not listed in this map will be reported.

lint({
  source: 'Answer {{user_query}} with context from {{knowledge_base}}.',
  variables: {
    user_query: { required: true },
    knowledge_base: { required: false },
  },
});

Rules

Clarity Rules

vague-instruction

Detects vague, non-specific phrases that weaken prompt effectiveness. Flagged phrases: "be helpful", "do your best", "try to", "if possible", "as needed", "as appropriate", "feel free to", "whenever you can", "you may".

Each occurrence is reported individually with a suggestion to replace the vague phrase with a specific, measurable directive.

missing-output-format

Warns when generation verbs (write, generate, create, produce, output, provide) appear in the prompt but no output format keyword is found. Format keywords include: json, yaml, markdown, list, table, csv, xml, bullet, numbered, paragraph, html, text.

If any format keyword is present anywhere in the prompt, the rule does not fire.

missing-task-definition

Warns when the first role block (typically the system message) contains no primary action verb. The rule looks for: analyze, write, generate, summarize, extract, classify, translate, answer, explain, review, evaluate, compare, create, identify, convert, describe, list, find.

contradictory-directives

Detects two categories of contradictions:

  1. Always/never conflicts -- both "always" and "never" appear in the prompt. While they may apply to different subjects, this pattern is flagged for manual review.
  2. Format conflicts -- multiple output format directives are detected (e.g., both "json" and "yaml" appear), suggesting conflicting format requirements.

Security Rules

prompt-injection-risk

Detects phrases commonly used in prompt injection attacks: "ignore previous", "ignore all", "ignore the above", "disregard", "forget everything", "new instructions", "override", "ignore all previous instructions", "ignore your instructions", "do not follow".

Each occurrence is reported as an error with a suggestion to remove the phrase or ensure user input is properly delimited.

undelimited-variable

Warns when template variables whose names suggest user input (containing: user, input, query, request, message, text, content, data) are not wrapped in protective delimiters. Recognized delimiters include XML tags, quotes (', ", `), and code fences (```).

This rule helps prevent prompt injection by ensuring user-controlled content is clearly bounded.

Structure Rules

undefined-variable

When options.variables is provided, reports any template variable used in the prompt that is not listed in the variables map. This catches typos in variable names and undeclared variables that would fail at runtime.

This rule only fires when the variables option is explicitly provided.

Efficiency Rules

excessive-length

Warns when the estimated token count exceeds 2,000 tokens. Token count is estimated as Math.ceil(characterCount / 4). Suggests splitting into smaller, focused prompts or reducing verbosity.

repeated-instructions

Detects instructions (sentences or clauses of 10+ characters) that appear 3 or more times in the prompt. Repeated instructions waste tokens and can confuse model behavior. Suggests consolidating into a single directive.

Best-Practice Rules

missing-examples

When a prompt exceeds 500 characters but contains no example markers, suggests adding few-shot examples. Recognized markers: example:, for example, e.g., eg., input:, output:, sample:, for instance, such as, demonstration:.


Error Handling

The lint() function is synchronous and does not throw under normal operation. The passed field on LintReport indicates whether the prompt passed linting:

  • passed: true -- zero errors were found (warnings and infos do not cause failure)
  • passed: false -- one or more errors were found

To gate a CI pipeline on lint results:

import { lint } from 'prompt-lint';

const report = lint({ source: promptText, preset: 'strict' });

if (!report.passed) {
  console.error(`Lint failed: ${report.summary.errors} error(s) found.`);
  for (const d of report.diagnostics.filter(d => d.severity === 'error')) {
    console.error(`  [${d.ruleId}] line ${d.location.startLine}: ${d.message}`);
  }
  process.exit(1);
}

Diagnostics are sorted by severity (errors first, then warnings, then infos), making it straightforward to process the most critical issues first.


Advanced Usage

Linting OpenAI Message Arrays

import { lint } from 'prompt-lint';

const report = lint({
  source: [
    { role: 'system', content: 'You are a helpful assistant. Try to be concise.' },
    { role: 'user', content: 'Summarize this document.' },
  ],
  preset: 'recommended',
});

// Detected format:
console.log(report.document.format); // 'message-array'

Linting Anthropic Prompts

import { lint } from 'prompt-lint';

const report = lint({
  source: {
    system: 'You are an expert analyst. Be helpful and thorough.',
    messages: [
      { role: 'user', content: 'Analyze the quarterly revenue data.' },
    ],
  },
  preset: 'strict',
});

console.log(report.document.format); // 'anthropic'

Linting Prompts with Template Variables

import { lint } from 'prompt-lint';

const report = lint({
  source: 'Answer {{user_query}} using context from {{knowledge_base}} and {{typo_var}}.',
  variables: {
    user_query: { required: true },
    knowledge_base: { required: true },
  },
});

// The 'undefined-variable' rule will report 'typo_var' as undefined.
// The 'undelimited-variable' rule will flag 'user_query' as undelimited user input.

Security-Only Scanning

import { lint } from 'prompt-lint';

const report = lint({
  source: userProvidedPrompt,
  preset: 'security-only',
});

if (!report.passed) {
  throw new Error('Security issues detected in prompt template.');
}

Building a Custom Preset with off Base

import { lint } from 'prompt-lint';

const report = lint({
  source: '...',
  preset: 'off',
  rules: {
    'prompt-injection-risk': 'error',
    'undelimited-variable': 'error',
    'vague-instruction': 'warning',
  },
});

Inspecting the Parsed Document

import { parse } from 'prompt-lint';

const doc = parse('<system>You are an assistant.</system><user>Hello {{name}}</user>');

console.log(doc.format);     // 'plain-text'
console.log(doc.roles);
// [
//   { role: 'system', content: 'You are an assistant.', location: {...} },
//   { role: 'user', content: 'Hello {{name}}', location: {...} },
// ]

console.log(doc.variables);
// [{ name: 'name', syntax: '{{}}', occurrences: [{...}] }]

console.log(doc.estimatedTokens);
console.log(doc.characterCount);

Parsing Markdown-Headed Prompts

import { parse } from 'prompt-lint';

const doc = parse(`# System
You are an expert code reviewer.

# User
Review the following pull request for security issues.`);

console.log(doc.roles.length);     // 2
console.log(doc.roles[0].role);    // 'system'
console.log(doc.roles[1].role);    // 'user'

Variable Syntax Support

The parser detects four template variable syntaxes:

| Syntax | Pattern | Example | |---|---|---| | Handlebars/Mustache | {{variableName}} | {{user_query}} | | Dollar-brace | ${variableName} | ${user_query} | | Python %-format | %(variableName)s | %(user_query)s | | Single-brace | {variableName} | {user_query} |

import { parse } from 'prompt-lint';

const doc = parse('Process ${input_data} and compare with {{baseline}}.');

console.log(doc.variables);
// [
//   { name: 'input_data', syntax: '${}', occurrences: [...] },
//   { name: 'baseline', syntax: '{{}}', occurrences: [...] },
// ]

TypeScript

prompt-lint is written in TypeScript and ships with full type declarations. All public types are exported from the package root.

import { lint, parse } from 'prompt-lint';
import type {
  Severity,
  RuleCategory,
  PromptMessage,
  AnthropicPrompt,
  PromptSource,
  SourceLocation,
  LintDiagnostic,
  LintSummary,
  PromptDocument,
  LintReport,
  LintOptions,
  RuleContext,
  RuleDefinition,
} from 'prompt-lint';

Type declarations are generated alongside the JavaScript output in the dist/ directory via declaration: true in the TypeScript configuration.


License

MIT