@mash/mdreview
v0.2.0
Published
Full-document Markdown review tool — difit-style review loop for a whole Markdown file, returns human comments to an AI agent.
Downloads
213
Readme
mdreview
A full-document Markdown review tool. It opens a browser UI showing your whole Markdown file, lets a human comment on any heading, paragraph, or line range, and returns those comments to an AI agent as structured text — a difit-style review loop for a complete document instead of a git diff.
npx @mash/mdreview README.mdThis opens a browser tab. Select text in the rendered document, add comments, then click Finish Review (or just close the tab). The collected comments are printed to stdout in an AI-friendly format.
Features
- Full-document review with pretty Markdown rendering and stable
data-line/data-end-linesource anchors on every block. - Rich rendering: GFM task lists, footnotes, strikethrough; KaTeX math
(
$…$/$$…$$, rendered in the browser); Mermaid diagrams (lazy, strict); local images served from a jailed/asset/route; lazy in-browser code highlighting (fast first paint, no blocking on highlight.js). - Precise comments: select any text → comment, with an exact text-span anchor (selected text + surrounding context + occurrence) on top of the line range, so an AI can apply changes accurately.
- Suggestions: attach a proposed replacement string to a comment.
- Discussion: resolve/reopen comments and add replies.
- Preload existing findings with
--comment file.json(e.g. AI-generated review notes the human then responds to). - Keyboard-driven:
j/knavigate comments,ccomment the selection,rresolve,Enterjump,Esccancel. - Source / Split views toggle between rendered Markdown and raw source.
- Multi-user review over Tailscale:
--tailscalelets several reviewers on the same tailnet open one review and see each other's comments live; the owner ends it with Finish All. See below. - Local & private: by default (single-user) binds to
127.0.0.1and closing the tab finishes after a grace period; a random per-session token guards the URL and every API call; file content never leaves the machine.
Why
AI writes a lot of Markdown — plans, design docs, READMEs, PRDs. Reviewing it
means commenting on the whole document, not a diff. difit nails the
review-loop ergonomics (local server, browser comments, comments returned to the
agent on close) but targets git patches. mdreview keeps that loop and points it
at a full Markdown file.
How it works
mdreview CLI ─reads file, renders with markdown-it (data-line anchors)
→ HTTP(S) server (default 127.0.0.1; tailnet IP in multi-user), token-guarded, SSR-first page
→ browser: select text → comment → autosave + heartbeat
→ finish: Finish Review (single-user, or tab close after grace) /
owner Finish All (multi-user) / Ctrl-C
→ structured comments on stdout → consumed by an AI agent / Skill- Server-side first render: the document HTML is rendered on the server and inlined in the page shell so it's readable before the interaction JS loads.
- Viewport-first, token-boundary slicing: only the first window of the
document (~160 source lines) is inlined into
#docfor a fast first paint; the rest ships inert in a<template>and the browser appends it right after paint. Splitting happens at top-level token-group boundaries, so fenced code, tables, lists, and blockquotes are never cut mid-block. - Source anchors: every block carries
data-line/data-end-line(1-based, inclusive) from markdown-it token maps, so a selection resolves to a stable source line range that an AI can edit. - Heading paths: each comment's line is resolved to its heading path
(
Architecture > CLI server) server-side at finalize time. - Finish paths are mode-aware: in single-user (loopback) — or multi-user before any reviewer has joined — the browser sends a heartbeat and closing the tab finalizes from the last autosaved comments after the grace window. Once a real reviewer joins, tab close no longer ends the session; the owner finalizes with Finish All (or Ctrl-C).
Output
Default stdout:
# Review comments for docs/plan.md
## Comment 1
Target: docs/plan.md:L12-L20
Heading: Architecture > CLI server
Selected text:
The CLI starts a local server...
Comment:
This explanation is too vague. Add the data flow.Options
| Option | Meaning | Default |
|---|---|---|
| --port <n> | Local server port | 0 (random free port) |
| --comment <path> | Preload comments from a JSON file (repeatable; invalid entries dropped) | — |
| --allow-remote-images | Relax page CSP to allow remote http(s) images in the reviewed Markdown | off |
| --tailscale | Bind to this machine's tailnet IPv4 (enables multi-user mode); serves HTTPS via a Tailscale TLS cert when obtainable, else falls back to HTTP | off |
| -h, --help | Show help | — |
The browser auto-opens by default. Set MDREVIEW_NO_OPEN=1 to suppress it (the
owner/reviewer URLs are still printed to stderr) — handy for tests and automation.
MDREVIEW_GRACE=<sec> overrides the default 5s heartbeat grace (advanced/testing).
Multi-user review (tailscale)
By default mdreview is single-user on 127.0.0.1. Passing --tailscale
enables multi-user mode: several reviewers on the
same tailnet open the same review, each leaves comments, and everyone sees each
other's comments live (via polling/SSE).
- Owner vs reviewer: the person who started the CLI is the owner and connects via a URL carrying a separate secret owner token. Reviewers get a token-only reviewer URL to share (the owner token is never in it). The CLI prints both URLs to stderr; the owner can also copy the invite link from the page UI.
- Ending the review: the owner clicks Finish All to finalize the whole session — everyone's comments are merged and printed. A reviewer's own Finish only marks that reviewer done; it does not end the session. Ctrl-C also finalizes. In single-user (loopback) — or multi-user before any reviewer has joined — closing the tab finishes after the grace window. If nobody ever opens the page, the server auto-closes after ~120s.
- HTTPS via Tailscale: with
--tailscale, mdreview obtains a TLS cert for this machine's MagicDNS name (tailscale cert) and serveshttps://<machine>.<tailnet>.ts.net:<port>/, so the page is a secure context (clipboard, etc.). If a cert can't be obtained it falls back to plain HTTP. Binding is on the tailnet IP; only the URL host is the MagicDNS name. - Identity: each reviewer's display name is resolved server-side via
tailscale whoison the peer IP (shown as verified), falling back to the host OS username for the local owner, else a short client id — there is no name prompt. Comments are bucketed by a per-client id (never by the client-supplied name), so one reviewer can't overwrite another's comments.
Security
- Single-user binds to
127.0.0.1; multi-user (--tailscale) binds to the tailnet IP. - Every session generates a random token, required on the page URL and all API calls. In multi-user mode the owner token and reviewer token are separate bearer secrets. The file content never leaves your machine.
- In multi-user mode the
/asset/route is restricted to an allowlist of paths the render actually referenced, and refuses dotfiles /.env/*.pem/.git. Raw HTML in the Markdown is sanitized; responses setReferrer-Policy: no-referrer; per-client and subscriber caps apply; peer connections are logged to stderr.
Development
npm install # also vendors browser bundles via scripts/vendor.mjs (prepare)
npm run build # vendor + tsc -> dist/
npm test # unit + e2e (node:test)
npm run dev -- file.md # run from source via tsxscripts/vendor.mjs copies the browser bundles (highlight.js, Mermaid, KaTeX)
from node_modules into web/vendor/ — they load lazily in the UI and are
gitignored (regenerated on install/build). The rendering strategy (markdown-it
source maps, viewport-first token slicing, lazy code highlighting, CSS) is
adapted from the local markdown-fast work.
Skill
skill/SKILL.md packages this as an agent skill so an AI can run the review
loop and apply the returned comments. See that file for the agent workflow.
