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

henyo-pi-core

v0.10.3

Published

Pi coding agent extension - henyo core functionality

Readme

henyo-pi-core

Pi coding agent extensions for long-horizon agentic SWE using local models.

Installation

On first load, the extension seeds ~/.pi/agent/AGENTS.md — it copies SAMPLE_GLOBAL_AGENTS.md to your pi config directory if the file doesn't already exist, providing default guidelines for new users.

Structure

henyo-pi-core/
├── package.json          # Extension manifest with pi entry point
├── .gitignore
├── LICENSE               # MIT License
├── pnpm-workspace.yaml   # pnpm workspace root (allowBuilds)
├── README.md
├── SAMPLE_GLOBAL_AGENTS.md  # Default AGENTS.md template (seeded on first load)
├── eslint.config.mjs     # ESLint flat config (style rules + Prettier integration)
├── .prettierrc.json      # Prettier configuration
├── .prettierignore       # Files to exclude from formatting
├── tsconfig.json         # TypeScript compiler options
├── vitest.config.ts      # Vitest test runner config
├── index.ts              # Re-export for pi extension loading
├── skills/               # Bundled pi skills
│   ├── plans/  # Structured plan generation for multi-step tasks
│   └── notes/            # Ephemeral working notes for tracking context and decisions
├── src/
│   ├── index.ts          # Extension factory (registers commands, tools, events)
│   ├── henyo-settings.ts # henyo settings block: types, defaults, merge, effective-state reader
│   ├── ttft-tokps.ts     # Working line: TTFT + live/exact tok/s display (config-gated)
│   ├── footer.ts         # Compact footer: name•model(level)•ctx%•path(branch)
│   ├── settings-io.ts    # Shared settings.json path + read helper (tolerates missing/invalid file)
│   ├── tool-repair.ts    # Re-export shim (legacy path; no logic — see tool-repair/)
│   ├── tool-repair/      # Standalone tool repair (event hooks: repair, coaching, prompt guideline)
│   │   ├── index.ts        # Public API barrel
│   │   ├── hooks.ts        # Extension wiring: message_end / tool_result / before_agent_start
│   │   ├── rules.ts        # The five args-repair rules (hoist, stringified, salvage, garbled, incomplete)
│   │   ├── fingerprint.ts  # Fingerprints + emission classification (edit-location, shape, sha12)
│   │   ├── telemetry.ts    # Log records, rewrite→result correlation, content-error upgrade
│   │   └── coach.ts        # Coaching + prompt-guideline constants
│   ├── edit-fallback.ts  # Fuzzy/nearest-match edit fallback: pure matching + report core (no pi imports)
│   ├── compaction-retry.ts # Compaction summary retry guard: strict plain-text prompt, up to 3 targeted retries
│   └── commands/         # Custom slash commands
│       ├── cwd.ts        # /cwd: switch project directory (new session in target dir)
│       ├── newp.ts       # /newp: start a new session with an initial prompt
│       └── henyo.ts      # /henyo: list or toggle all henyo features
├── tooling/              # Dependency-free maintenance scripts (not shipped with the npm package)
│   └── plan-record-lint.mjs # line-level plan-record linter (R1-R4 checkbox/note/hash checks)
└── test/
    ├── footer.test.ts    # Unit tests for footer layout and status line
    ├── tool-repair/            # Module tests (mirror src/tool-repair/)
    │   ├── hooks.test.ts       # Hook wiring: message_end / tool_result / before_agent_start + telemetry
    │   ├── rules.test.ts       # The five args-repair rules
    │   ├── fingerprint.test.ts # Fingerprint + emission classification
    │   └── telemetry.test.ts   # Telemetry: rememberRewrite cap, applyEditFallback guards, classifyContentError null paths
    └── tool-repair/
        ├── edit-fallback-hooks.test.ts   # Hook wiring: rewrite, pending telemetry, coaching, guards
        ├── fingerprint.test.ts
        ├── hooks.test.ts
        ├── rules.test.ts
        └── telemetry.test.ts
    ├── edit-fallback.test.ts # Unit tests for the pure matching/candidate/duplicate core
    ├── index.test.ts     # Entry-point tests: settings fill-write, footer attach, re-render
    ├── load-henyo-settings.test.ts # henyo settings block: merge, fill writes, steady state
    ├── ttft-tokps.test.ts          # Working line: v2 harness scenarios + trace on/off/rotation/contract
    ├── compaction-retry.test.ts    # Compaction retry guard: success, 4 failure retries, exhaustion, abort, no-ops, contract
    ├── fixtures/         # Test fixtures (recorded model failure payloads)
    │   └── edit-failure-payloads.json
    ├── commands/         # Unit tests for command handlers
    │   ├── cwd.test.ts
    │   ├── henyo.test.ts
    │   └── newp.test.ts

Custom Footer

A compact footer renders one packed line:

myproj•qwen3.8-27b(xhi)•42%/84k•~/pi/proj(main)
  • Session name (bright) is prepended as name• only when the session has a name; it is never truncated
  • Model segment is model plus a thinking-level suffix for reasoning models: a 3-char level ((low) / (med) / (xhi)) when the model's compat declares supportsReasoningEffort, else (on) / (off); no suffix for non-reasoning models
  • Context usage is NN%/usedk, or ?/windowk when unknown; color-coded: yellow 50–80%, red ≥81%
  • Path is shown from the right as space allows — last segment bright, prefix dim — with the git branch glued in parens: path(branch)
  • All segments are joined by • (no spaces); non-bright content is dimmed
  • Truncation: when space is tight, the path and branch are truncated from the right while the left block (name • model • context) stays intact
  • Extension statuses appear on a second line (dim, keys sorted) only when an extension registers them — the footer is one line by default

Registered Commands

/cwd [path]

Switch to another project directory and start a new session in the target dir.

  • With no args: shows the current working directory.
  • With a path argument: creates a session file in the target's session directory with the correct CWD in its header, then switches to it. The file is deleted after switching so empty sessions don't pollute /resume — pi persists to the correct location on the user's first message.

/newp <prompt>

Start a new session with an initial prompt. The prompt is sent as the first user message in the new session.

/henyo [key [value]]

List or toggle all henyo features from the TUI — the replacement for hand-editing settings.json:

  • With no args: opens a picker of all 11 keys labeled key: on / key: off (state from the effective merged settings); pick one to toggle it.
  • /henyo <key> flips the key's current effective state.
  • /henyo <key> <value> sets the key explicitly; values are on off true false enable disable (case-insensitive).
  • Keys are given in canonical form (toolRepair, footer, agentsMd, ttftTokps, trace, compactionRetry, skills.notes, commands.cwd) or, for the dotted keys, in their flat shorthand (notes, plans, cwd, newp) (ttftTokps/trace are top-level — no shorthand). Tab-completion is offered for both keys and values.
  • footer applies live in the current session; all other keys are written and applied after an automatic extension reload (same semantics as /reload — the success toast says — reloading).
  • /henyo is always available — it is intentionally not one of the commands.* settings keys, so it can never be gated behind a setting that would need it to re-enable itself.
  • Non-TUI mode (hasUI: false): toggles via explicit args still work (write + reload); the bare picker form is a no-op.

Bundled Skills

/skill:plans

A structured methodology for producing plans that an agent can execute without human clarification. Every plan is a checklist: read, check off steps, commit, verify. Produces plans with measurable acceptance criteria, scope boundaries, dependency ordering, and per-step verification. Use whenever a plan is requested or when a task involves multiple steps, file changes, or dependencies.

Workflow: Plan → Execute → Verify

/skill:notes

A structured approach for creating ephemeral working notes during development sessions. Notes capture context, decisions, and next steps — they are not permanent artifacts. Notes are stored in ~/.pi/agent/notes/ and deleted once an implementation plan exists.

When to use: Capturing transient information, tracking decisions, recording blockers, documenting context before a plan is written.

Bundled Extensions

Tool repair

Some models (observed: Qwen 3.6) emit broken edit arguments: path nested inside edits[0] instead of the top level, the whole edits array stringified as JSON, edits truncated mid-JSON, or entries missing keys — the call fails validation. henyo-pi-core repairs these and coaches on any tool's schema failures, with three event hooks — no tool overrides, so it coexists with other repair layers:

  • Repair — a message_end hook rewrites the assistant message's edit calls before execution, applying up to five rules in order: a stringified edits value ("edits": "[...]") is parsed back into an array (parse-stringified-edits); edits[0].path is hoisted to the top-level path and removed from the edit objects (extract-path); edits strings that fail strict parsing are salvaged — cut where the model degenerated into its next emission, raw control chars escaped, missing string/array closers appended — only when a root path exists and the result has a complete oldText/newText entry (salvage-corrupt-edits); a garbled path key nested inside an entry (e.g. path>) is moved to the top level and deleted from the entries (recover-garbled-path); and entries missing a string oldText or newText are dropped when at least one entry is complete (drop-incomplete-edits). Several rules can fire on one call and the fix is logged with the rules that fired. History side effect: repaired calls appear in the session history in corrected form, not in their original shape.
  • Edit fallback — a second message_end stage for edit calls that survived the shape rules but would still fail on whitespace drift: when the built-in exact match would fail (the oldText is absent from the file), the fallback looks for the one region whose whitespace-normalized lines match the oldText exactly (1:1, unique). On a unique match the oldText is rewritten to the file's exact bytes (raw lines, CRLF preserved, final-line terminator included; leading BOM excluded) and the edit proceeds; the file is read once per call, and a 1 MB UTF-8 size guard skips huge files. Everything else is report-only, never applied: ambiguous or multi-match drift, a ratio-0.8 nearest-match report with the top candidate line ranges, or duplicate-occurrence line lists appended to the error. edits[].oldText === newText, empty oldText, and lone-\r (old-Mac) files are left to the built-in behavior unchanged.
  • Coaching — when a tool call fails, a one-line Henyo note: hint is appended to the error the model sees. edit content-mismatch failures — the dominant class: text not found, text not unique, overlapping edits, or a no-op edit (oldText and newText identical) — each get a category-specific line, and the edit-fallback stage upgrades three of them when it can say more: not-found errors with a strong nearest match (ratio ≥ 0.8, within the 0.03 gap filter) gain a candidates report — top candidate line ranges and ratios — which replaces the plain hint, and not-found errors whose best match sits between 0.6 and 0.8 gain a near-miss hint pointing at the nearest region. The plain not-found hint also carries a prohibition: do not re-emit an oldText that has already failed — it will fail again. Not-unique errors gain the exact occurrence line numbers. Overlap and no-op failures keep the plain one-line hint. edit shape-validation failures (both pi signatures: Validation failed for tool "X" and the older Invalid input for tool "X") get the specific "path goes at the top level, next to edits" line, and every other tool a generic schema hint. On Tool X not found (hallucinated tool name) the hint instead lists the available tool names.
  • Prompt guideline — four lines are appended to the system prompt: put path at the top level next to edits; read the file immediately before edit so edits[].oldText is copied verbatim from a fresh read; if an edit or write call reports success, trust that result — do not re-read the file to "verify" it because of stray characters in the transcript echo of your own call (the tool result is authoritative; the echo is a display artifact); and edit existing files only via the edit tool, never bash sed/awk or a full-file write. Each line is idempotent — skipped when already present, so a mid-session prompt upgrade picks up whichever ones are missing.

Active by default; no configuration needed. The edit-fallback stage is gated by the editFallback key (default true) — with it off (or with toolRepair off) the hooks resolve byte-identical to the pre-fallback behavior: no file reads, no rewrites, plain one-line coaching only.

Extended 2026-09-02 after the session-failure analysis (77% content mismatch / ~15% structural for the served Qwen models). Extended 2026-09-04 with the edit-fallback stage (unique 1:1 whitespace-drift rewrite + candidate/duplicate coaching). Extended 2026-09-19 — trust-result + no-bypass prompt lines, anti-verbatim-retry coaching, retriedVerbatim telemetry (7-day audit: 28 verbatim retries, 163 multi-line checkbox failures, \r-phantom self-doubt after successful results).

Safety model: the rewrite fires only when the built-in exact match would fail (so it never shadows a successful edit), only on a unique 1:1 whitespace-normalized match, and only when oldText !== newText; the rewritten oldText is the file's exact bytes at that region, so the resulting edit is the one the model intended, applied at the region the built-in itself would accept. Anything ambiguous is reported (line ranges / occurrence lines) instead of applied. Telemetry tracks the outcome per call (fixed → applied / failed) so every auto-rewrite is auditable after the fact.

Upgrade caveat: the exact-match predicate is a byte-level port of pi's internal dist/core/tools/edit-diff.js (pi 0.84.2). After upgrading pi, re-run pnpm test — if pi's matching semantics change, the port and its "built-in would fail" guard must be re-derived before the fallback stays sound.

Log file: telemetry outcomes are appended as JSONL to ~/.pi/agent/tool-repair.jsonl (non-edit successes are not logged — the ok denominator is edit-only). Record shape: { ts, tool, model, outcome, rules?, issues?, fingerprint, category?, emission?, retriedVerbatim?, recoveredBy?, afterMs? } — outcome is fixed (a repair rule or an edit-fallback rewrite applied), ok (denominator — every successful edit tool result), applied (an edit-fallback rewrite was confirmed by the subsequent successful tool result), recovered (a previously failed edit on the same file succeeded — carries the original failure's fingerprint and issues, plus recoveredBy (the successful call's toolCallId) and afterMs (failure→recovery time in ms)), or failed (validation actually failed — issues carries a shape diagnostic, a content-mismatch category, or unknown-tool for hallucinated tool names). Content-mismatch failed records carry category (the original built-in error category) so an upgraded issues subcategory stays traceable to its origin category — issues !== category is the upgrade signal, and a category that doesn't match the issues prefix is the mislabel signal. failed records may also carry retriedVerbatim: true — a failure with the same fingerprint is still open on the same file and no successful read of that file happened since it, i.e. the model re-emitted an already-failed attempt verbatim (file-scoped; failures without a resolvable path are not tracked). Validation-class failed records may carry emission: truncated (args cut off mid-payload — G3), glued (multiple object emissions concatenated into one args value — G5), or shape-quirk (any other unparseable shape) — so the truncation/glue gaps are measurable from the log alone. Rewrite records add lineRange ({ startLine, endLine }), fileLines, oldTextLines, editIndex (for multi-edit calls), and sha12 (first 12 hex of SHA-256 of the original oldText — argument values are never logged). Fingerprint: edit events use the location fingerprint (hash of the path basename + a normalized oldText prefix — an irreversible hash, values never logged); non-edit events keep the shape fingerprint (hash of the sorted top-level argument keys). Content-mismatch issues can carry a subcategory after a colon: content-not-found:candidates, content-not-found:no-match, content-not-unique:listed, too-large.

Working Line (TTFT + tok/s)

While generating, the Working... line shows time to first token (TTFT), a live tok/s rate, and the token span, e.g. Working... TTFT 1.00s · ≈34.00 tok/s · 44 tok/1.19s:

  • Live rate — estimated tokens (delta character count ÷ learned per-model chars-per-token ratio) ÷ elapsed seconds. When the provider reports usage.output mid-stream the rate becomes exact (same ≈ readout, usage-based); at message_end the working line is restored immediately — the exact per-call rate stays in the trace log
  • Stall handling — no delta of any kind for 1.5 s → the line holds its last readout and appends …; generation resuming or the message ending restores the normal readout
  • Turn summary toast — at agent_end a single info toast reports the aggregated turn stats: first-call TTFT, total output tokens, tok/s over the sum of per-call generation spans (tool execution and gaps excluded), turn wall clock, and LLM call count, e.g. TTFT 1.00s · 42.10 tok/s · 643 tok · 18.72s (4 calls). The rate is omitted when the summed span is below 200 ms; aborted/errored calls are excluded; non-TUI modes (no UI) get no toast
  • Calibration — per-model chars-per-token ratios (think/text/tool) are learned online (EMA on in-range samples) and persisted to ~/.pi/agent/extensions/.ttft-tokps-state.json; a neutral bias (clamped [0.5, 2.0]) further corrects the estimate against exact usage, keyed per model in the same file

Gated by ttftTokps (default true).

Trace file: with trace: true, every display decision is appended as JSONL to <os.tmpdir()>/ttft-debug.log (default; override with logFile) — each line carries the event payload and the exact working message that was displayed, so live-vs-final estimate error is auditable straight from the log. Writes are size-rotated (.1, .2, .3 backups by default — 10 MiB cap, 3 backups) and silent on failure (a broken log never breaks the TUI). Off by default — enable it via /henyo trace on to investigate rate discrepancies.

Legacy note: the original standalone ~/.pi/agent/extensions/ttft-tokps.ts pre-dates this port. After the live /reload verification (plan step 7.3) it will be deleted; until then disable it there (or delete it) so the two don't render the working line twice.

jq -r .outcome ~/.pi/agent/tool-repair.jsonl | sort | uniq -c

Compaction Retry Guard

Some models (observed: qwen3.8-27b via apollo-8002) "narrate" tool calls in the tool-free summarization request. Pi treats any toolCall block in the summary response as a hard failure ("Summarization attempted to call a tool"), so auto-compaction dies and the session eventually runs out of context. The guard takes over summary generation on session_before_compact:

  • Strict plain-text system prompt — the summary request forbids tool calls, JSON tool blocks, and conversation continuation; reasoning is off (saves output budget, fewer narration artifacts)
  • Targeted retries — up to 3 attempts; each failure class (tool call emitted / empty text / truncated response / API error) gets a targeted repair note appended to the next attempt's prompt
  • Clean format — the same structured checkpoint format pi's built-in summarization uses (including the previous summary when one exists), so future compactions merge cleanly
  • Safe fallback — if it can't get clean text after 3 attempts it falls back to pi's default compaction (with an error notify)

Gated by compactionRetry (default false).

Legacy note: the original standalone ~/.pi/agent/extensions/compaction-retry.ts pre-dates this port. It is superseded — remove it when the core feature is enabled so the two don't double-register session_before_compact.

Settings

All henyo-pi-core features can be individually enabled or disabled via a henyo block in ~/.pi/agent/settings.json. The block is created automatically on first install and extended when the extension adds new features — only missing keys are added (with their defaults); keys you have set are never modified. Absent or unknown keys behave as enabled, so a partial block is always safe.

Feature Toggles

| Key | Type | Default | Description | |-----|------|---------|-------------| | toolRepair | boolean | true | Edit path repair + stringified-edits fix + all-tools validation coaching + unknown-tool hint + prompt guideline | | editFallback | boolean | true | Fuzzy/nearest-match edit fallback: unique 1:1 whitespace-drift rewrite + candidate/duplicate coaching on content-mismatch errors | | footer | boolean | true | Render compact footer (name•model(level)•ctx%•path(branch) + conditional status line) | | agentsMd | boolean | true | Copy SAMPLE_GLOBAL_AGENTS.md to ~/.pi/agent/AGENTS.md on first session (if it does not already exist) | | ttftTokps | boolean | true | Working line with TTFT + live tok/s during streaming; info toast with TTFT, tok/s, tokens, and wall clock at turn end | | trace | boolean | false | JSONL trace of every ttftTokps display decision (incl. the exact displayed string), size-rotated | | compactionRetry | boolean | false | Compaction summary retry guard: strict plain-text prompt + up to 3 targeted retries; falls back to pi's default compaction | | skills.<name> | boolean | true | Enable/disable individual bundled skills | | commands.<name> | boolean | true | Enable/disable individual custom commands |

Nested Keys

Skills:

| Key | Default | Description | |-----|---------|-------------| | plans | true | Structured plan generation for multi-step tasks | | notes | true | Ephemeral working notes for tracking context and decisions |

Commands:

| Key | Default | Description | |-----|---------|-------------| | cwd | true | /cwd — switch project directory | | newp | true | /newp — start a new session with an initial prompt |

Toggling individual features from the TUI is covered by /henyo (see Registered Commands) — it is intentionally absent from the commands.* keys. Stale entries left under commands in an existing settings.json for a retired command (the old footer toggle) are inert: unknown keys are preserved on settings writes and ignored by the extension.

Example Configuration

{
  "henyo": {
    "toolRepair": true,
    "editFallback": true,
    "footer": true,
    "agentsMd": true,
    "ttftTokps": true,
    "trace": false,
    "compactionRetry": true,
    "skills": {
      "plans": true,
      "notes": false
    },
    "commands": {
      "cwd": true,
      "newp": false
    }
  }
}

To disable only the notes skill:

{
  "henyo": {
    "skills": {
      "notes": false
    }
  }
}

To disable all henyo features:

{
  "henyo": {
    "toolRepair": false,
    "editFallback": false,
    "footer": false,
    "agentsMd": false,
    "ttftTokps": false,
    "trace": false,
    "compactionRetry": false,
    "skills": {
      "plans": false,
      "notes": false
    },
    "commands": {
      "cwd": false,
      "newp": false
    }
  }
}

Note: When the henyo block is absent from settings.json, all features remain enabled (default behavior unchanged).

Development

Prerequisites

  • Node.js 22+
  • pnpm — activated via corepack: corepack enable pnpm (if the default install dir is read-only, target a writable PATH dir: corepack enable pnpm --install-directory ~/.local/bin)
  • Globally installed Pi coding agent (npm install -g @earendil-works/pi-coding-agent)

Available Scripts

pnpm dev                    # Run pi with this extension loaded (pi -e ./index.ts)
pnpm test                   # Run Vitest unit tests
pnpm run lint               # Type-check (tsc) + ESLint style checks
pnpm run lint:fix           # Auto-fix ESLint issues
pnpm run format             # Check Prettier formatting
pnpm run format:fix         # Auto-format with Prettier
pnpm run build              # TypeScript type-check build
pnpm run test:coverage      # Run tests with coverage report (90% thresholds)

Architecture

The extension follows a factory pattern — src/index.ts exports a default function that receives the Pi ExtensionAPI and registers all commands, tools, and event handlers. This function is invoked by pi when loading the extension.

Command registration: Each command lives in its own file under src/commands/, exporting a function that accepts the API instance and calls pi.registerCommand().

Session management: The /cwd command creates a minimal session file (.jsonl) in the target's session directory with the CWD in its header, then calls ctx.switchSession(). The file is deleted in withSession so empty sessions don't pollute /resume — pi persists to the correct location when the user sends their first message.

Testing

Tests use Vitest with mocked pi-coding-agent internals. Each test file covers its corresponding command handler, including edge cases for error conditions and cancellation scenarios. Coverage thresholds are set at 90%.

API Reference

See Pi Extensions Docs for the extension API reference.