Skip to content

Repository files navigation

diffscope

Your coding agent just rewrote three files. Do you actually know what changed?

MIT license Claude Code plugin POSIX sh and git only tests Python focused

English · 한국어


The problem

Coding agents are fast. Reading what they did is not.

A single task can touch six files. To understand it you open each diff, rebuild the call graph in your head, and try to work out whether the change actually altered behaviour or just moved code around. By the time you finish, the agent has moved on.

So most people stop reading. They skim, they trust, and their understanding of their own codebase quietly erodes.

What diffscope does

The moment your agent finishes a turn, diffscope captures only that turn's changes and prints one briefing to your terminal — a change map, a before/after behaviour trace, and an explanation of any Python that would trip you up.

No command to remember. No diff to open.

━━━━━━━━━━ 변경 브리핑 ━━━━━━━━━━
📍 Switched config loading from a raw dict to a frozen Record dataclass,
   so typo'd keys now fail at load time instead of deep in the call stack
   3 files · +48/-12

⚠ report.py:55 still does cfg["timeout"] — Record is not a dict, this raises TypeError

▌1. What changed          intent-grouped chunks + a call-relationship map
▌2. How behaviour differs  before/after table + a traced data flow
▌3. The Python here        3–5 constructs that actually appear, explained in context
▌4. Read in this order     three entry points, with reasons
▌5. Check yourself         two questions you can only answer by opening the code
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

It is a learning tool, not a reviewer. It will not lecture you about style. It flags exactly one class of problem — the kind that is already broken, like a caller nobody updated — and otherwise sticks to explaining.

Install

/plugin marketplace add https://github.com/SWHee/diffscope
/plugin install diffscope@diffscope
/reload-plugins

Working from a local clone? Point the first command at the directory instead:

/plugin marketplace add ./diffscope

That's it. Edit code in any git repository and the briefing appears when the turn ends.

Commands

Command What it does
/diffscope:off Stop briefings in the current repository. Survives restarts.
/diffscope:off --global Stop them everywhere.
/diffscope:on Turn them back on.
/diffscope:status State, target extensions, and why recent turns were skipped.

You can also just say "turn the briefing off" — the skill knows what to do.

Nothing showed up?

Run /diffscope:status. The hooks are built to fail quietly so they can never block your workflow, which means that log is the only record of why a turn produced nothing:

Code Meaning
briefing A briefing was requested — the healthy path
baseline First turn of the session. Comparison starts next turn
nochange No files changed
filtered Files changed, but none matched the target extensions
off Briefings are disabled here
skipped Not a git repository
error Snapshot failed

No log at all means the hooks never ran — check that you ran /reload-plugins.

"Stop hook error" in your transcript is not an error. Claude Code labels every Stop-hook message that way, including the healthy one that requests a briefing. If the briefing follows it, everything worked.

Updating doesn't happen automatically. The plugin is copied into a local cache at install time, so pulling new commits does nothing on its own. Re-run /plugin marketplace update diffscope, then /plugin install diffscope@diffscope and /reload-plugins.

How it works

When Hook What happens
Turn starts UserPromptSubmit The whole working tree is frozen into a git tree object — the baseline
Turn ends Stop Current tree vs. baseline; if source changed, write a patch and ask for a briefing

Why not git diff HEAD?

Because git diff HEAD shows every uncommitted change since the last commit. Use that and you re-explain work you already understood, every single turn.

Instead, diffscope writes the working tree into a throwaway git index and calls git write-tree at both ends of the turn, then diffs the two tree objects. This gets you:

  • Untracked new files, which git diff HEAD cannot see
  • .gitignore respected, for free
  • Zero mutation — your real index, HEAD, and working tree are never touched
  • Speed on large repos — it seeds from your real index, so git's stat cache means only genuinely changed files get re-hashed

Why it doesn't loop forever

The baseline is advanced to the current tree at the moment the briefing fires. Since a briefing doesn't modify code, the next Stop sees two identical trees and passes through. A stop_hook_active guard sits underneath as a second line of defence.

What's inside

.claude-plugin/   plugin + marketplace manifests
commands/         on · off · status
hooks/            UserPromptSubmit + Stop
scripts/          POSIX sh + git. No Node, no Python, no npm install
agents/           change-cartographer · python-tutor · logic-tracer
skills/
  diffscope/            the orchestrator
.github/          CI: hook smoke tests on every push
  python-syntax-tutor/  Python explainer + six reference files
tests/            hook-smoke.sh — 27 scenarios

The three agents run as a sub-agent fan-out, not a team: map first, then syntax and logic in parallel. Team coordination would buy nothing here and cost latency, and latency is the entire point. Changes under 2 files / 40 lines skip agents altogether.

Both analysis agents are read-only by construction — no Bash tool. An automatic, unattended briefing has no business running your test suite.

Requirements

  • Claude Code with plugin support
  • A git repository (outside one, diffscope does nothing at all)
  • sh, git, and standard POSIX utilities

Limits worth knowing

  • Briefing length follows change size. Under 2 files / 40 lines you get a compact three-section note; anything larger gets the full five sections.

  • Python gets the deep treatment. Other languages get the change map and the behaviour trace, but no syntax section. Two agents run instead of three.

  • Only these extensions trigger a briefing. Everything else is ignored on purpose, so lock files and build output can't drown the logic you actually need to read:

    py pyi ipynb js jsx mjs cjs ts tsx vue svelte html css scss sass
    go rs java kt kts rb php c h cc cpp hpp cs swift m mm dart scala
    ex exs lua zig sh bash sql tf
    

    Missing yours? Override the whole list: export DIFFSCOPE_EXT='py|erb|slim'

  • The briefing follows your conversation language. Talk to Claude in English and the briefing is in English; the same goes for any other language. Claude Code has no output-language setting, so this is the closest thing to one. To pin it regardless of what you type: export DIFFSCOPE_LANG=Korean

  • Generated churn is skipped. Lock files, migrations, and pure formatting get a one-line note instead of a full briefing.

  • State lives in ~/.diffscope, mode 700, patches 600, pruned after 7 days. Those patches contain your source — the permissions are deliberate.

Contributing

Issues and PRs are welcome, in English or Korean — whichever you prefer.

If you touch scripts/, run the smoke tests first:

sh tests/hook-smoke.sh

A broken hook doesn't degrade gracefully — it sits in the path of every message the user sends. Two contracts keep that safe: every script exits 0 no matter what goes wrong, and the Stop hook response never carries a continue field. Both are explained, along with how the plugin handles user source code, in CONTRIBUTING.md. Please read it before changing shell code.

Security problems go to SECURITY.md, not the issue tracker.

If it helped

diffscope exists because reading diffs one file at a time is a bad way to spend an afternoon. If it bought you one of those afternoons back, a ⭐ is the cheapest possible way to say so — and it's the only signal that tells me anyone is out there.

Commercial use

diffscope is MIT-licensed, so the license itself permits commercial use. That is the legal answer, and it is the honest one.

The human answer: if you're planning to ship this inside a product, bundle it with a paid tool, or roll it out across a company, please open an issue first. Not because I can stop you — I can't, and I'm not trying to. I'd simply rather hear where it ended up from you than stumble across it later. Attribution costs you a line and means a lot on this end.

License

MIT © 2026 geonhee

About

Auto-explains what your coding agent just changed — change map, behavior diff, and Python syntax notes, printed to your terminal.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages