Skip to content
| Marketplace
Sign in
Visual Studio Code>AI>Gadfly ReviewerNew to Visual Studio Code? Get it now.
Gadfly Reviewer

Gadfly Reviewer

gadfly-reviewer

|
1 install
| (0) | Free
Read, review and listen to Markdown plans. Sends review feedback to the Claude Code session that wrote them.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

Gadfly

Gadfly is for reviewing the Markdown plans Claude Code writes. You comment on exact passages in VS Code, and the review goes back into the session that wrote the plan. Session transcripts can be reviewed the same way, and a local TTS voice can read either one aloud. The repository README explains the idea and shows what a review looks like when it reaches Claude.

Install

Install Gadfly Reviewer from the VS Code Marketplace (code --install-extension gadfly.gadfly-reviewer). Each GitHub release also carries gadfly.vsix, or build it from a clone with npm install and npm run package (which writes extension/gadfly.vsix).

  1. Install from the Marketplace, or code --install-extension gadfly.vsix
  2. Run Gadfly: Install Claude Code Hooks. This adds the PreToolUse, PostToolUse, Stop and UserPromptSubmit hooks to ~/.claude/settings.json (a backup is kept) and installs the review skill into ~/.claude/skills/gadfly-review/. After that, Gadfly refreshes the hooks and the skill by itself when you update the extension.
  3. Quit VS Code with ⌘Q and reopen it. A window reload doesn't reliably pick up a new extension manifest.

Open a .md file with Open in Gadfly (editor title button, Explorer context menu, or Reopen With…). Gadfly: Make Gadfly the Default for Markdown makes it the default; Gadfly: Stop Using Gadfly as the Default for Markdown undoes that.

Requirements

  • VS Code 1.100 or later, and Claude Code (Gadfly is built against its current release).
  • The player only: an Apple Silicon Mac (Kokoro-82M runs on the GPU through MLX) and uv, auto-detected at /opt/homebrew/bin/uv, ~/.local/bin/uv or on PATH, or set gadfly.uvPath. The model (gadfly.model, default mlx-community/Kokoro-82M-8bit) comes from the Hugging Face cache or is reachable through HF_ENDPOINT/HF_TOKEN. On first play the daemon installs its Python dependencies, which takes a minute once.

The TTS daemon starts on the first play, so everything except listening works without uv or the model.

Layout

Everything lives in one tab: Contents · Source · reader · Notes · Review. Toggle each from the top bar, or click Zen mode to hide them all and read the document alone (click again to bring them back). Drag the dividers to resize, drag a pane past its minimum width to hide it, and double-click a divider to reset it. Clicking a heading in Contents scrolls the reader, and the source if it's open.

The scroll track on the right marks the bookmark (red ribbon) and comment threads (violet; click one to open the thread). Clicking highlighted text in the reader also opens its thread.

The source pane is Gadfly's own editor on the same document. VS Code handles undo (⌘Z), dirty state and save (⌘S); the pane scrolls in step with the reader and is read-only only while Claude applies a completed review. Gadfly: Open Source in Text Editor opens VS Code's own editor if you prefer it. gadfly.readerFont switches the reader between Literata and your UI font.

Review workflow

  • Select text and click Comment (or click + in the review panel: it comments on the selection, or adds a document-wide general note when nothing is selected). The first comment starts a review: Claude's Write/Edit is blocked by the hook until the review is complete. You can keep editing the file yourself (see Drafts below).
  • The review panel is one list, grouped by top-level section (General first; click a group to fold it). Each thread shows the number Claude cites (4, G1), the sub-section when it is deeper than the group (hover for the full path), and pending (not sent yet, or new messages since the last Send) or sent. Status and Section filters combine; open threads show by default. Collapse all, Resolve all and Delete pending act on the threads shown and ask first.
  • Send to Claude posts a compact digest (mode=DISCUSS) to the linked session's inbox. The link comes from the PostToolUse hook when Claude writes the file. With no live link, the digest goes to the clipboard.
  • Claude answers in chat. Once every thread sent so far is settled, Claude asks you once (Post and resolve, Post only, or Don't post), then posts summary replies in one batch (gadfly review reply <file> --batch [--resolve]), coloured by the session that wrote them. A reply is unread (blue dot) until you open or click its thread; N unread at the top jumps to the next one. Resolve is one click, and you can resolve, reopen and reply yourself. A thread's resolution is Claude's latest reply (plus anything you added after it); a thread with no reply from Claude means no change. Claude never clicks Review complete.
  • References in comments ([§5 Infra "we already run Postgres 16"], or type # to pick a section) are links: click one to scroll the document (and the source) to the quote. A red link means the quote isn't in this version; it still jumps to the section if that exists.
    • References follow your edits like comment quotes do: each one remembers the text it was made on and where its words were in it, and when you edit that text the quote shown in the comment follows (a comment already sent becomes pending again, and the next Send shows reference edited: [§… "old"] → [§… "new"]).
    • When all of the referenced text is gone, the reference is outdated: red, struck through, "Referenced text removed", and nothing asks you anything. It comes back by itself if the text returns (undo, switching branches back). Claude's digest shows it as [§… "quote" (referenced text removed)].
    • In the document, text that a comment references has a thin dotted underline in the link colour; click it to open the thread (a comment on the same text wins).
  • Connected to @name under the title shows the linked session: the one that last wrote or replied on the file. Its dot turns amber when that session isn't running (Send then copies to the clipboard). Click it to pick another running session; from inside a session, gadfly review link <file> does the same.
  • Review complete (enabled once every thread is resolved) asks who makes the next version:
    • Claude applies the resolutions saves the file, sends the mode=APPLY digest (with a note to keep your manual edits, if you made any) and lets Claude write. Meanwhile the file is read-only for you (the top bar says "Claude is applying the review"); Take back ends that. Claude's update becomes the next version and ends the hand-over; Verify shows the diff next to your resolutions.
    • I'll write the next version sends nothing. Claude stays blocked while you edit; Save as new version ends it.
  • Drafts. You can always edit (except while Claude applies a review). Edits that make the file differ from the latest version are a draft ("Draft on v9" in the top bar, "v9 (current) · draft" in the version picker). Saving to disk (⌘S, autosave) never makes a version; undoing back to the latest version ends the draft. While a draft exists Claude's Write/Edit is blocked ("the user has unsaved draft edits"), so its work can't clash with yours. Save as new version (top bar, or Gadfly: Save as New Version) saves the file, records it as the next version and ends the draft. It is disabled during a review until every thread is resolved and you click Review complete. You can comment while you have a draft: the review is on the version the draft is based on. Restoring an older version while you have a draft asks before discarding it.
  • Comments follow the text, the way GitHub and Gerrit carry review comments to a newer patch: without asking, and without saving anything per edit. A comment remembers the text it was made on (kept once, in the review folder) and where its words were in it. Where it sits now is that position carried through a diff of the two texts, so Gadfly's source pane, VS Code's text editor, a git pull or branch switch, and edits made while Gadfly was closed all give the same answer, and undo, or switching away and back, restores positions exactly. Text typed inside a quote extends it; deleted text shrinks it. At an edge, only the word the quote ends (or starts) in is followed: "tenant" becomes "tenants" when you add an "s" and "rewrite" when you add "re", but " (draft)", a full stop or a new word stay outside. Words keep - and ' when they sit between letters ("per-tenant", "tenant's"), and a word you edit inside the quote ("tenant" to "tenancy") is followed. If the words moved elsewhere, the exact quote is found again when it occurs once.
    • When the whole quote is gone the thread stays, marked outdated ("outdated · won't be sent" while it has never been sent): its quote is dimmed and struck through, and it has no highlight or scroll-bar mark. It returns by itself when the text does. An unsent outdated thread can be deleted; a sent one is resolved as usual.
    • Outdated comments are not sent. A thread that was never sent and is outdated is left out of a Send and is not counted in "Send to Claude (N)"; when its text returns it is an ordinary pending thread. A thread Claude already has that becomes outdated turns pending once, and the next Send carries one line, #4 dropped: quoted text removed by the user. If the text comes back after that, the thread is pending again as an ordinary follow-up. A sent thread whose quote text changed shows quote edited: "old" → "new". Resolved threads add nothing to a Send. In Review complete, a resolved thread whose text is gone is skipped.
    • Renamed or moved files (git mv): opening a Markdown file Gadfly doesn't track, whose text equals the latest version (or draft) of a tracked file that no longer exists, offers "Continue the review of old/path.md here?". Continue moves the whole review.
    • A draft that comes from the file changing under the editor (not from typing) is labelled "v9 · draft (includes outside changes)" in the version picker.
  • Compare (the compare button by the version picker, or Gadfly: Compare Versions) shows the line diff between any two versions, or between a version and your unsaved draft. Pick From and To in the header, swap them, and click a "… N unchanged lines" gap to expand it. It shows text only, not review threads.
  • One version per Claude turn. Claude's edits within one turn amend a single version (marked "writing…" in the picker) instead of making one each. It is sealed when Claude's turn ends (the Stop hook that Install Claude Code Hooks adds), when you start a draft, comment, save a version or restore, when another session writes, or after 5 minutes without a write. gadfly versions squash <file> merges older runs of one session's consecutive versions (never across comments or a draft); it also runs once for every tracked file after upgrading.

The CLI is there for Claude and for recovery. It changes the store only by posting Claude's replies, linking a session and recording versions:

~/.gadfly/bin/gadfly review pending <file>        # reprint the digest
~/.gadfly/bin/gadfly review status <file>
~/.gadfly/bin/gadfly review reply <file> <id>     # one reply, body on stdin
~/.gadfly/bin/gadfly review reply <file> --batch [--resolve]
~/.gadfly/bin/gadfly review link <file>           # make this session the linked one
~/.gadfly/bin/gadfly versions squash <file>
~/.gadfly/bin/gadfly install-hooks
~/.gadfly/bin/gadfly doctor

Notes

A private notepad next to the document: Notes in the top bar, or ⌥N. Nothing in it is ever sent to Claude: it is not in digests, references or the CLI's output, and it is not read aloud.

  • Notes belong to the Claude session, not to a document. A session window shows its own notes; a document shows the notes of the session it is connected to ("Connected to @name"), so the notes follow you from the conversation to the plan it produced and back, and through --resume. With no connected session the pane says "Notes belong to a Claude session. Connect one to take notes." with a Connect a session button. The header reads "Notes · @name · private, never sent to Claude".
  • A light list editor. Bullets (- ), numbers (1. ), checkboxes (- [ ] ) and quotes (> ), with bold, italic, code and links styled where they stand (there is no preview mode; headings only show as bold lines, tables and HTML as plain text). What is stored is a small Markdown subset, so the file stays readable anywhere.
    • Enter continues the list (a new checkbox starts unchecked, numbers count on); on an empty item it ends the list. Backspace right after a marker removes the marker.
    • Tab / Shift+Tab indent and outdent an item by two spaces.
    • ⌘Enter toggles the checkbox on the line (an item without one gets one; a plain line becomes a checkbox item). Clicking a box toggles it too.
    • Undo inside the pane (⌘Z) is the pane's own.
  • Autosave about 400 ms after you stop typing, and when the pane hides or the tab closes.
  • Add to notes on the selection bar appends the selected text as a > quote and puts the cursor after it.
  • Open in editor (pane header) opens the raw .md in a normal VS Code tab beside; saving there updates the open panes.
  • Everywhere at once. All panels on one session's note, in this window or in other VS Code windows, show the same text: a change arrives when you are not typing in that pane.
  • Notes from before 0.3.2 were keyed by document and are not migrated; their files stay in the store until retention removes them.

Saved sessions and expired sessions

Claude Code deletes transcripts after a while (cleanupPeriodDays, 30 days by default). So Gadfly keeps its own copy of every session you open in it: the messages it shows (you, Claude, subagent results, Gadfly digests, compaction summaries and rules) plus the session's name, folder, branch, model and times. The copy is written when you open the session and follows it live.

When Claude Code's transcript is gone, the session still opens from the copy, under Gadfly: Open Session… in an Expired in Claude Code group, with the banner "Session expired in Claude Code · read-only". Everything that would change it is read-only: new comments, replies, edits, resolve, reopen, delete, Send, and its notes (you can still read and copy them; Open in editor and Add to notes are off). gadfly session reply refuses too. You can still read it, listen to it, select and copy references to it, and gadfly show ref=s:… still resolves its messages. A session that is merely not running keeps its full editing; only a missing transcript expires it.

Retention

gadfly.retentionDays (default 90; 0 keeps everything) deletes what Gadfly stores for a document or a session that has not been opened in Gadfly for that many days, counted from when it was last opened or closed in any window.

  • A session is one unit: its saved copy, comments, link, details and notes go together. A document's whole review folder (versions, comments) goes together too. Leftover notes (keyed by a document, from 0.3.1) are judged by their file's modification time.
  • Never deleted: a running session, anything open in a window, and a document review that is under review or has a draft.
  • It runs once a day across all windows (30 seconds after a window starts, if no window has run it in the last 24 hours). There is no warning: a session never opened in Gadfly has nothing of Gadfly's to lose, and one that was opened already has its copy.

Session review

The conversation before the plan gets the same loop as the plan: read it, listen to it, comment on it, and send the comments back to the session. Gadfly: Open Session… lists the running Claude Code sessions of this window's folders first ("Show sessions in other folders" for the rest), then past sessions, then those that have Expired in Claude Code (see above). The review panel's connection chip has an Open transcript button for the session a document is linked to. A session opens once per window; opening it again focuses its tab.

  • What you see. Your messages, Claude's text, subagent results, Gadfly digests and compaction summaries (both folded, readable, not commentable), and a "Compacted · time" rule at each compaction. Tool calls, thinking and harness noise are dropped. Each message has a heading (Claude · 14:02 · msg 12); Contents lists the turns. The transcript is followed live: new messages append in place without moving your scroll position or the playhead, and Claude's message that is still being written is marked "writing…" until it ends. Sessions have no versions, drafts, Compare, Source pane or Review complete.
  • Listening. Read aloud chips (You · Claude · Subagents · Gadfly · Summaries, all on) choose what is spoken; a short cue ("Claude.") is spoken when the speaker changes. The chips are kept in the tab's state only. Playback follows live: when it has reached the end, new messages play next; when you are behind, they wait and Catch up shows "N new".
  • Comments. Select text in your message, Claude's or a subagent's and comment as in a document. Threads are numbered S1, S2… and grouped by turn ("msg 12 · Claude"). Messages never change, so nothing goes outdated. Threads are stored by the session's root id (<store>/sessions/<id>/), so they follow a session through --resume.
  • Send carries only this session's pending threads and goes only to this session's inbox. It is disabled while the session isn't running ("@name isn't running — resume it to send"); sessions never fall back to the clipboard. Claude answers in plain chat first; once everything sent is settled it asks once and posts the replies in one batch (~/.gadfly/bin/gadfly session reply --batch [--resolve]), optionally resolving them.
  • References both ways. Select text in a document or a session (running, stopped or expired) and press Copy reference in the selection bar (or run Gadfly: Copy Reference to Selection). It copies [@name › msg 12 "quote"] ref=s:… or [file.md › §Section "quote"] ref=d:…, an id that resolves in any VS Code window. Pasted into a Gadfly comment (new, reply or edit), it becomes a live reference: a chip that opens the session at that message or the document at that text, followed through later edits of a document (it goes outdated when the text is removed). Pasted anywhere else, such as Claude's prompt, it stays readable and gadfly show ref=… resolves it. An id this machine can't resolve stays plain text. Digests expand references with the quote, a sentence either side and the id, for gadfly show <ref> [--context N].
  • Plan mode. With open session threads, a session can't enter plan mode (gadfly.sessions.blockPlanOnOpenThreads, default on).

CLI for Claude: gadfly session pending [<id>], session threads [--open], session reply --batch [--resolve] (blocks headed === S4), show <ref>.

Player

⌥Space play/pause · ⌥←/→ skip 10 words (gadfly.skipWords) · ⌥⇧←/→ previous/next sentence · ⌥↑/↓ speed ±0.25 · ⌥C comment on the selection or current sentence · ⌥B go to bookmark (click the red ribbon on the right edge to go to, play from, or remove it; or run Gadfly: Remove Bookmark). Player keys pause while you type in the source pane or a comment box. While playing, click a word to jump; ⌥-click plays from anywhere.

At rest the player is a small pill: back, play, forward and speed, under a thin progress line. Hover it (or Tab into it) and it opens in place, with Play staying where it is: the progress line grows into the time bar, follow and focus slide out on the left, and settings and minimise on the right. The voice is picked from settings. On the time bar, pointing near a section mark snaps to it, so a click starts that section. Minimise leaves just play, settings and expand in the bottom-right corner; below 400 px of reader width the player uses that size by itself. Gadfly remembers the size. The gear opens the settings you change while listening; the rest are in VS Code's Settings. Voice engine progress and playback errors appear in VS Code's status bar (click for the log); the play button only gets a red dot. Speed runs 0.5–3.0× in 0.1 steps. In hybrid mode (gadfly.speedMode) Kokoro synthesizes at 0.5/0.75/1/1.25/1.5 and pitch-preserving playback rate covers the rest.

Where things live

  • ~/.gadfly/ (override: GADFLY_HOME):

    • config.json points the CLI and hooks at the store (storePath) and mirrors two settings for them (blockPlanOnOpenThreads, retentionDays), plus the time of the last retention run.
    • bin/gadfly (a wrapper) and bin/gadfly.mjs: the CLI copy the hooks call, so hooks survive extension updates; bin/SKILL.md is the skill it installs.
    • gadflyd.sock, gadflyd.lock, gadflyd.log: the shared TTS daemon.
  • The store is VS Code's global storage for this extension, normally ~/Library/Application Support/Code/User/globalStorage/gadfly.gadfly-reviewer/ (override: GADFLY_STORE, for the CLI and tests). It is never inside a repo:

    tracked-index.json            tracked documents (locked? why? open threads?) and open session
                                  threads: what the hooks read to decide, fast
    recent-writers.json           .md files Claude wrote before they were tracked
    reviews/<sha1 of path>/       one folder per document opened in Gadfly
      meta.json  link.json  versions.json  owner.json (only while a window has it open)
      blobs/<sha1>.md             every version's text (and comment base texts), stored once
      pins/<sha1>                 blobs a copied d: reference names, never dropped
      threads/<n>.json            the comments made on version n
    sessions/<root session id>/   one folder per Claude session reviewed or opened in Gadfly
      meta.json  link.json        session details, and how to reach the running session
      owner.json                  (only while a window has it open)
      threads.json                the comments (S1, S2…)
      transcript.jsonl            the saved copy (one message per line)
      info.json                   name, folder, branch, model, times, resume chain
    notes/s-<root session id>.md  the private note of that session
    audio/                        synthesized sentences (<key>.wav + <key>.json), capped by gadfly.cacheMB
    

    Sessions are stored by their root id (the id of a conversation's first transcript entry), so comments and notes follow a session through --resume. If the extension id ever changes, copy this folder across (see Extension id and the store location).

Settings

Setting Default What it does
gadfly.openMarkdownByDefault false Open Markdown files in Gadfly instead of the text editor.
gadfly.readerFont serif serif is Literata; sans is your VS Code interface font.
gadfly.retentionDays 90 See Retention above. 0 keeps everything.
gadfly.sessions.blockPlanOnOpenThreads true Block plan mode in a session that has open Gadfly threads.
gadfly.voice af_heart TTS voice.
gadfly.speed 1 Playback speed, 0.5 to 3.
gadfly.speedMode hybrid hybrid (see Player above), model (all speed from Kokoro) or playback (all from playback rate).
gadfly.skipWords 10 Words to skip with ⌥←/→.
gadfly.readCode false Read code blocks aloud instead of announcing and skipping them.
gadfly.followResumeSeconds 8 Resume follow mode this many seconds after a manual scroll.
gadfly.uvPath empty Path to uv; empty means auto-detect.
gadfly.model mlx-community/Kokoro-82M-8bit Kokoro model: a Hugging Face repo id or a local folder.
gadfly.daemonIdleMinutes 15 The shared TTS daemon exits after this many idle minutes.
gadfly.cacheMB 300 Size limit of the audio cache.

When something is wrong

Gadfly: Doctor writes an environment report to Gadfly's log (gadfly doctor prints the same report in a terminal), and Gadfly: Show Log opens that log. Gadfly: Restart TTS Engine restarts the voice daemon, whose own log is ~/.gadfly/gadflyd.log. Bugs go to the issue tracker. To work on Gadfly itself, see CONTRIBUTING.md.

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft