Skip to content

Instantly share code, notes, and snippets.

@myobie
Created March 17, 2026 11:48
Show Gist options
  • Select an option

  • Save myobie/375b399ca505cf8cdcf03527f4264276 to your computer and use it in GitHub Desktop.

Select an option

Save myobie/375b399ca505cf8cdcf03527f4264276 to your computer and use it in GitHub Desktop.
Literate PR Review — Claude Code SKILL file for interactive code review with diffs, web UI, and comment monitoring
name literate-review
description Use when the user wants to review code changes interactively. Uses literate markdown documents served in a local web app. Works with one repo or many. Triggers on: 'review PR', 'review branch', 'review changes', 'literate review', 'set up a review', 'create review docs', 'review these branches'. Generates topic-organized markdown files with inline diffs and surrounding context, serves them with syntax highlighting and inline commenting, and watches for reviewer comments that the current Claude Code session can respond to.

Literate PR Review

Generate a topic-organized code review experience for changes in one or more git repositories. The output is a set of .diff and literate markdown files served in a local web app where reviewers can leave inline comments that Claude watches and responds to. Works equally well for a single branch in one repo or coordinated changes across many repos.

Overview

  1. Produce raw diffs — save to docs/ as .diff files (source of truth)
  2. Plan topics — group hunks by functional area, not by repo
  3. Write literate markdown — diffs are the primary content, prose explains them, context in <details>
  4. Build the web server — Express + marked + highlight.js + WebSocket live-reload + selection based commenting UI
  5. Run Playwright tests — verify everything works before presenting, never ask a user to test something you haven’t already tested
  6. Launch — server + CronCreate for comment monitoring

Step 1: Gather Information

Ask the user for:

  • Repo(s) and branch(es) to compare (e.g., main...feature-branch)
  • Output directory (suggest a sibling directory like ../review-name/)
  • Topics — learn from reading the diffs. The user can optionally tell you some of the topics they are interested in.

If the user has already provided this, proceed directly.

Step 2: Produce the Raw Diffs

For each repo, get a full diff and save into docs/ as 00-<repo-name>.diff. These files:

  • Appear in the sidebar for quick reference
  • Are the source of truth — every hunk must appear in at least one literate doc
  • Are commentable by the reviewer, just like the markdown docs

Step 3: Plan Topics

Read the diff files. Group hunks into topics by functional area (not by repo or file). Each topic should tell a coherent story. Propose topics to the user, or let them define them.

Step 4: Write Literate Markdown Docs

The diffs are the primary content. For each topic file:

  1. Prose intro — what this topic covers and why the changes were made
  2. For each relevant hunk, create a section:
    • Brief prose explaining the change
    • The actual diff code block (copied verbatim from the .diff files)
    • If the diff is too short to understand alone, either increase the surrounding context of the diff a small amount or add a collapsed <details> block with the full function/class/etc for context
  3. Cross-reference between topics using relative markdown links
  4. Add visualizations where they help (ASCII diagrams, tables, sequence annotations). Be creative. Be helpful.

Example Structure

# Topic Title

Prose explaining what this topic covers and why.

## Section Name (repo-name/path/to/file.ext)

Prose explaining this specific change.

\`\`\`diff
 context line
-removed line
+added line
\`\`\`

<details>
<summary>Full function for context</summary>

\`\`\`language
function example() { ... }
\`\`\`

</details>

See also [Other Topic](XX-other-topic.md) for related changes.

Rules

  • Every section MUST have a diff block. No exceptions. The reviewer came to see the diffs.
  • Diffs are verbatim from git diff. Never fabricate or rephrase. Copy actual +/-/ lines.
  • Context goes in <details>. Collapsed by default, available on demand.
  • For new files, show key hunks as diffs with + lines. Short files (< 50 lines) get the whole diff. Long files get the important hunks plus a “see full diff” details which expands to show the entire diff for the new file.
  • Don't duplicate. Include a hunk in one doc, cross-reference from others.
  • Lead with prose. Explain why before showing what.
  • Justify the changes. Why is this necessary? Why is this worth submitting for review?
  • Number the files (01-, 02-, ...) to suggest reading order.
  • Be creative with visualizations — ASCII diagrams, tables, sequence annotations. Think whiteboard.

Step 5: Build the Web Server

Create server.mjs in the project root. Dependencies: express, marked, marked-highlight, highlight.js, chokidar, ws, etc.

npm init -y  # Set "type": "module" for ESM
npm install express marked marked-highlight highlight.js chokidar ws # etc

Endpoints

  • GET / — Sidebar with all docs (.md and .diff), renders first .md by default
  • GET /docs/:name — For .md: renders markdown with syntax highlighting. For .diff: renders with highlight.js diff highlighting, splitting on embedded > blockquote lines to render inline comments. Returns HTML fragment when X-Requested-With: fetch header present.
  • POST /api/request — Accepts { file, selectedText, comment }. Generates a 7-char hex short-id. Creates requests/<short-id>.md with frontmatter AND inserts > **Reviewer** (<short-id>): comment into the doc. Returns { ok: true, id }.
  • WebSocket /ws — Pushes { type: "reload", file } on file changes in docs/

UI Requirements

  • Dark theme (GitHub-dark): #0d1117 background, #e6edf3 text
  • Sidebar (280px fixed) with doc links, active state highlighting. .diff files shown in italics.
  • Content layout: Full-width scrollable <main> with max-width inner wrapper. Scroll works anywhere in the right pane, not just over the text column.
  • SPA navigation using fetch + pushState. Target the inner wrapper, not the scroll container. Make sure the back/forward buttons work.
  • Blockquote styling: Red border for Reviewer, blue border for Claude. Anchor IDs (comment-<short-id>) on each.
  • Comment nav: Sticky bar at top with "Comments (N)" dropdown. Lists all blockquotes, click to scroll-and-highlight. Rebuild on navigation and live-reload.
  • Split diff toggle: "Split diffs" button parses diff code blocks into side-by-side old/new panes. Widens content to ~1400px. Persists across live-reloads and navigation.
  • Commenting: mouseup on selection > 3 chars shows fixed-position "Comment" button. Popover flips above when near viewport bottom. Works on .md and .diff files. Server strips > prefixes and **Reviewer/Claude** (id): markers when matching selected text.
  • WebSocket auto-reconnect on close (2s delay)

Request File Format

---
file: 05-subscription-changes.md
selectedText: "..."
status: pending
created: 2026-03-17T22:14:03Z
---

The reviewer's comment text.

Step 6: Comment Monitoring

The requests/ directory is the queue. Set up a recurring cron after the server is running:

CronCreate: "*/1 * * * *", recurring: true

The cron prompt should instruct Claude to:

  1. List requests/ for pending files
  2. For each, read the comment and surrounding doc context
  3. Determine type (ask or change) and process accordingly
  4. Delete the request file after processing (audit trail is in the doc blockquotes)

Two Types of Comments

Claude infers the type — no explicit label needed.

Ask — reviewer wants an explanation or has a question. Claude reads the context, reads source files if needed, inserts a > **Claude** (<id>): response blockquote after the reviewer's comment.

Change — reviewer wants code modified. Claude makes the change in the actual repo, regenerates the diff section in the doc (collecting old comments into a collapsed <details> archive), and responds with > **Claude** (<id>): Done — description.

Diff Regeneration (for change comments)

  1. Collect existing blockquote pairs from the section
  2. Re-read the source with git diff main -- path/to/file (includes working tree changes) and update the .diff file that is our source of truth
  3. Rewrite the diff/context blocks across all .md files
  4. Archive old comments in <details><summary>Previous review comments (N)</summary>...</details>

Step 7: Playwright Tests

Create test.mjs using playwright (not @playwright/test). The test starts its own server, runs checks, then kills it.

Required coverage:

  1. Server responds with 200
  2. Sidebar has correct number of links (.md + .diff)
  3. Each doc renders with headings and code blocks
  4. Syntax highlighting present (.hljs classes)
  5. Diff code blocks present
  6. Text selection shows comment button
  7. Comment submission creates request file + blockquote with short-id
  8. Live reload shows comment without manual refresh
  9. Commenting on blockquotes works
  10. Commenting on .diff files works (via API)
  11. Screenshots at each stage
npm install playwright && npx playwright install chromium
node test.mjs

Step 8: Launch

After all tests pass:

  1. Start server: nohup node server.mjs &
  2. Set up CronCreate for comment monitoring
  3. Tell the user the URL and cron job ID

File Structure

review-project/
├── package.json
├── server.mjs
├── test.mjs
├── docs/
│   ├── 00-repo-name.diff    (raw diffs, one per repo)
│   ├── 01-topic-name.md
│   ├── 02-topic-name.md
│   └── ...
├── requests/                 (transient queue, files deleted after processing)
└── screenshots/

Important Reminders

  • Test before presenting — every time. Run node test.mjs after every change to the server, UI, or docs. Never tell the user to reload until all tests pass. Non-negotiable.
  • Audit after generating docs. Run grep -c '````diff' docs/*.md — every doc must have diffs. Cross-check every hunk in the .diff files against the docs. No fabricated lines.
  • UI preferences go in this SKILL file. If the reviewer asks for different styling or layout, implement it AND update this file so future sessions produce the same UI.
  • The cron is session-only. Remind the user it dies when Claude exits (3-day auto-expiry).

Lessons Learned

These went wrong during real usage. Follow them to avoid repeating.

Validation

  • Escape \n in template literals. Inside backtick HTML templates, \n becomes a real newline — use \\n. This is the #1 cause of browser SyntaxErrors. Playwright catches them.

CSS / Layout

  • The scroll container is #content, not window. window.scrollY is always 0. Comment button/popover use position: fixed with raw getBoundingClientRect() values. Clamp to viewport bounds with Math.max(8, ...).
  • Sticky nav needs padding-top: 0 on #content. Put top padding on #content-inner instead.

Comment System

  • Comments can land inside code blocks. The server's line search finds text inside fenced code regions. Prefer matches outside triple-backtick regions.
  • Selected text from rendered blockquotes won't match raw markdown. Strip > prefixes and **Reviewer/Claude** (id): markers when searching.
  • Popover must flip above when near viewport bottom. Check window.innerHeight - rect.bottom before positioning.

Diff Regeneration

  • Use git diff main -- path/to/file (not main...HEAD) to include unstaged changes.
  • Blank line required between <details> tag and blockquote content, otherwise marked won't parse the blockquotes.

Content

  • Context goes in <details>, not as the main content. The diff is what the reviewer is here to see.
  • Don't duplicate hunks across docs. Include once, cross-reference elsewhere.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment