Your coding agent just rewrote three files. Do you actually know what changed?
English · 한국어
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.
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.
/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.
| 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.
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.
| 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 |
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 HEADcannot see .gitignorerespected, 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
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.
.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.
- Claude Code with plugin support
- A git repository (outside one, diffscope does nothing at all)
sh,git, and standard POSIX utilities
-
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 tfMissing 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, mode700, patches600, pruned after 7 days. Those patches contain your source — the permissions are deliberate.
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.shA 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.
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.
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.
MIT © 2026 geonhee