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

pi-hashline-edit-pro

v0.8.1

Published

Strict hashline read/replace tool for pi-coding-agent with hash-anchored edits (3-char, 18-bit, perfect hashing)

Downloads

2,528

Readme

pi-hashline-edit-pro

A pi-coding-agent extension that replaces the built-in read and edit tools with a hash-anchored line-replacing workflow. Strict semantics, no silent relocation, no autocorrection, no fuzzy fallback. 3-character content hashes over a 64-character URL-safe base64 alphabet give 18 bits of entropy per anchor, with perfect hashing (collision resolution) ensuring every line gets a unique anchor.

Fork of pi-hashline-edit by RimuruW. The strict-semantics policy is unchanged. This fork extends the upstream design in two ways: a 3-character hash length with a 64-character alphabet for more entropy, and perfect hashing (collision resolution) that ensures every line in a file gets a unique anchor.

Every line returned by read carries a short content hash. Edits reference those hashes instead of raw text, so the tool can detect stale context and reject outdated changes before they reach the file.

Why fork?

The original uses 2-character hashes of a 16-character alphabet, with the hash being a pure function of line content. That's 8 bits / 256 buckets, and two byte-identical lines (e.g. repeated import statements, repeated }) always share a hash because the hash is xxHash32(content).

This fork makes two changes that compound:

  1. Bump hash length to 3 characters of the 64-char URL-safe base64 alphabet. That gives 18 bits / 262,144 buckets, up from 8 bits / 256 in the upstream.
  2. Perfect hashing (collision resolution). When computing hashes for a file, if a line's base hash collides with an already-assigned hash, the hash is incremented (using a retry counter: R{retry}) until a unique hash is found. This ensures every line in a file gets a unique anchor, even with the shorter 3-character hash space. Two byte-identical lines (e.g. repeated import statements, repeated }) get different hashes automatically.

Installation

From npm:

pi install npm:pi-hashline-edit-pro

From a local checkout:

pi install /path/to/pi-hashline-edit-pro

How It Works

read -- tagged line output

Text files are returned with a HASH│content prefix on every line. The line number is not part of the wire format, only the 3-character hash followed by the separator and the line content. Example output for the source below:

function hello() {
  console.log("world");
}

would be returned as:

0qH│function hello() {
szJ│  console.log("world");
_zl│}
  • HASH is a 3-character content hash from the URL-safe base64 alphabet A-Za-z0-9-_ (e.g. aB3).

Optional parameters:

  • offset -- start reading from this line number (1-indexed).
  • limit -- maximum number of lines to return.

Images (JPEG, PNG, GIF, WebP) are passed through as attachments and do not participate in the hashline protocol. Binary and directory paths are rejected with a descriptive error. Empty files return an advisory suggesting using replace to insert content.

replace -- hash-anchored modifications

Replaces using the HASH│content anchors from read output to target lines precisely:

{
  "path": "src/main.ts",
  "edits": [
    { "start": "ve7", "end": "ve7", "lines": ["  console.log('hashline');"] }
  ]
}

| Field | Purpose | |---|---| | start | Range-start anchor (required). | | end | Range-end anchor (required). Single-line replace: set start = end. | | lines | Replacement content (one string per line). Use [] to delete. |

  • Request structure validation. The request envelope (path, edits, returnMode, returnRanges) and individual edit items are validated before any file I/O. Unknown fields, missing required fields, invalid types, and malformed anchors are rejected with [E_BAD_SHAPE] or [E_BAD_OP].
  • Legacy dialect rejected. The native top-level oldText/newText (and old_text/new_text) dialect is rejected with [E_LEGACY_SHAPE]. The error message tells the model to call read first and send {start:"<HASH>", end:"<HASH>", lines:[...]}.

All edits in a single call validate against the same pre-edit snapshot and apply bottom-up, so line numbers stay consistent across operations.

Chained edits

After a successful replace, the result text contains an --- Anchors --- block with fresh HASH│content references for the changed region. These can be used directly in the next replace call on the same file without a full re-read, provided the next replace targets the same or nearby lines. For distant changes, use read first.

Auto-read after write

Auto-read is disabled by default. When enabled, after a successful write the extension automatically reads the file and appends a --- Auto-read (hashline anchors) --- block to the result. This gives the model immediate HASH│content anchors for the newly written file without requiring a separate read call. The workflow becomes:

  1. write a file, result includes hashline anchors
  2. replace using those anchors directly

Toggle at runtime with the /toggle-auto-read command. The current state persists for the session.

For large files (>2000 lines), the auto-read output is truncated with a pagination hint. Use read with offset to see more.

Diff for the host

The post-edit diff (with +/- markers and new HASH│content anchors) is exposed to the host UI via details.diff. It is intentionally not in the LLM-visible text. The model only needs the fresh anchors in text to chain follow-up edits, and re-emitting the diff would cost extra tokens.

Design Decisions

  • Stale anchors fail. A hash mismatch means the file has changed since the last read. The error tells the model to call read() to get fresh anchors, then copy the 3-character HASH from each line into the next replace call.
  • No fallback relocation. Mismatched anchors are never silently relocated to a "close enough" line. This trades convenience for correctness.
  • Strict patch content. If lines contains +HASH│ display prefixes (or -N diff rows), the edit is rejected with [E_INVALID_PATCH]. Bare HASH│ content (the first 4 chars of a lines entry looking like 3 base64 chars + ) is also rejected with [E_BARE_HASH_PREFIX]. When the suspect's prefix happens to match a real file-line anchor, the error message flags that as strong evidence the model copied an anchor from the read output.
  • Legacy dialect rejected. The native top-level oldText/newText (and old_text/new_text) dialect is rejected with [E_LEGACY_SHAPE]. The error message tells the model to call read first and send {start:"<HASH>", end:"<HASH>", lines:[...]}.
  • Atomic writes. Files are written via temp-file-then-rename to avoid corruption from interrupted writes. Symlink chains are resolved so the target file is updated without replacing the symlink. Hard-linked files are updated in place to preserve the shared inode. File permissions are preserved across atomic renames.
  • Per-file mutation queue. Edits queue by the canonical write target, so concurrent edits through different symlink paths still serialize onto the same underlying file.

Hashing

Hashes are computed with xxhash-wasm (xxHash32 via WebAssembly), then mapped to a 3-character string from the URL-safe base64 alphabet A-Za-z0-9-_. That's 64 distinct characters, 6 bits per position, 18 bits of entropy per anchor.

The alphabet is sized for an LLM consumer. The model tokenizes, it doesn't squint at pixel glyphs, so the human-readability heuristics used by smaller hand-curated alphabets (no G/L/I/O because they look like digits, no vowels so the hash doesn't accidentally spell a word, no hex digits so it can't be confused with 0xFF) don't apply. The full 64 chars give maximum entropy per character, with case and digits included.

Perfect hashing (collision resolution): When computing hashes for a file, if a line's base hash collides with an already-assigned hash, the hash is incremented (using a retry counter: R{retry}) until a unique hash is found. This ensures every line in a file gets a unique anchor, even with the shorter 3-character hash space. Two byte-identical lines (e.g. repeated } or repeated import statements) get different hashes automatically.

The runtime always precomputes the full per-line hash array for a file via computeLineHashes(content), then looks up by line number during validation and during read / replace response formatting. There is no per-line recomputation that could disagree with what the model saw in its last read.

HASH_LENGTH and HASH_ALPHABET are constants at the top of src/hashline/hash.ts; bump the length to 4 if you need even more entropy without collision resolution.

Bare-prefix detector

With the delimiter format, the bare-prefix detector regex ^\s*[A-Za-z0-9_\-]{3}│ is highly specific. It only matches lines starting with exactly 3 base64 chars and . This eliminates false positives from common code patterns like init:, data:, else:, etc. The detector rejects edit lines matching this pattern with [E_BARE_HASH_PREFIX] to prevent the model from accidentally pasting hash anchors into file content.

Development

Requires Node.js and npm.

npm install
npm test

Set PI_HASHLINE_DEBUG=1 to show an "active" notification at session start.

Set PI_HASHLINE_AUTO_READ=1 to enable auto-read after write by default (can still be toggled at runtime with /toggle-auto-read).

Credits

  • RimuruW -- original pi-hashline-edit and the strict-semantics policy
  • can1357 -- original oh-my-pi implementation and the hashline concept

License

MIT