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-zvec

v0.4.0

Published

Local semantic code search for pi via zvec-grep (zg): hybrid BM25 + embedding retrieval over a per-project index. The zg_search tool is exposed to the model only while the index is ready; /zg:status reports state + environment diagnostics in one framed di

Downloads

671

Readme

pi-zvec

Local semantic code search for pi, powered by zvec-grep (zg).

Hybrid BM25 + embedding retrieval over a per-project index, maintained by zg and exposed to pi as one LLM tool plus lifecycle commands. Fully local: local Potion embedding models run on CPU; no remote-authorization path is ever touched.

Why

ffgrep/rg find exact strings. They can't answer "where do we handle retry backoff?" when you don't know the identifier — that's grep thrash: repeated empty searches, growing context, wasted tokens. zg_search complements exact search with ranked, line-cited retrieval: concept in, evidence out, in one tool call.

Prerequisites

npm install -g @zvec/zvec-grep   # Node >= 22

The first index downloads the small potion-code-16m-v2 embedding model (~32 MB) to ~/.zvec-grep/models (respects ZVEC_GREP_HOME). Index state lives in .zvec-grep/ under your project root.

You do not have to install it yourself: pi-zvec detects a missing zg and offers to run the install (see Environment readiness).

Known install snag. @zvec/zvec-grep pulls onnxruntime-node, whose postinstall script picks a CUDA-specific build and can die with Failed to detect CUDA version ... Unsupported CUDA version: 13 followed by connect ECONNREFUSED 127.0.0.1:443. This is the native binding download, not the CLI. npm install -g @zvec/zvec-grep --ignore-scripts installs a working zg for local CPU embeddings. pi-zvec surfaces the raw npm log rather than hiding it.

Install

pi install /absolute/path/to/pi-zvec    # local
pi install git:github.com/kslamph/pi-zg # once published

or copy/symlink index.ts into ~/.pi/agent/extensions/ / .pi/extensions/.

What you get

zg_search tool (for the model) — visible only when the index is ready. Natural-language search over the project index. Ranked, compact, line-cited snippets. The tool description teaches routing: concept questions → zg_search; exact identifiers/paths/regexes → ffgrep/rg. It also tells the model the call is cheap (local, sub-second), so it reaches for it before a third guess-and-check exact search rather than after. Every result passes through the evidence gate first.

The tool is registered always but activated only when zg status --check-ready passes. An unindexed project costs the model nothing: no schema, no Available tools line, no guideline bullet — and no coaching hint pointing at a tool it cannot call. When a background index finishes mid-session, the tool is revealed at the next agent_settled boundary, never mid-request (activating a tool rebuilds the system prompt and can invalidate the provider's cached prefix).

Commands (for you)

| Command | What it does | |---|---| | /zg:index | Diagnose + repair, then build/update the index: /zg:index once, /zg:index auto, --rebuild. Installs zg, clears a project opt-out, and gitignores the index before creating it | | /zg:status | Read-only, framed report: project state on top (tool visibility, mode, last indexed, embedding, coaching), environment diagnostics below (node, binary, launch proof, model cache, index dir, gitignore), plus the next step if it isn't ready | | /zg:query | Search from the prompt: /zg:query how are callbacks validated [-t ts] [-g src/**] [-n 10] | | /zg:rg | Managed ripgrep (no index needed): /zg:rg -i -C 2 pattern path | | /zg:drop | Delete the project's index (confirm first) and hide the tool | | /zg:disable | Stop pi-zvec in this project — hides the tool, no nudges, and never asks again | | /zg:enable | Re-enable pi-zvec for this project after an opt-out |

Index lifecycle: once vs auto

indexMode in .pi/zvec.json decides what happens at session start when the project has no index:

| Mode | Behaviour | |---|---| | ask (default) | One dialog: index once, auto-index, ask later, or never for this project | | manual | Never index on its own — just report that /zg:index is available | | auto | Build/update in the background at session start, no prompt |

{
	"indexMode": "auto",
	"disabled": false,
	"coach": true,
	"verify": true,
	"gitignore": true
}

Note that --refresh wait on every query already keeps an index current for freshly saved files, so auto is about first build / rebuild after drop, not about per-edit freshness. disabled is orthogonal: it opts the project out of pi-zvec entirely, keeps the tool out of the prompt, and suppresses the setup offer — choosing "Never for this project" sticks across sessions.

Index hygiene

pi-zvec creates .zvec-grep/, so it owns keeping it out of git. The entry is added to the repository root's .gitignore before zg index runs, not offered afterwards:

  • A cache dir that exists but is not ignored shows up in git status the moment it appears; a post-build prompt leaves a multi-minute window where git add -A commits it.
  • Doing it up front means background and auto builds are covered too. They must never raise a dialog, and this needs none — it is a one-line, idempotent append, reported through a notification rather than a question.
  • Outside a git repository it is a no-op: pi-zvec never creates a stray .gitignore in an untracked tree.
  • The authoritative check asks git (git check-ignore), so a rule you already cover via a global core.excludesFile, .git/info/exclude, or a parent .gitignore is respected and no redundant line is written. A cheap in-process text scan gates that subprocess, and is deliberately kept no looser than git's own whitespace rules — a scanner that accepted .zvec-grep/ (leading spaces are significant to git) would skip a repair that is actually needed.
  • Set "gitignore": false to manage .gitignore yourself.

Environment readiness

pi-zvec walks a ladder, cheapest check first, and names the first unmet layer instead of failing with an opaque exit code:

node >= 22 → zg on PATH → zg actually launches → model cache → .zvec-grep/ → index ready

Session start uses only the in-process layers (PATH scan, stat) plus one zg status when an index dir already exists, so an unindexed project pays no subprocess at startup. When zg is missing, pi-zvec asks before installing and then runs npm install -g @zvec/zvec-grep using the npm beside the running node binary (so an nvm switch cannot install into a prefix pi is not using), re-probes with zg --version, and continues into indexing. Nothing installs without a yes, and nothing installs at all in non-interactive modes.

Grep-thrash coaching — after 3 consecutive near-empty rg/grep/ffgrep searches, one bounded hint suggests zg_search for concept queries. Strictly rate-limited: max 2 per session, 5-minute cooldown, never twice in a row, only for search-shaped commands (rg exit 1 = no matches; path typos are ignored), and only while zg_search is actually callable.

Evidence gate (lexical verification)

The problem this solves. zvec-grep fuses its FTS and vector routes with Reciprocal Rank Fusion (score = Σ 1/(60 + rank)) and cuts at a positional limit. RRF is purely ordinal, so the top hit scores ~0.032 whether its cosine similarity was 0.92 or 0.03 — and a query for something the repo does not contain still fills to limit with confident, line-cited hits. Measured here: a genuinely relevant query scored 0.0320, while "quantum chromodynamics lattice QCD gluon field tensor" scored 0.0323. matchedBy=fts+vector is not a confidence signal either — both routes always return topk documents, so it is set for any query. zg discards the raw cosine in its recall layer, so pi-zvec cannot simply read a real score.

What the gate does instead, using only local, cheap signals: tokenizes the query into distinctive terms (stopwords, generic verbs, and structural words like field/value/data removed), reads the actual text of each cited range (zg prints only an anchor line), and does one bounded in-process scan of the workspace for those terms.

| verdict | when | what the model sees | |---|---|---| | none | no query term occurs anywhere in the workspace | hits suppressed; explicit no-match + "confirm with ffgrep" | | weak | terms exist, but too few appear in the returned hits | hits kept, warning before them, rarest terms to hunt | | ok | a real share of the terms is found in the hit text | hits + one ✓ lexical support line |

Cost: 3–4 ms cold, 1–2 ms cached (60 s TTL per cwd+terms), against zg's own 1–2 s. Set "verify": false to see zg's raw positional ranking untouched.

Honest limits. This is lexical, not semantic — it cannot catch polysemy, so "rate limiting" still looks supported in a repo whose only rate limiting is a UI hint throttle. It reports the terms it did not find, which is the most the model can act on. And absence is only claimed when the scan finished: a scan that hits its file/byte budget says "not seen before the scan budget ran out" instead of "absent", and can never produce none.

Privacy / security

  • Retrieval is local: local embedding model, CPU device, direct (no-daemon) mode.
  • The evidence gate reads file contents in-process to check the query's terms (bounded: 5 000 files / 12 MB / 2 MB per file). It is the same data the index already holds, and nothing is sent anywhere.
  • Remote embeddings are never configured; zg auth is never touched. zg's local-first guardrails remain in force regardless.
  • /zg:rg runs through a whitelist of rg flags; no output-format rewrites, no shell interpolation. Flags that execute programs (--pre) or rewrite output (--json, -r) are rejected.
  • The only network access in pi-zvec is the one you approve: an npm install -g and zg's first model download. Indexing itself is local CPU inference.
  • The index (.zvec-grep/) and settings (.pi/zvec.json) live in your project directory; pi-zvec adds the index to your repository's .gitignore before building it.
  • The only file pi-zvec ever writes inside your repo (besides the index itself) is that one .gitignore line, and only when the directory is a git repository. Opt out with "gitignore": false.

Architecture

index.ts   extension entry: tool, commands, readiness-gated activation, coaching
zg.ts      zg CLI wrapper (direct mode, typed errors: noIndex / missingBinary)
env.ts     readiness ladder + consent-gated repair (PATH scan, launch proof, npm i)
coach.ts   thrash state machine (pure, testable)
relevance.ts evidence gate: tokenizer, hit parser, corpus scan, verdict (pure core)
state.ts   per-project settings and indexMode migration (.pi/zvec.json)
render.ts  TUI text dialog for command output
smoke.ts   functional tests: `node smoke.ts` (live tier auto-skips without `zg`)
test/      pi-server resolve stub, so the pi-dependent tiers can actually run

Every zg call runs --mode direct with --refresh wait, so results see freshly saved files without a daemon — except while an index build is in flight, where queries drop to --refresh off rather than blocking on the writer. Errors are typed: "no index" and "binary missing" are detected precisely and surface targeted messages instead of stack traces. Detached work (background index, npm install) survives session replacement: every UI side effect in those paths is stale-context-safe, because a throw inside a stdout stream callback would otherwise kill the host process.

Tests

node smoke.ts                 # pure tier always runs; live tier needs `zg`
PI_ZVEC_SMOKE_LIVE=0 node smoke.ts   # pure tier only

# Full coverage: also runs the pi-dependent tiers (state migration, prompt
# parsing, session-start decision, index hygiene). Plain `node smoke.ts` skips
# them, because pi-coding-agent's public entry statically imports
# @earendil-works/pi-server, which is not installed with it.
node --import ./test/register-pi-server-stub.mjs smoke.ts

The live tier builds a scratch workspace, queries it, drops the index, and asserts the post-drop noIndex path; it skips (not fails) when the binary is absent, so the suite is runnable on an unready machine — the exact case this extension has to handle well. A missing peer dependency skips; any other load error fails the run, because reporting a broken module as "skipped" is how the index.ts tier went unnoticed while it rejected Node's strip-only type loader.

The scratch fixtures use real git init repositories, so .gitignore behavior is verified against git itself rather than against our own text scan.

The evidence gate is tested both ways: pure-tier cases for the tokenizer, metadata-stripping hit parser, stem matcher, bounded scan and verdict; and a live-tier end-to-end that builds a real index, confirms zg still pads a "quantum chromodynamics…" query to the limit, and asserts the gate verdicts it none while a genuine "payment retry backoff" query comes back ok.

Scope discipline

pi-zvec does one thing: semantic search over a workspace index. Related but separate capabilities (memory/docs recall, multi-workspace search) belong in their own extensions — install what you need.

License

MIT