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

urtext

v0.5.2

Published

Diff review with evidence tiers: deterministic analysis, LLM interpretation, every claim labeled

Downloads

83

Readme

Urtext

CI npm OpenSSF Scorecard

A diff reviewer that shows its evidence. Point urtext at a git range and it reports what changed and why it matters — ranked, and with every claim labeled by the kind of evidence behind it. A verified finding can point at the line of code that proves it. Nothing is asserted on the reader's trust alone, and the tool never issues an approve/reject verdict; the judgement stays with the human.

The name is the thesis: in philology, the urtext is the authoritative original from which all editions derive. Here the code is the urtext; a review is an edition of it, and an edition that cannot cite its source is worthless.

What it does

urtext review takes a git range, analyses the change, and prints what matters — ranked, with every claim labeled by the kind of evidence behind it:

  • verified — proven by static analysis; the report points at the code
  • inferred — a model claim that analysis corroborates but does not prove
  • model — a model claim nothing mechanical confirms

It analyses TypeScript projects, including the JavaScript in them. Guards, effects, and citations read .ts, .tsx, .mts, .cts, .js, .mjs, .cjs and .jsx without consulting the project's tsconfig at all. Surface and blast radius read the same TypeScript extensions always, and the JavaScript ones only when the project's own compiler options admit them — both need the type checker, not just a file they can parse on their own. Point urtext at a Python, Go or Rust repository and the code analyzers find nothing; the citations analyzer still checks prose in .md and .txt, and the dependencies and lockfile analyzers still read package.json and package-lock.json — three of the seven. That is a limit, not a roadmap item.

And every review says where that limit fell. A report names the changed files no analyzer reported on — a package.json, a workflow YAML, a SQL migration — so silence about a file is never mistaken for a clean bill of health. It claims non-reporting, not non-reading, and it says plainly that anything the report does say about those files came from the model alone. A report also names any changed file whose shape says a tool wrote it — a bundle, a generated client — and says the same of it: no analyzer reported on it, because code a build produced is not something a person wrote for a reviewer to read.

Seven analyzers run over the change:

  • guards — conditionals, early returns, and throws removed from code that survived
  • surface — exports added, removed, or changed shape
  • blast radius — how many places reference a changed export
  • effects — network, filesystem, process, env, database, and timing effects appearing or disappearing
  • citations — prose that cites code by path:line or by a quoted phrase, where the citation resolved when its line was last written and no longer resolves now
  • dependencies — package.json entries added, removed, or version-changed, in any of the four dependency maps; declared constraints only, since within a range the lockfile decides what actually resolves
  • lockfile — package-lock.json against package.json and against its own previous state: a lockfile the manifest disagrees with, a resolved version that moved, a stale root version, and a count of how the transitive tree changed

Findings are ranked. A verified or inferred finding carries the evidence behind it — file, line, and the quoted source. A model finding carries none by construction, and the report says so where it prints one: it is a lead to check, not a result.

Above the ranked list sits an index of what the change does not describe. When the model marks a finding as unaccounted for by the range's own commit messages, that mark is collected into one short block: "Not described by this change's messages (3)". It predicts nothing and reorders nothing below it — the ranked list stays untouched and complete. Fact-backed entries come first and model-only ones last, which is deliberately not the ranked list's order: that one is ordered for triage, where a claim alleging a problem sorts high; the index is ordered by what a reader can check, where evidence leads.

No entry in it can be verified, and that is structural rather than a policy. The mark is something the model says, and a verified finding is one the model said nothing about, so "marked and verified" is a contradiction. inferred is the index's strongest tier, and when it carries a model-only entry it names the model that produced it.

A run that completes writes an HTML report into .urtext/ at the repository root and prints its path; a run broken enough to exit non-zero deliberately writes none, so a report on disk never stands in for a review that worked. --export md,pdf additionally writes the review as GitHub-flavored Markdown and as a client-presentable PDF beside the HTML report, sharing its name; the same no-report rule applies to every format. urtext does not edit the reviewed repository's .gitignore — it suggests adding .urtext/ when nothing already ignores it, and leaves the file alone.

npm run review                          # working tree vs merge-base with the default branch
npm run review -- HEAD~3                # against a specific revision
npm run review -- --no-llm              # deterministic analysis only; no API key needed
npm run review -- --json                # machine-readable findings
npm run review -- --open                # open the written report
npm run review -- --export md,pdf       # also write Markdown and PDF beside the HTML report
npm run review -- --model claude-opus-5 # pick the interpretation model
npm run review -- --help                # every flag

The interpretation stage needs ANTHROPIC_API_KEY. Without it, urtext reports the analyzers' findings and says in the report that the model was never asked.

That includes the index of findings the commit messages do not account for: it collects marks the model makes, so a keyless run has none and shows no index. The report already says the model was not asked, so the absence is disclosed rather than silent.

Install

npm install -g urtext
urtext review          # from any git repository

The published package ships its build, so nothing compiles on install and no lifecycle script runs — which matters under npm v12, where dependency scripts and git dependencies are both off by default.

Installing from GitHub needs those defaults relaxed. npm install -g github:noahogbi/urtext is refused outright by npm v12 (EALLOWGIT), and even allowed through it would fetch a tree with no dist/, whose build runs from a prepare script that v12 also disables. Use the registry unless you have a reason not to; from a local checkout, npm install -g path/to/urtext still works.

The npm run review form above is the dev loop inside this repository; it runs src/ directly and needs no build.

PDF export embeds the bundled DejaVu fonts, which cover Latin, Cyrillic, and Greek broadly but not CJK or other scripts — full-Unicode fonts cost tens of megabytes.

In CI

The CI integration does not come from npm. npm install urtext gives you the CLI; the GitHub Action is action.yml at the root of noahogbi/urtext, referenced by uses: rather than installed as a dependency. Nothing is installed from the repository under review, and no script of its own is executed. The runner itself needs node, npm, gh and jq, all of which are preinstalled on GitHub-hosted runners; a self-hosted runner has to provide them.

That action is a composite that reviews a pull request and posts the result as one comment, edited in place on every push:

name: urtext review
on: pull_request

jobs:
  review:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0   # base...head needs the merge base, which a shallow clone lacks
      - uses: noahogbi/urtext@v1

@v1 is a moving major tag: it is re-pointed at the default branch with each release, so it does change between runs, deliberately. That is the trade a major tag makes — you get fixes without editing your workflow, and you accept that the code moves. For a reference that cannot change, pin the commit SHA, which is what this repository does for every action it consumes itself. Each move is made from a green cross-platform CI run, the check you can see for yourself here.

Four of the action's behaviors were verified against live pull requests, and it is worth being exact about where that evidence sits: it was recorded during development, in a private repository this one does not descend from, so you cannot follow it from here. What was observed there — the workflow posted one marked comment carrying a real review; a re-run edited that same comment rather than adding a second; a deliberately failed artifact upload left the job green with the comment intact; and the same failure without the upload guard took the job red. Read those as the author's report, not as something this repository lets you check.

The pull_request_target refusal has been observed. It cannot be tested from a branch of this repository — that trigger reads its workflow definition from the base branch — so it was exercised in a throwaway repository carrying the misconfiguration on its default branch. The action failed its guard step and every later step of the composite was skipped: nothing fetched, nothing built, no attacker-authored code parsed. Refusing before doing anything is the property that matters, and it is what the run showed. That evidence was also in a private repository, so it is the author's report on the same terms as the four above.

Worth correcting an earlier version of this paragraph, which said that refusal could not be observed "from a branch" and was read — including by its author — as needing a fork. It does not. pull_request_target fires for pull requests from branches in the same repository too; the only requirement is that the workflow live on the default branch.

The fork case is verified, and it took two runs because no single run could show all of it.

A pull request from a fork — a real one, from another organization — showed that GitHub does hand the workflow a token that cannot comment: the post failed, the action warned in its own words ("the workflow's token may be read-only, which is the case on pull requests from forks"), outcome came back reviewed, posted came back none, and the review survived in the job summary and a 395-byte artifact. That run is red, and the reason is this repository's own choice rather than the action's behavior: the workflow above sets fail-on-error: true, which is the opposite of the default and is set only here, because a urtext failure in urtext's own repository is a defect in the thing under test.

The green half is a second run, on a workflow that simply omits pull-requests: write — the same 403 from the same endpoint on the same path — with fail-on-error left at its default. It stayed green with posted: none, which is what a consumer gets.

So: GitHub's read-only token is real, the handling is what this file says, and the default keeps the job green. Both runs are public and you can read them yourself, unlike the four behaviors above.

The permissions: block sits at the job level, not the workflow level, so adopting this does not widen the token for a repository's other jobs. issues: write is not required — a pull request comment is created through the issue-comments endpoint on a pull request, which the pull-requests scope governs. The block itself is not optional: without pull-requests: write the post comes back HTTP 403, which looks exactly like the fork case below and is a different problem entirely.

By default the review is deterministic: with no key, the action passes --no-llm, and the comment says so in urtext's own words. One line turns the model on:

        with:
          anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}

An undefined secret expands to the empty string, so that line degrades to deterministic on its own rather than failing.

The action never fails a pull request: a review that could not be produced is posted as a comment saying so, not as a red check. Every step that can fail captures its own status, and the one step that cannot — the artifact upload, which is a uses: step with no shell to capture in — is marked continue-on-error. A failed upload therefore costs the comment its full report link and empties the report-artifact-url output, rather than reddening the check. Note what that costs on a large review: the job summary is capped by GitHub at 1 MiB and the artifact is the uncapped copy, so past that size a failed upload loses the only complete copy of the review. fail-on-error: true opts into the opposite.

One exception remains, deliberately, and fail-on-error does not govern it either: the pull_request_target refusal below fails the job on purpose.

Fork pull requests cannot be commented on. On a pull_request run whose head is a fork, GITHUB_TOKEN is read-only regardless of the permissions: block and repository secrets are unavailable, so the post fails with HTTP 403; the action emits a warning, sets posted: none, leaves the full review in the job summary — and in the uploaded artifact when one was uploaded — and stays green. No configuration changes this — a personal access token does not help either, because secrets are not exposed to fork-PR runs. The known-safe pattern is a two-workflow split: a pull_request workflow runs the review and uploads the artifact this action already produces, and a workflow_run workflow — which runs from the base branch with a write token and never checks out head code — downloads it and posts. Shipping that second half is a deliberate follow-up.

pull_request_target is refused. The action fails its first step on that trigger, on purpose: it grants a write token and secrets to a workflow that then reads the head revision, and urtext parses attacker-authored TypeScript.

The job summary carries the full, untruncated review. GitHub caps a job summary at 1 MiB and a larger review is cut by GitHub, not by urtext; the uploaded artifact is the uncapped copy. If a review comment exceeds comment-limit, whole findings are removed and the comment says how many and where the rest are.

A failure comment does not shrink that way. Its headline, reason, closing sentence and footer are fixed copy, so a comment-limit below their combined length produces a comment longer than you asked for — and it is still posted. The action withholds a comment only when the body exceeds what the API itself accepts, which lowering comment-limit cannot cause; that run warns, sets posted: none, and leaves the review in the job summary — and in the artifact when one was uploaded.

Every input is optional:

| Input | What it does | |---|---| | range | The git range to review, passed to urtext review verbatim. Empty derives <base sha>...<head sha> from the pull request payload. | | anthropic-api-key | Key for the interpretation stage, passed to the CLI as an environment variable and never as an argument. Empty runs --no-llm; it never fails. | | model | Passed as --model when non-empty. No effect without anthropic-api-key. | | github-token | The token gh authenticates with. Needs pull-requests: write. | | comment-marker | The hidden marker identifying this action's comment. Change it to keep two independent urtext comments on one pull request. | | comment-limit | Maximum comment body length in characters — the forge's cap carried as data rather than compiled into urtext. | | upload-report | Upload the run's .urtext/ directory and the Markdown review as a build artifact, and link it from the comment. | | artifact-name | The name of that artifact. | | fail-on-error | Exit non-zero when the review could not be produced or could not be posted. |

And the outputs a later step can read:

| Output | Value | |---|---| | outcome | reviewed when urtext produced a review, failed when it did not. | | posted | created, edited, or none. none collapses three causes — a body the API would reject on length, a body that could not be composed, and a post the API refused — and the run's warning names which. | | comment-id | The created or edited comment's id; empty when posted is none. | | comment-url | The comment's html_url; empty when posted is none. | | exit-code | urtext's own exit code, verbatim. The action interprets it; it never rewrites it. | | omitted-findings | How many findings the character cap left out. | | report-artifact-url | The uploaded artifact's URL; empty when upload-report is false, and also when the upload failed, which no longer fails the job. |

Every default lives in action.yml and is deliberately not restated here, so there is one place to read it and one place to change it.

What leaves your machine

Nothing reaches the author of this tool. urtext has no server, no telemetry, and no account. The CLI runs on your machine; the action runs in your own runner, installing from its own checkout.

With --no-llm, nothing leaves at all. Every analyzer is local, every finding is verified, and the review makes no network call.

With a key, what goes to Anthropic goes under your key and your agreement with them — never through anything the author operates. Traced against the prompt builder rather than described from the architecture, it is exactly this:

  • the range label — vs main, or verbatim whatever range you typed;
  • each changed file's path, its status, and the names of the symbols in it that changed;
  • for each analyzer fact: its kind, its file:line, its symbol, and one trimmed source line — the single line the finding points at;
  • the abbreviated hash, subject and body of each commit in the range, used to compare the change against its stated intent, with Co-authored-by: and Signed-off-by: trailers stripped first.

The diff itself is not sent, and neither are the contents of the files it touches. Only the one line per fact above. Git author names and email addresses are not sent either: the intent stage asks git for hash, subject, and body, and never for identity fields.

Nothing is sent when no key is present, and a run that did not ask the model always says so — --no-llm was set or no ANTHROPIC_API_KEY set appears in the review's own notes.

The report is written to .urtext/ in your own repository. In CI it is also attached to the workflow run as an artifact, which your repository's retention settings govern.

Something wrong with a finding? Errors, bad output, or a model claim that should never have been printed: open an issue. A model-tier finding that turned out to be nonsense is a useful report rather than a nuisance — that tier exists because it is expected to be wrong sometimes, and reports are how often gets measured.

Layout

  • src/extract/ — git range → changeset (files, hunks, changed symbols)
  • src/analyze/ — analyzers producing typed facts with source evidence
  • src/interpret/ — the model stage: facts in, labeled claims out
  • src/score/ — importance weights, tier assignment, ranking
  • src/report/ — terminal and HTML renderers, and report writing
  • src/cli.ts — entry point
  • action.yml — the composite GitHub Action that reviews a pull request
  • action/ — the action's comment composer, plain ESM run by the runner's node
  • archive/prototype/ — the klar-era IR prototype, kept for provenance

Design: docs/superpowers/specs/2026-08-15-urtext-diff-review-design.md, and docs/superpowers/README.md for how to read the rest of them — including which citations in them no longer resolve here, and why they were left that way.

Provenance

Built ~March 13, 2026 as a standalone prototype under the working name klar — an AI-native IR the model authored directly. Renamed urtext and first committed to version control August 15, 2026. In August 2026 it was re-aimed at the problem that had become the real bottleneck: reviewing AI-written diffs rather than authoring code in an IR. The prototype lives in archive/prototype/.

Contributing

Bug reports, and especially a review that stated something it could not support, are welcome: open an issue. For a vulnerability, use the private channel in SECURITY.md instead. CONTRIBUTING.md covers the process, what CI enforces, and the test policy.