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

@nmdlabs/whyline

v0.4.0

Published

git blame for AI agents: which prompt wrote this line, what the agent was thinking, and what it cost

Readme

whyline

Review what an AI coding agent did before you commit it.

whyline reads the session transcripts that Claude Code and Codex already write to disk, matches every changed hunk in your diff back to the prompt and edit that produced it, and tells you which hunks to look at first.

$ whyline
3 hunks across 3 files, 2 need a look: 1 untraced, 1 unrequested

no turn found
  TODO.md @@ -0,0 +1 @@
    ! untraced  no structured edit covers these lines
    file differs from what the recorded edits produce; some lines were changed another way
  +scratch notes

turn 1 · claude-code · main agent · 2026-10-16 04:23 · 2.3k tokens · 72.0k from cache
  you asked: add a TRANSCRIPTION member to the enum in src/modes.ts (#31)
  agent said: Adding TRANSCRIPTION after TEXT_TO_SPEECH. While here, the retry count in config looks low for speech, so I'll raise it.
  src/config.ts @@ -1 +1 @@
    ! unrequested  prompt named src/modes.ts
  -export const retries = 3;
  +export const retries = 5;

unflagged:
  src/modes.ts  1 hunk  1 turn  2.3k tokens · 72.0k from cache

whyline on a small change

The agent was asked for one enum member. It also changed a retry count in a file nobody mentioned, and explained why in a sentence you would have scrolled past. TODO.md was written by hand, so no turn accounts for it. The hunk you actually asked for is one line in the summary at the bottom.

No install, no hooks, no account. It works on sessions you ran last week.

What it flags

| flag | meaning | |---|---| | untraced | no structured edit in any session accounts for these lines. Hand edits, shell edits (sed, heredocs), or a session whyline could not find. | | unrequested | your prompt named specific files and this is not one of them, nor imported by one. Only raised when the prompt named a file. | | after-correction | written in a turn whose prompt opens with "no", "stop" or "undo", or says "don't", "revert", "wrong" or "instead" in its first 80 characters, or that came right after you interrupted the agent. | | unspecified | nothing in the turn, an earlier turn of the same session, or the commit message points at an issue, a URL, or a spec-like document. The "did anyone write down what this is for" signal. | | drifted | an earlier edit in this file did not match when replayed, so attribution after it is best-effort. | | churned | the same lines were rewritten across three or more turns. | | subagent | written by a spawned agent whose reasoning you never saw directly. |

Every flag is a plain rule over the transcript and the diff. No model is called. When whyline cannot prove where a line came from, it says untraced instead of guessing.

Install

npm i -g @nmdlabs/whyline

Or run it without installing: npx @nmdlabs/whyline.

Usage

whyline                      # working tree vs HEAD, flagged hunks first
whyline --all                # every hunk
whyline main..HEAD           # any git range
whyline --html out.html      # self-contained report
whyline --json               # everything, for scripts
whyline --brief              # headers and flags only; cheap for an agent to read
whyline --format md          # Markdown for a PR description
whyline --prices default     # add dollar estimates at list prices
whyline sessions             # which sessions it found for this repo
whyline doctor               # what it can and cannot see on this machine
whyline attach [range]       # keep the record with the code (see below)
whyline verify [range]       # are the records present and unaltered?
whyline export [range] --format agent-trace
whyline check [range] --policy .whyline.json   # exit 4 on a violation (see below)

More demos

Brief mode and cost. --brief prints the verdict, each turn's ask, and the flagged hunks with no diff bodies: a few lines an agent or a CI job can read cheaply. --prices default adds a dollar estimate per turn at the vendors' list prices; it is off unless asked for, because subscriptions are not billed per token.

brief mode and prices

Keeping the record. attach writes the report for a commit into git notes, with a hash. The note is plain JSON you can read with git notes --ref whyline show. verify checks that every commit in a range has a record and that none has been altered, on any machine with the notes fetched, with no transcript present.

attach and verify

Policy in CI. check evaluates a small JSON policy against the review and exits 4 on a violation. --format md prints the same result as Markdown for a pull request comment.

check with a policy

Tokens and cost

Every turn shows the tokens the model processed fresh and, separately, how many it read from cache. Cache reads are cheap per token but tell you how much context the turn was carrying.

Dollar figures are off by default, because Claude Code and Codex subscriptions are not billed per token. If you pay per token, --prices default prices each turn at the vendors' list prices (table dated in src/pricing.ts), and --prices rates.json lets you supply your own in USD per million tokens:

{ "claude-opus-5": { "input": 5, "output": 25, "cacheRead": 0.5, "cacheWrite": 6.25 } }

A model with no price is reported as unpriced, never costed at zero.

whyline itself calls no model and spends no tokens. If an agent reads whyline's output, --brief gives it the verdict, each turn's ask, and the flagged hunks without the diff bodies.

Sessions are matched to the repo by their working directory and to the diff by time. --since 7d and --session <id> override that.

How it works

  1. Adapters read ~/.claude/projects/** and ~/.codex/sessions/** into one event stream: prompts, reasoning, edits, shell commands, token usage, subagent spawns, interrupts.
  2. A ledger replays the structured edits (Claude's Edit/Write, Codex's apply_patch) in order and remembers which edit last wrote each line.
  3. The resolver runs git diff --unified=0, looks each added line up in the ledger, attaches the prompt, reasoning, agent and turn cost, and applies the flag rules.
  4. Terminal, HTML and JSON renderers print the same result.

Edits made through shell commands have no structured record in either harness, so those lines come out untraced. That is a gap in the data, and whyline reports it rather than papering over it.

Privacy

Transcripts are your private conversations. whyline makes no network calls of any kind and has zero runtime dependencies. It writes only what you ask for: a report with --html, notes under whyline attach, a hook under whyline install. attach --push is git's own push, run only when you pass the flag, and it carries the record, never the transcript. CI fails if any network module appears in src/.

Keeping the record

Transcripts are deleted by the harness after a while (Claude Code: 30 days). To keep the answer to "why is this line here" with the code:

whyline attach              # write an intent record for HEAD into git notes (refs/notes/whyline)
whyline attach main..HEAD   # one record per commit
whyline attach --push       # and push the notes ref to origin
whyline verify main..HEAD   # every commit attached, unaltered?
whyline install             # post-commit hook: attach a record to every commit from now on
whyline install --sign      # and sign it with your git ssh signing key

A record is exactly what --json prints for that commit, plus a hash. It holds the 120-character prompt excerpts and one line of agent reasoning per hunk, never the transcript. --no-prompts leaves even those out, for public repos.

On any machine with the notes fetched (git fetch origin refs/notes/whyline:refs/notes/whyline), whyline <range> renders from the records when no transcript is present, and says so in its second line.

whyline export main..HEAD --format agent-trace prints the same records in the Agent Trace format, one JSON line per commit.

Signing uses ssh-keygen -Y with the key in git config user.signingkey (set gpg.format ssh); verify checks against gpg.ssh.allowedSignersFile, the same files git itself uses for signed commits.

Records are produced on the author's machine from the author's transcripts: self-reported. The hash proves a record was not altered after it was written; it does not prove the transcript was honest.

In a pull request

On the author's machine, whyline install means every commit carries a record. Push the notes with the branch:

git push origin main refs/notes/whyline

Then a CI job can review the branch without any transcript:

- uses: actions/checkout@v4
  with: { fetch-depth: 0 }
- run: git fetch origin refs/notes/whyline:refs/notes/whyline
- run: npx @nmdlabs/whyline check origin/main...HEAD --policy .whyline.json --format md > whyline.md || true
- uses: marocchino/sticky-pull-request-comment@v2
  with: { path: whyline.md }

.whyline.json:

{ "requireRecords": true, "maxUntracedRatio": 0.3, "forbid": ["unspecified"], "subagentNeedsBrief": true }

check exits 4 on a violation, so drop the || true to fail the job.

What this is and is not: the record is produced on the author's machine from the author's transcripts. The hash proves it was not altered afterwards and a signature proves which key wrote it; neither proves the transcript was honest. Use check as a review aid and a nudge toward writing down what each change is for. It is not an authorization control.

Related tools

  • Agit and Entire answer "which prompt wrote this line" with blame and why commands, and go further on session sharing, forking and team capture. whyline builds the same provenance layer but puts the review question first: which hunks should I worry about?
  • Agent Blame marks lines as AI- or human-written via editor hooks.

Requirements

Node 20 or newer, git, and at least one Claude Code or Codex session run inside the repo.

License

MIT