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

@diabolicallabs/prompt-registry

v0.1.0

Published

Versioned LLM prompt lifecycle — admin-standard prompt_versions pattern as a package. Seed from repo files, publish new versions, roll back, audit history. © Diabolical Labs

Readme

@diabolicallabs/prompt-registry

Versioned LLM prompt lifecycle. Implements the fleet admin standard (§S7) as a package: seed() from repo files, get() the active or a specific version, publish() a new version, history(), rollback(). Storage is adapter-based; PostgresPromptStorageAdapter is the shipped reference. © Diabolical Labs

Canonical source: /Users/mann/Documents/Claude/admin-standard.md (ratified 2026-07-06) — this package implements §S7 ("LLM prompt storage — prompts live in the DB, versioned"). If the standard and this README disagree, the standard wins; file a PR to reconcile.

Install

pnpm add @diabolicallabs/prompt-registry
# pg is an optional peerDependency — install if you don't already have it
pnpm add pg

Usage

import { Pool } from 'pg';
import {
  createPromptRegistry,
  PostgresPromptStorageAdapter,
  loadSeedFilesFromDirectory,
} from '@diabolicallabs/prompt-registry';

// Bring your own pool — the registry never opens its own connection.
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const adapter = new PostgresPromptStorageAdapter(pool);
await adapter.ensureSchema(); // idempotent CREATE TABLE IF NOT EXISTS

const registry = createPromptRegistry({ adapter });

// Seed step (deploy time) — repo files are the source of truth for v1.
const entries = await loadSeedFilesFromDirectory('./constants/prompts');
await registry.seed(entries); // no-op after the first run

// Runtime read
const prompt = await registry.get('onboarding'); // type defaults to 'system', version defaults to active
const systemPrompt = prompt.content;

// Admin publish (new version, activates immediately, old version untouched)
await registry.publish('onboarding', updatedText, {
  createdBy: 'diana',
  changeNotes: 'tightened tone per Reid review',
});

// Roll back to a prior version without creating a new row
await registry.rollback('onboarding', 3);

// Full version history, newest first
const versions = await registry.history('onboarding');

Multiple prompt types (SYSTEM/USER)

await registry.publish('interview', systemText, { type: 'system' });
await registry.publish('interview', userTemplateText, { type: 'user' });

const system = await registry.get('interview', { type: 'system' });
const user = await registry.get('interview', { type: 'user' });

Seed file format

.md files with YAML-ish frontmatter — name required, type optional (defaults to system):

---
name: onboarding
type: system
---
You are a helpful onboarding assistant...

API

createPromptRegistry(config): PromptRegistry

| Config field | Type | Description | |---|---|---| | adapter | PromptStorageAdapter | Required. PostgresPromptStorageAdapter or a custom implementation. |

PromptRegistry

| Method | Signature | Description | |---|---|---| | seed | (entries: SeedPromptEntry[]) => Promise<SeedResult[]> | Idempotent — inserts v1 (active) only for names with no existing version. Safe to call on every deploy. | | get | (name, options?: { type?, version?: number \| 'latest' }) => Promise<PromptRecord> | Defaults: type: 'system', version: 'latest' (the active row). A numeric version returns that row regardless of active state. Throws PromptNotFoundError. | | publish | (name, content, meta?: { type?, createdBy?, changeNotes? }) => Promise<PromptRecord> | Always inserts a new version and activates it; never overwrites a prior row. | | history | (name, options?: { type? }) => Promise<PromptRecord[]> | All versions, newest first. Empty array if never seeded/published. | | rollback | (name, version, options?: { type? }) => Promise<PromptRecord> | Re-activates a prior version in place (content untouched, no new row). Throws PromptNotFoundError if the version doesn't exist. |

PromptRecord

interface PromptRecord {
  id: number | string;
  name: string;
  type: string;
  version: number;
  content: string;
  isActive: boolean;
  activatedOn: Date | null;
  createdBy: string | null;
  changeNotes: string | null;
  createdOn: Date;
}

Sensitivity masking

Per admin-standard §S6 (is_sensitive masking), prompt bodies are maskable for lower-trust display contexts — audit-log list views, Slack/webhook notification payloads:

import { maskPromptBody, redactConnectionString } from '@diabolicallabs/prompt-registry';

maskPromptBody(record.content);          // preview mode (default): first 60 chars + byte count
maskPromptBody(record.content, 'full');  // fully redacted, byte count only
maskPromptBody(record.content, 'hash');  // sha256 fingerprint only — for diff-changed checks

redactConnectionString('postgres://admin:[email protected]:5432/prod');
// -> 'postgres://***:***@db.internal:5432/prod'

The package's own internal logging never includes raw content or connection secrets in any log call — asserted by src/__tests__/unit/logging-security.test.ts, which spies on every log call across the full seed → get → publish → history → rollback lifecycle and asserts a sentinel prompt body never appears in logged output.

CI eval-gate

Gate a prompt change on an eval script's exit code — wire into your CI before a publish() call, or as a pre-merge check on a PR that touches a seed file:

import { runPromptEvalGate } from '@diabolicallabs/prompt-registry';

// Throws PromptEvalGateFailedError on non-zero exit — un-caught, this fails the CI step.
await runPromptEvalGate({
  promptPath: './constants/prompts/onboarding.md',
  evalScriptPath: './scripts/eval-onboarding-prompt.mjs',
});

Or as a shell step via the bundled CLI:

npx prompt-registry-eval-gate ./constants/prompts/onboarding.md ./scripts/eval-onboarding-prompt.mjs

The eval script receives the prompt path as argv[2] and the PROMPT_FILE env var; its exit code is the verdict. This package does not prescribe what the script checks — golden-output diffing, a judge-model rubric, a regex smoke test are all valid. scripts/eval-gate-demo.mjs demonstrates both directions (pass and fail) against fixture prompts/scripts and runs as part of this package's own CI.

PromptStorageAdapter interface

interface PromptStorageAdapter {
  ensureSchema(): Promise<void>;
  insertVersion(input: InsertPromptVersionInput): Promise<PromptRecord>;
  getVersion(name: string, type: string, version: number): Promise<PromptRecord | null>;
  getActiveVersion(name: string, type: string): Promise<PromptRecord | null>;
  listVersions(name: string, type: string): Promise<PromptRecord[]>;
  getMaxVersion(name: string, type: string): Promise<number>;
  activateVersion(name: string, type: string, version: number): Promise<PromptRecord>;
}

PostgresPromptStorageAdapter is the shipped implementation. It takes a structural PgPoolLike (query + connect) — any real pg.Pool instance satisfies it, so you pass your existing pool rather than the registry opening a new connection. All queries are parameterized ($1, $2, ...) — no string interpolation into SQL anywhere in the adapter. insertVersion() and activateVersion() each run inside a single checked-out-client transaction (BEGIN/COMMIT/ROLLBACK) so "insert new version" + "deactivate the old active row" (or "deactivate + activate") never observably interleave with a concurrent reader.

A non-Postgres backend implements the same interface — the interface is the extension point, not a plugin registry.

Migration guide: converting an existing per-product implementation

This section shows the actual diff shape for migrating a product that already has its own prompt_versions implementation (the pre-package pattern from admin-standard §S7) onto this package. FitCheckerApp (/Users/mann/Documents/GitHub/FitCheckerApp) was used as the reference — admin-standard identifies it as the fleet's cleanest prompt_versions schema (db/schema/prompt-versions.ts, lib/prompts.ts, scripts/seed-prompt-versions.ts). This is a worked example, not a completed migration — no product is migrated in this PR (see "Out of scope" below); a real migration is its own per-product follow-up brief.

Before — FitCheckerApp's hand-rolled implementation

db/schema/prompt-versions.ts (Drizzle):

export const promptVersions = pgTable("prompt_versions", {
  id: uuid("id").defaultRandom().primaryKey(),
  promptName: varchar("prompt_name", { length: 100 }).notNull(),
  promptType: varchar("prompt_type", { length: 10 }).notNull(),
  version: integer("version").notNull(),
  content: text("content").notNull(),
  llmModelId: uuid("llm_model_id").references(() => llmModels.id),
  isActive: boolean("is_active").notNull().default(false),
  activatedOn: timestamp("activated_on"),
  createdBy: uuid("created_by").references(() => users.id),
  createdOn: timestamp("created_on").defaultNow().notNull(),
  changeNotes: text("change_notes"),
}, (table) => [
  unique("prompt_versions_prompt_name_prompt_type_version_key")
    .on(table.promptName, table.promptType, table.version),
]);

lib/prompts.ts (custom cache + fallback logic, ~165 lines):

export async function getPrompt(prompt_name: string, prompt_type: string): Promise<Prompt | null> {
  const normalizedName = prompt_name.trim().toUpperCase();
  const normalizedType = prompt_type.trim().toUpperCase();
  try {
    const cacheKey = `prompt:${normalizedName}:${normalizedType}`;
    const dbPrompt = await withCache(cacheKey, PROMPT_CACHE_TTL, async () => {
      const { getActivePromptVersion, getPromptModelOverride } =
        await import("@/lib/repositories/prompt.repository");
      const active = await getActivePromptVersion(normalizedName, normalizedType);
      // ...
    });
    if (dbPrompt) return { /* hand-built Prompt shape */ };
  } catch {
    // DB unavailable — fall through to filesystem
  }
  // ...filesystem fallback via loadPrompts()...
}

scripts/seed-prompt-versions.ts (~114 lines, hand-rolled pg.Pool + manual INSERT ... WHERE NOT EXISTS idempotency check).

After — with @diabolicallabs/prompt-registry

// lib/prompt-registry.ts — replaces db/schema/prompt-versions.ts's manual
// table definition (ensureSchema() owns the DDL) and lib/prompts.ts's
// getPrompt()/loadPrompts() (registry.get() replaces both).
import { Pool } from 'pg';
import { createPromptRegistry, PostgresPromptStorageAdapter } from '@diabolicallabs/prompt-registry';

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
export const promptRegistry = createPromptRegistry({
  adapter: new PostgresPromptStorageAdapter(pool),
});
// scripts/seed-prompts.ts — replaces scripts/seed-prompt-versions.ts (114 lines -> ~10)
import { loadSeedFilesFromDirectory } from '@diabolicallabs/prompt-registry';
import { promptRegistry } from '../lib/prompt-registry';

const entries = await loadSeedFilesFromDirectory('./constants/prompts');
const results = await promptRegistry.seed(entries);
console.log(results); // [{ name, type, status: 'seeded' | 'skipped_existing', version }]
// call sites — getPrompt(name, type) becomes registry.get(name, { type })
- const prompt = await getPrompt("ONBOARDING", "SYSTEM");
- const text = prompt?.prompt ?? FALLBACK_TEXT;
+ const prompt = await promptRegistry.get('onboarding', { type: 'system' });
+ const text = prompt.content;

What changes, what doesn't

| Aspect | FitCheckerApp before | With prompt-registry | |---|---|---| | Schema | Hand-written Drizzle table, product owns migration | ensureSchema() (or hand-author an equivalent migration from schema.sql if you already use Drizzle migrations) | | Idempotent seed | 114-line script, manual WHERE NOT EXISTS | loadSeedFilesFromDirectory() + seed(), ~10 lines | | Read path | getPrompt() + withCache() + filesystem fallback, ~165 lines | registry.get(name, options) — no built-in cache (see Performance note below); filesystem fallback is your call, not the package's | | Rollback | Not implemented in FitCheckerApp today | registry.rollback(name, version) | | Concurrency safety | Not explicitly handled | PromptVersionConflictError on a version-number race (UNIQUE constraint backstop) | | Frontmatter format | name/type fields, same as this package | Unchanged — loadSeedFilesFromDirectory() reads the exact same .md files FitCheckerApp already has in constants/prompts/ |

Not carried over on purpose: FitCheckerApp's withCache() 10-minute TTL and filesystem fallback are product-specific request-path optimizations, not part of the admin-standard contract. A migrating product keeps its own cache layer in front of registry.get() — see the Performance note in manifest.yaml.

Testing

pnpm test              # unit suite, mocked/in-memory adapters
pnpm test:integration   # requires DATABASE_URL — see src/__tests__/integration/postgres.test.ts header
pnpm demo:eval-gate     # eval-gate fixture demonstration (requires `pnpm build` first)