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

@pugi/plugin-mutation-ledger

v0.1.0-alpha.2

Published

Pugi mutation-ledger plugin - append-only structured event ledger with hash-chain integrity + SQLite index for tool / file / shell mutations.

Readme

@pugi/plugin-mutation-ledger

Append-only structured event ledger with hash-chain integrity, SQLite query index, and verification-gate veto for the Pugi soft fork of Pugi.

Part of the Pugi 1.0 soft fork sprint (see ADR-0081). Replaces the model's narrated "I created the file" / "tests passed" with a tamper-evident JSONL of actual write + execution events. The Phase 1 runtime verification gate (codeforge task #299 / #301) reads this ledger to refuse a session.finish that lacks real proof of success.

Install

pnpm add @pugi/plugin-mutation-ledger better-sqlite3 uuid

Usage

// pugi.config.ts
export default {
  plugin: [
    ['@pugi/plugin-mutation-ledger', {
      // Defaults shown - everything is optional.
      ledgerPath: '.pugi/mutation-ledger.jsonl',
      mirrorDbPath: '.pugi/mutation-ledger.sqlite',
      enableIntegrityHash: true,
      enableSqliteMirror: true,
      vetoOnFailedVerification: false,
    }],
  ],
};

Event schema

Every entry is one JSON line. Discriminated union on type:

| type | payload | |-------------------|---------------------------------------------------------------------------| | file_write | path, bytes, sha256, prevSha256?, mode, diffSummary? | | file_edit | same as file_write with mode='edit' | | file_delete | path, prevSha256 | | shell_exec | command (SCRUBBED), exitCode, durationMs, stdoutBytes, stderrBytes, workingDir | | tool_invocation | toolName, argsHash (sha256 of canonical JSON), resultStatus, durationMs | | session_event | event (created / finish / veto / resumed / aborted), reason? | | verification | check (build / test / lint / typecheck / health_probe), passed, evidence |

Every entry also carries ts (ISO-8601), sessionId, toolUseId?, prevHash (sha256 of the previous entry; empty string for the first), and eventId (UUID v7, time-ordered, monotonically sortable).

Hash chain integrity

The ledger is tamper-evident, not tamper-proof. verifyIntegrity() walks the file from start, recomputes each entry's sha256 over a canonical JSON form (recursively sorted keys), and compares against the next entry's prevHash. Any mismatch surfaces the first break with line number and reason. Defense in depth - off-machine replication - is tracked under mode: 'replicated' (deferred).

Sample tamper detection:

import { verifyIntegrity } from '@pugi/plugin-mutation-ledger';
const result = await verifyIntegrity('.pugi/mutation-ledger.jsonl');
if (!result.ok) {
  console.error(`ledger break at line ${result.brokenAt}: ${result.reason}`);
}

Atomic append

The writer opens the JSONL file with O_APPEND for every entry. On POSIX, write(2) on an O_APPEND fd is per-syscall atomic: the seek + append happen in one operation so concurrent writers from sibling processes cannot tear an entry. Within a single Node process we serialize through a per-file async queue so prevHash is always read consistently before each append. Entry size is capped at maxLineBytes (default 64 KiB) and LineTooLargeError is thrown when an event would exceed it - fail loud beats silent truncation.

SQLite mirror

The JSONL file is the source of truth; the SQLite mirror is a derived index for O(log n) queries. Schema:

CREATE TABLE events (
  event_id   TEXT PRIMARY KEY,  -- UUID v7
  ts         TEXT NOT NULL,
  session_id TEXT NOT NULL,
  tool_use_id TEXT,
  type       TEXT NOT NULL,
  prev_hash  TEXT NOT NULL,
  data       TEXT NOT NULL      -- full JSON
);
CREATE INDEX idx_events_ts      ON events(ts);
CREATE INDEX idx_events_session ON events(session_id);
CREATE INDEX idx_events_type    ON events(type);
CREATE INDEX idx_events_tool    ON events(tool_use_id);

WAL mode is enabled so concurrent readers (the Console mutation timeline, the verification gate) do not block the writer. If the SQLite file becomes corrupt, call rebuildMirror(db, ledgerPath) to drop and replay every row from the JSONL log.

Registered tools

The plugin registers four tools that the model can call:

| Tool | Purpose | |----------------------------|----------------------------------------------------------------------| | ledger_history | Filtered event list (sessionId, type, sinceTs, limit) | | ledger_verify_integrity | Walk the chain, return first break or clean status | | ledger_summary | Per-session counts, total duration, files modified, verifications | | ledger_assert_evidence | Return events supporting a claim (tests_pass / build_success / ...) |

Verification gate integration

When vetoOnFailedVerification: true, the plugin registers a experimental.session.finish veto hook (Day 6 patch C of the Pugi pugi fork). The hook reads ledger evidence and rejects the finish when:

  • Any verification entry with check='build' recorded passed: false.
  • Any verification entry with check='test' recorded passed: false.
  • Zero verification entries exist and reason looks like success.

The veto decision lives as a pure exported function (shouldVetoSessionFinish), so it is testable without depending on the hook firing. The published @pugi-ai/[email protected] runtime ignores the unknown hook key - gracefully degrading rather than blocking sessions for users who are not on the Pugi fork.

import { shouldVetoSessionFinish } from '@pugi/plugin-mutation-ledger';

const decision = await shouldVetoSessionFinish(
  { ledgerPath: '.pugi/mutation-ledger.jsonl', db: null, vetoOnFailedVerification: true },
  { sessionId, reason: 'success' },
);
if (decision.veto) console.error(decision.vetoReason);

Secret scrubbing

Plaintext secrets must never reach the ledger. scrubSecrets() runs over every shell_exec.command and any text-bearing payload before write, replacing matches with [REDACTED]. Default patterns:

| Pattern | Example match | |-------------------------------|--------------------------------------------------| | OAuth bearer | Bearer eyJhbGciOiJIUzI1... | | OpenAI key | sk-..., sk-proj-... | | Anthropic key | sk-ant-... | | Stripe live secret | sk_live_..., rk_live_..., pk_live_... | | GitHub PAT (classic/fine) | ghp_..., gho_..., ghu_..., ghs_..., ghr_... | | Slack bot/user/app token | xoxb-..., xoxp-..., xoxa-..., xoxr-... | | AWS access key ID | AKIA..., ASIA... | | GCP API key | AIza... | | Env-var assignment | AWS_SECRET=..., GITHUB_TOKEN=..., etc. | | Hardcoded password | password = 'hunter2' | | RSA/EC/OpenSSH/PGP key block | -----BEGIN ... PRIVATE KEY----- |

Append your own via secretScrubPatterns: [/CUSTOM-[A-Z0-9]+/g].

Pattern provenance: GitHub secret-scanning documented prefixes, AWS IAM identifier reference, Stripe key format conventions, OpenSSL PEM RFC 7468, historical breach disclosures (e.g. CVE-2022-26134-style env-var leaks).

Threat model

| Threat | Mitigation | |--------------------------------------|----------------------------------------------------------------------------| | Silent corruption of an entry | Hash chain breaks at the tampered index; verifyIntegrity reports line | | Concurrent writer races | O_APPEND single syscall + per-file in-process queue | | Plaintext secret persisted | scrubSecrets() runs BEFORE write; default + custom patterns | | Mirror divergence from JSONL | rebuildMirror() drops + replays every row from JSONL | | Disk full / partial write | Best-effort error swallowed (stderr); tool execution continues | | Model claims "done" without proof | vetoOnFailedVerification rejects finish lacking verification events | | Attacker rewrites whole chain | Out of scope today; mode='replicated' (off-machine relay) deferred |

Hook surface

  • tool.execute.before - capture start time + tool name
  • tool.execute.after - emit tool_invocation + (for file-write tools) file_write/file_edit + (for bash) shell_exec
  • experimental.session.finish - opt-in veto when vetoOnFailedVerification: true (fork-only; published runtime ignores)
  • dispose - close SQLite handle

License

MIT. See LICENSE.