--- 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 1. **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." 2. **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. 3. **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. 4. **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 null`s, `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. 5. **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. 6. **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. 7. **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 1. **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. 2. **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. 3. **Diff actual files changed vs. scope.** After editing, list every file the diff touches. Compare to the scope list from step 1. 4. **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. 5. **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. 6. **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. 7. **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 — · Triggered: · Fixed: · Verified: · Remaining gap: ``` - `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.