| name | clean-diff-guard |
| description | Keeps a diff surgical and scoped — blocks unrelated file edits, whole-file/whole-repo formatter sweeps, over-engineered abstractions for a small ask, leftover debug/dead code, and overwriting the user's uncommitted work. Best used reactively before presenting or committing a code change, when files were edited, generated, moved, deleted, or reformatted. Use when the user says "make the change", "fix this", "refactor", "commit", or after a coding agent edits files. DO NOT USE for test-code quality (out of proofguard's scope; a test-quality-guard is in `deferred/`, not yet shipped), doc sync (docs-drift-guard), or the final done-claim (evidence-before-done); this guard keeps the DIFF surgical and scoped. |
clean-diff-guard
A generic "write clean code" review skill has been shown NOT to change agent
behavior in kill-tests — it's too vague to act on. This guard replaces that
with mechanically checkable imperatives about diff SCOPE, SIZE, and NOISE,
not style taste. It exists because of a well-documented 2026 failure
pattern: agents turning a 30-line task into 300 lines, touching files the
task never mentioned, or sweeping a whole file/repo through a formatter
because the diff was already open.
When to use — three modes
- Guard-pass (default). Right before presenting a diff or running
git add/git commit: compute the task's actual file scope, diff it
against the files actually touched, and run the checks below. This is the
mode that fires on "make the change," "fix this," "refactor," "commit," or
immediately after any coding agent finishes editing files.
- Live. While actively editing: before touching a file, ask "is this
file inside the task's stated scope?" If not, stop and either fold it into
scope explicitly (state why) or leave it alone. Catching an out-of-scope
edit before it happens is cheaper than reverting it later.
- Review. When asked to review someone else's (or a prior session's)
diff for scope creep: audit as a critic. List files-touched vs.
files-in-scope, size vs. task size, and any debug/dead code found. Don't
silently start fixing unless asked to.
What this guard blocks
- Files touched outside task scope. Before editing, list the files the
task actually requires (see
references/scope-discipline.md). After
editing, diff files-changed against files-in-scope. Any file changed
that isn't in scope and isn't justified by a stated, necessary
dependency (e.g. an import that must change because a signature moved)
gets REVERTED — not kept "while I'm in there."
- No whole-file/whole-repo formatter or import-reorder sweeps. If the
task is "fix the typo on line 42," the diff should touch line 42, not
re-indent, re-order imports, or re-wrap every line in the file. Check the
diff's line count against the number of lines the fix logically requires;
a formatter/linter-driven rewrite of untouched lines is noise, not part
of the requested change — revert those hunks and keep only the real edit.
- Over-engineering — diff much larger than the task needs. If a
request that's naturally a handful of lines (one function, one field, one
condition) produces a new class hierarchy, a config/options object, a
plugin/factory layer, or multiple new files, that's a size mismatch. Flag
it and rewrite to the smallest change that solves the stated problem. See
references/over-engineering-smells.md for concrete patterns and
before/after examples. This does NOT mean every multi-file diff is
over-engineered — a task that legitimately requires new
modules/validation/persistence/tests IS multi-file; judge against what
the task needs, not an arbitrary line count.
- Leftover debug/dead code. Before presenting the diff, scan the new
and changed lines for:
console.log/print/debugger statements added
for this change and never removed; blocks of commented-out old code left
next to the new code; // TODO / # TODO stubs standing in for real
logic; and placeholder bodies that compile but don't do the work (a
function that return nulls, throw new Error("not implemented")s, or
returns a hardcoded stand-in value where real logic was asked for).
Remove all of these before presenting the diff as finished.
- Never overwrite or discard the user's uncommitted work. Before any
sweeping edit, revert, or file rewrite, run
git status (and git diff
if anything is already modified/staged) first. If the working tree
already has uncommitted changes in a file you're about to touch, do not
blindly overwrite or checkout/reset/clean over them — read what's
there, edit around it, or ask before discarding it.
- One logical change per diff. If the task's fix and an unrelated
improvement both ended up in the same diff, split them: keep the
requested change in this diff, and either drop the unrelated change or
call it out separately as a distinct, explicitly-approved follow-up.
- Report files-touched vs. files-in-scope, explicitly. Every guarded
response ends with a plain accounting: which files were touched, which
of those were in scope, which (if any) were reverted, and why any
in-scope-but-larger-than-expected change was actually necessary.
Procedure — guard-pass steps
- Compute task scope. Read the task/request literally. List the
file(s) it names or clearly implies (e.g. "fix the typo in
handler.js"
→ scope is handler.js; "add user registration with validation,
persistence, and tests" → scope is the new module's files plus their
tests). See references/scope-discipline.md for the exact method.
- Check working-tree state first. Run
git status (and git diff for
anything already modified). Note any pre-existing uncommitted changes so
they are never blindly overwritten by a later revert/format sweep.
- Diff actual files changed vs. scope. After editing, list every file
the diff touches. Compare to the scope list from step 1.
- Revert noise. For any file changed that is out of scope, or any hunk
inside an in-scope file that isn't part of the real change (formatter
reflow, import reorder, whitespace-only rewrite of untouched lines),
revert that file/hunk back to its original content, keeping only the
real edit.
- Flag over-engineering. Compare the size/shape of the diff to what the
task needs. If new abstractions, config surfaces, or files were added
that the stated task doesn't require, replace them with the smallest
change that solves it — unless the task itself is genuinely multi-file
(new feature with validation/persistence/tests), in which case that
breadth is expected and should NOT be flagged or shrunk.
- Strip debug/dead code. Search the diff's added/changed lines for
debug prints, commented-out old code, TODO stubs, and placeholder
bodies. Remove them or replace placeholders with the real implementation
the task asked for.
- Compose the report (files-touched vs. files-in-scope, what was
reverted, why any larger-than-minimal change was necessary) and emit the
guard footer.
Output
Emit this verbatim as the last line of any response where a diff is
presented or committed:
proofguard:clean-diff-guard — <PASS | FIX-REQUIRED | WAIVED(reason)> · Triggered: <what fired it> · Fixed: <n> · Verified: <files-touched vs files-in-scope> · Remaining gap: <what's unchecked>
PASS — files touched match task scope (or any extra file is justified
and stated), no formatter/reflow noise, diff size matches task size, no
debug/dead code left, and no uncommitted work was overwritten.
FIX-REQUIRED — out-of-scope files, formatter noise, over-engineering, or
debug/dead code was found and has not yet been reverted/stripped in this
response.
WAIVED(reason) — a larger diff or an out-of-scope touch was genuinely
necessary (e.g. a renamed export forces callers to change) and that's
been stated explicitly, not silently absorbed.
References
references/scope-discipline.md — how to compute task scope and
files-in-scope, the revert-out-of-scope rule, and the
don't-overwrite-uncommitted-work rule.
references/over-engineering-smells.md — concrete over-engineering
smells (unused abstraction, premature config, factory for one impl, 300
lines for a 30-line task, debug/dead code, placeholder bodies) with
short bad/good examples.
What this guard does NOT do
It is not a linter or formatter — it doesn't enforce a style guide, and it
doesn't run/configure Prettier, ESLint, gofmt, or Black (those are
tool-level concerns; if the task IS "run the formatter," that's in scope
and this guard doesn't block it). It does not judge whether a test is a
good test (test-code quality is out of proofguard's scope; a
test-quality-guard is in deferred/, not yet shipped), whether docs stayed
in sync (docs-drift-guard), whether the diff leaks a secret
(no-secret-leak-guard), or whether the done-claim has evidence
(evidence-before-done). This guard controls one thing only: the diff's
SCOPE, SIZE, and NOISE against what the task actually asked for.