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

v0.2.0

Published

Hashline-compatible read/edit tools for Pi coding agent.

Readme

pi-hashline

Hashline-compatible read/edit replacement tools for Pi coding agent. Imports the hashline text DSL: a compact, line-anchored patch language for safe file editing.

Why this exists

Agentic coding often fails because context goes stale: line numbers drift, exact text snippets are duplicated, or another edit changes the file between read and edit. This package adds a lightweight optimistic-concurrency check to file edits:

  1. read returns [path#TAG] + N:content lines (hashline format).
  2. The model sends back hashline-format diff text to edit.
  3. edit re-reads the file and verifies the snapshot tag still matches.
  4. Any mismatch forces a fresh read, preventing silent corruption.

Install

pi install npm:pi-hashline

Or install from GitHub:

pi install git:github.com/YanwuZeng/pi-hashline

Read output

read returns every editable text line in hashline format:

[src/file.ts#A1B2]
1:const value = 1;
2:function greet() {
3:  return "hello";
4:}
  • [src/file.ts#A1B2] — file-section header with a 4-hex content-hash tag (xxHash32 of the whole file).
  • 1:const value = 1; — 1-based line number + colon + content.
  • Every edit must include the header tag for version binding.

Edit format (hashline DSL)

Edits use a compact text DSL instead of JSON:

[src/file.ts#A1B2]
SWAP 2.=2:
+  console.log("Hello, " + userName);

Operations

| Op | Example | Description | |----|---------|-------------| | SWAP N.=M: | SWAP 2.=2: | Replace lines N through M with body rows | | SWAP.BLK N: | SWAP.BLK 1: | Replace the whole brace-delimited block beginning on line N (TS/JS/Java/C/C++/Go/Rust/C#) | | DEL N.=M | DEL 4 | Delete lines N through M (no body) | | DEL.BLK N | DEL.BLK 1 | Delete the whole brace-delimited block beginning on line N | | INS.PRE N: | INS.PRE 1: | Insert body rows before line N | | INS.POST N: | INS.POST 3: | Insert body rows after line N | | INS.BLK.POST N: | INS.BLK.POST 1: | Insert body rows after the end of the block beginning on line N | | INS.HEAD: | INS.HEAD: | Insert body rows at file start | | INS.TAIL: | INS.TAIL: | Insert body rows at file end |

Body rows are prefixed with +:

[file.ts#A1B2]
SWAP 2.=3:
+  const result = a + b;
+  return result;

Examples

Single-line replace (patch):

[greet.ts#A1B2]
SWAP 2.=2:
+  console.log("Hello, " + userName);

Delete a line:

[service.ts#C3D4]
DEL 4

Insert before a line:

[app.ts#E5F6]
INS.PRE 1:
+import { something } from "./util";

Insert after a line:

[schema.ts#G7H8]
INS.POST 2:
+  email: '[email protected]',

Multiple operations in one edit:

[package.json#I9J0]
SWAP 3.=3:
+  "version": "2.0.0",
SWAP 4.=4:
+  "author": "team"

Safety rules

  • Every edit must include a [path#TAG] header from the latest read.
  • Numbers refer to the ORIGINAL file; never shift as hunks apply.
  • Every applied edit mints a fresh #TAG — anchor the next edit on the response or a fresh read.
  • Touch only lines your latest read literally displayed.
  • On stale-tag rejection: STOP and re-read before further edits.
  • One hunk per range; body = final content, never an old/new pair.
  • Ranges cover ONLY lines whose content changes. Never widen over unchanged lines.
  • Body rows must be +TEXT — never write -old or bare context lines.

The .BLK ops resolve the syntactic block with a built-in brace-matching scanner (strings, template literals, comments, and regex literals are skipped so their braces don't corrupt the match). If a block can't be resolved confidently — e.g. the anchor sits inside a block rather than on an opener, or the language is indent-based (Python) — the edit fails with a clear "use a concrete line range" error instead of guessing.

Robustness

The edit tool runs every patch through a single hardened pipeline:

  • All-or-nothing batches. Multi-file diffs are prefetched in memory; if any section fails to prepare (hash mismatch, unseen lines, parse error, unresolved block), no file is written.
  • Seen-lines check. An edit anchored on a line the model never displayed (a partial read, a folded summary, or memory) is rejected — re-read first.
  • Stale-tag recovery. If the file drifted between read and edit, the edit is replayed against the cached snapshot and 3-way-merged onto the live file with fuzzFactor: 0 (never slides onto a duplicate closer). Recovery failure forces a re-read.
  • HEAD/TAIL drift exemption. INS.HEAD:/INS.TAIL: are position-stable, so a stale tag is non-fatal — they apply with a warning.
  • Self-healing apply. A SWAP whose body restates the unchanged structural closer just past the range has the duplicate dropped automatically.
  • Noop-loop guard. Repeated byte-identical no-ops on the same file/payload hard-fail after 3 attempts, breaking model fixation loops.
  • Fresh tag per edit. Every successful edit returns a new [path#TAG] so the next edit re-grounds on current line numbers.

TUI behavior

  • read shows a 10-line preview by default; Ctrl+O expands the full result.
  • Raw read output is capped at 400 lines or 32 KiB by default; use offset/limit to continue.
  • Successful edit shows a colored diff in a green edit block.
  • Failed edit shows the error message inside a red edit block.

Command

/hash-edit-status

Shows whether the extension is loaded and which hash length is active.

Development

npm install
npm test

Project layout:

  • index.ts registers the extension.
  • src/read.ts — hashline-format read tool.
  • src/edit.ts — hashline-DSL edit tool.
  • src/patcher.ts — the hardened edit engine (prepare/commit, all-or-nothing).
  • src/input.ts — multi-file [path#TAG] section splitting.
  • src/apply.ts — line-anchored apply + self-healing boundary repair.
  • src/block-resolver.ts — brace-matching BlockResolver for .BLK ops.
  • src/recovery.ts — stale-tag 3-way merge recovery.
  • src/snapshots.ts — in-memory SnapshotStore (content hash + seen lines).
  • src/noop-loop-guard.ts — fixation-loop breaker.
  • src/parser.ts — hashline text DSL parser (tokenizer + state machine).
  • src/format.ts — hashline format constants and helpers.
  • prompts/ — tool prompt guidelines for the LLM.
  • test/ — Node test cases and manual-test scenarios.

Acknowledgments

This project is based on Fadouse/pi-hash-anchored-edit and adopts the hashline syntax from can1357/oh-my-pi (packages/hashline).

Changelog

0.2.0 (2026-06-30)

Single hardened pipeline (replaces the dual inline/library code paths).

The live edit tool no longer re-implements parsing, hash validation, recovery, block resolution, apply, and snapshotting inline. It now routes every patch through the tested Patcher engine (src/patcher.ts), so the runtime path and the integration-tested path are the same code.

  1. P0 — Single pipeline via Patcher. src/edit.ts shrank from ~200 lines of inline logic to a thin wrapper: Patch.parse → Patcher.apply → format result. Fixes the multi-file bug where a second [path#TAG] section's edits were silently applied to the first file.
  2. P1 — read double-record fix. snapshotStore.record was called twice per read and details.fileHash carried a Snapshot object instead of the hash string. Both fixed.
  3. P2 — Working block ops. A built-in brace-matching BlockResolver (src/block-resolver.ts, strings/template-literals/comments/regex-aware) is now wired in, so SWAP.BLK/DEL.BLK/INS.BLK.POST actually resolve blocks instead of always throwing. Unresolvable anchors bail to a clear "use a concrete line range" error.
  4. P3 — All-or-nothing batches. Patcher.apply now prepares every section in memory before any disk write; if any section fails to prepare, no file is touched.
  5. P4 — Noop-loop guard. src/noop-loop-guard.ts hard-fails after 3 consecutive byte-identical no-ops on the same file/payload, breaking model fixation loops.
  6. P5 — Recovery version-chain walk. recovery.recover now iterates all historical snapshots (via SnapshotStore.versions) instead of fetching the same version and stopping. fuzzFactor: 0 is preserved so a stale tag never slides a hunk onto a duplicate closer.
  7. P6 — Self-healing apply. applyEdits drops a trailing body row that exactly duplicates the unchanged structural closer just past a SWAP range (conservative: exact match + closer only).
  8. P0 (HEAD/TAIL drift). INS.HEAD:/INS.TAIL: now apply with a warning when the tag is stale, since head/tail positions are content-independent.
  9. P7 — Doc cleanup. Removed the stale test/test-scenarios.md and test/prompt-workflow-scenarios.md (described the defunct op=/LINE#HASH/dryRun API) and rewrote test/manual-tests/README.md to match the current DSL and the real auto-verify.test.ts runner.

New tests: test/robustness.test.ts adds 28 cases covering the block resolver, noop guard, recovery walk, all-or-nothing, HEAD/TAIL drift, self-healing, and the live edit tool end-to-end (single-file, multi-file, SWAP.BLK, no-op, stale-tag mismatch, path override). All 71 tests pass (43 existing + 28 new).

0.1.5 (2026-06-29)

Bug fixes & robustness improvements:

  1. Fix: #TAG stripped from file path — edit no longer treats the 4-hex snapshot tag as part of the filename. The tag is properly extracted from [path#TAG] for hash validation, while the real file path is used for filesystem access.

  2. Fix: Line-number drift with multiple hunks — applyEdits now groups edits into atomic operations and processes them bottom-up (descending line number). This prevents earlier edits from shifting the line positions that later edits depend on.

  3. Fix: INS.POST content duplication — Multiple payload lines inserted after the same anchor are now batched into a single splice call.

  4. Improved diff output — buildCompactDiffPreview compares line-by-line and only shows lines that actually changed (- old / + new). Unchanged lines are omitted.

New regression tests (11 cases):

  • Multi-hunk SWAP line-drift verification
  • INS.POST with multiple payload lines (no duplication check)
  • SWAP range shrinking/expanding
  • Mixed SWAP + INS.POST + DEL operations
  • INS.POST landing-shift with structural closers
  • SWAP ranges containing closing braces
  • buildCompactDiffPreview correctness
  • Multiple interleaved operations on nearby lines

All 34 tests pass (23 original + 11 regression).

License

MIT