focused-fix · diff

v1.0.0 to v1.0.0

63 added, 75 removed. Audit A to A.

---
name: focused-fix
description: >
- This skill should be used when the user asks to "fix a bug with minimal changes",
- "analyze change scope for a bugfix", "find the minimal set of files to change",
- "do a focused bugfix", or "scope a minimal repair".
+ Constrain a bugfix to the smallest reviewable change: reproduce, patch, add a regression
+ test, check the diff for scope creep. Use when asked for a minimal fix, hotfix or
+ release-blocker; diagnose unknown causes with bug-diagnosis.
license: MIT + Commons Clause
metadata:
version: 1.0.0
author: borghei
category: engineering
domain: debugging
- updated: 2026-04-02
+ updated: 2026-11-08
tags: [bugfix, minimal-change, scope-analysis, debugging, focused-fix]
---
# Focused Fix
- > **Category:** Engineering
- > **Domain:** Debugging & Maintenance
-
- ## Overview
+ The agent fixes the reported bug and nothing else: a reproduction, the smallest change that makes it pass, a regression test, and a diff that a reviewer can read in a minute. Small fixes are easier to review, safer to roll back and less likely to hide a second regression.
- The **Focused Fix** skill enforces a disciplined minimal-change approach to bug fixing. Instead of refactoring or improving code during a bugfix, it identifies the smallest possible change set that resolves the issue. This reduces risk, simplifies code review, and prevents scope creep.
+ This skill starts once the cause is known or close to known. When the cause is still a mystery (intermittent failures, no stack trace), run `engineering/bug-diagnosis` first and come back with a confirmed cause.
- ## Use when
+ ## When to Use
- - The user asks to "fix a bug with minimal changes", "do a focused bugfix", or "scope a minimal repair"
- - A bug report needs triage to identify the smallest set of files to touch
- - A PR is at risk of scope creep (unrelated refactors, style changes, "nearby" fixes)
- - A hotfix or release-blocker needs a low-risk, reviewable change set
- - The user asks "what is the minimal change to fix X?" or "which files do I need to touch for this bug?"
+ - "Fix this bug with minimal changes", "do a focused fix", "what is the smallest change for X?"
+ - A hotfix or release-blocker that must be low-risk and quick to review.
+ - A fix PR at risk of scope creep (refactors, style changes, "nearby" fixes).
## Clarify First
Before scoping the fix, settle the inputs below. When one is missing or vague and would change the result, ask about it; when it wouldn't, use a sensible default and say which one you used.
- - **Observable failure:** the exact symptom, repro steps, and expected vs actual behavior (the bug description the analyzer matches against, and what the fix must resolve — not a guess at the cause).
- - **Codebase path & file types:** which directory and extensions to search (sets `--path`/`--extensions`).
- - **Minimal vs structural:** confirm this is a focused fix, not a refactor (determines whether out-of-scope "while I'm here" changes are rejected).
+ - **Observable failure:** exact symptom, repro steps, expected vs actual behaviour. This is what the fix must resolve, not a guess at the cause.
+ - **Expected scope:** which files or module the fix should touch, if known (becomes `--allow` for the scope check).
+ - **Minimal vs structural:** confirm a focused fix is wanted. If the honest fix spans several modules, stop and say so instead of forcing it into a "minimal" patch.
Ask about the two or three inputs that matter most for this request, not the whole list. If the user wants a result straight away, produce it and state your assumptions at the top.
+ ## Workflow
+
+ 1. **Reproduce.** Write the failing test first, from the bug report's inputs. It must fail for the reported reason before you change any code.
+ 2. **Locate.** Use the stack trace, `grep` for the failing function and its callers, and `git log -p` on the suspect file. `change_scope_analyzer.py` can suggest candidate files from the bug text when there is nothing better to go on; treat its list as a hint.
+ 3. **Patch** the root layer with the smallest change that makes the test pass. No renames, reformatting, or "while I'm here" edits.
+ 4. **Check the diff** with `diff_scope_check.py`: file and line budget, files outside the expected scope, whitespace-only hunks, bundled dependency changes, and whether a test changed.
+ 5. **Verify** that the new test passes and the existing suite still passes; both must pass before the PR opens. The scope check exits 0, or every finding has a one-line justification in the PR. The commit message names the bug and why the fix works.
+ 6. **Stop and escalate** if the fix needs more than the budget (default 3 files / 60 lines outside tests) or crosses module boundaries: propose a separate, scoped refactor instead.
+
## Quick Start
+ Check a fix before opening the PR, from a saved diff:
+
```bash
- # Analyze a bug description to identify minimal change scope
- python scripts/change_scope_analyzer.py --bug "Login fails when email has + character" --path ./src
+ python3 scripts/diff_scope_check.py --diff assets/sample_focused.diff --allow 'src/auth/*' # focused (exit 0)
+ python3 scripts/diff_scope_check.py --diff assets/sample_sprawling.diff --allow 'src/auth/*' # 5 findings (exit 1)
+ ```
- # Analyze with JSON output
- python scripts/change_scope_analyzer.py --bug "API returns 500 on empty array input" --path ./src --format json
+ Or straight from git, as a pre-PR checklist step:
- # Analyze with specific file extensions
- python scripts/change_scope_analyzer.py --bug "CSS overflow on mobile" --path ./src --extensions .css .scss .html
+ ```bash
+ git diff main...HEAD | python3 scripts/diff_scope_check.py --diff - --format json
```
- ## Tools Overview
+ Optional, when there is no stack trace to start from:
- | Tool | Purpose | Key Flags |
- |------|---------|-----------|
- | `change_scope_analyzer.py` | Identify minimal files to change for a bugfix | `--bug`, `--path`, `--extensions`, `--format` |
+ ```bash
+ python3 scripts/change_scope_analyzer.py --bug "Login fails when email has + character" --path ./src
+ ```
- ### change_scope_analyzer.py
+ ## Tools Overview
- Analyzes a bug description against a codebase to identify:
- - Files most likely related to the bug (keyword matching, import tracing)
- - Estimated change scope (number of files, lines)
- - Risk assessment for the change
- - Recommended fix approach (minimal vs. structural)
+ | Tool | What it does | Key flags | Exit codes |
+ |------|--------------|-----------|------------|
+ | `diff_scope_check.py` | Reads a unified diff; flags non-test files over budget, lines over budget, files outside `--allow`, whitespace-only hunks, lockfile/manifest changes bundled with code, and a missing test change | `--diff <file or ->`, `--allow <globs>`, `--max-files`, `--max-lines`, `--no-test-required`, `--format text|json` | 0 focused, 1 findings, 2 unreadable or not a diff |
+ | `change_scope_analyzer.py` | Keyword and import heuristics from the bug text to rank candidate files, estimate scope and risk | `--bug`, `--path`, `--extensions`, `--format human|json` | 0, 2 bad arguments |
- ## Workflows
+ `change_scope_analyzer.py` matches words in the bug text against file names and contents. It misses dynamic dispatch, reflection and config-driven paths, and it can rank files on generic words ("fails"). Prefer evidence (traces, tests, git history) over its ranking.
- ### Focused Bugfix Workflow
- 1. **Write a clear bug description** — reproduction steps, expected vs actual behavior
- - *Validate:* the description names the observable failure, not a guess at the cause
- 2. **Run `change_scope_analyzer.py` to identify scope**
- - *Validate:* analyzer output lists concrete files and an estimated line count
- 3. **Review the recommended files and approach**
- - *Validate:* recommended approach is "minimal" — if it says "structural", stop and scope a refactor PR separately
- 4. **Make ONLY the changes needed to fix the bug**
- - *Validate:* `git diff --stat` matches (or is smaller than) the analyzer's recommendation
- 5. **Verify no unrelated changes leaked in**
- - *Validate:* `git diff` shows no formatting-only changes, no unrelated imports, no "while I'm here" edits
- 6. **Submit PR with focused change set**
- - *Validate:* commit message states the exact bug fixed, and a regression test is included
+ ## Anti-Patterns
- ### Scope Validation
- 1. After making changes, re-run analyzer
- 2. Compare actual changes against recommended scope
- 3. Flag any out-of-scope modifications for separate PRs
- - *Validate:* any file touched that was not in the analyzer recommendation has a one-line justification or is reverted
+ **Mistake:** "While I'm here" refactors in the bugfix PR.
+ **Why it happens:** The surrounding code is in view and looks improvable.
+ **Instead:** Note it and open a separate `refactor:` PR. Mixed PRs multiply review time and mask regressions behind the real fix.
- ## Reference Documentation
+ **Mistake:** Reformatting the files you touch.
+ **Why it happens:** Format-on-save or habit.
+ **Instead:** Revert formatting-only hunks before committing; `diff_scope_check.py` lists them.
- - [Focused Fix Methodology](references/focused-fix-methodology.md) - Principles, anti-patterns, and decision framework
+ **Mistake:** Skipping the regression test because the fix is obvious.
+ **Why it happens:** A one-line change feels self-evidently right.
+ **Instead:** Every fix ships a test that failed before it. Obvious bugs come back during later refactors.
- ## Common Patterns
+ **Mistake:** Fixing the symptom in the wrong layer.
+ **Why it happens:** The first place the error shows is the easiest to patch.
+ **Instead:** Trace to where the bad value is produced and fix it there; otherwise the bug returns in a new form.
- ### Do
- - Fix the exact bug reported
- - Add a regression test for the fix
- - Document why the fix works in the commit message
- - Keep the diff as small as possible
+ **Mistake:** Bundling the fix with a dependency upgrade.
+ **Why it happens:** The upgrade seemed related.
+ **Instead:** Land the upgrade separately so either change can be rolled back alone.
- ### Don't
- - Refactor surrounding code during a bugfix
- - Fix "nearby" issues in the same PR
- - Change formatting or style in touched files
- - Add features disguised as bugfixes
+ ## Files
- ## Anti-patterns
+ - `scripts/diff_scope_check.py`, `scripts/change_scope_analyzer.py` — the tools above.
+ - `assets/sample_focused.diff` (one source file plus a test), `assets/sample_sprawling.diff` (five files, formatting noise, a dependency bump, no test).
+ - `evals/` — prompt cases and a grader for this skill's behaviour.
- | Anti-pattern | Failure mode | Fix |
- |--------------|--------------|-----|
- | "While I'm here" refactors in the bugfix PR | Blast radius explodes; review time multiplies; unrelated regressions masked by the real fix | Open a separate PR for the refactor, tagged as `refactor:` not `fix:` |
- | Reformatting or auto-save style changes in touched files | Diff becomes unreadable; real fix hidden in 200 lines of whitespace | Revert style changes before committing; configure the editor to format-on-save only for new files |
- | Fixing the symptom in the wrong layer | Bug returns in a new form; accumulates workaround debt | Trace to the root layer — analyzer's keyword-match output is a hint, not an answer |
- | Skipping the regression test "because the fix is obvious" | Bug silently returns on a refactor 6 months later | Every `fix:` commit adds at least one failing-then-passing test |
- | Treating `change_scope_analyzer.py` output as authoritative | Analyzer is keyword/import-based, not semantic — misses dynamic dispatch, reflection, config-driven paths | Use it as a starting set; grep for callers and tests before committing to the scope |
- | Bundling the fix with a dependency upgrade | Two risk profiles in one PR; if rollback is needed, both are lost | Land the upgrade separately, then the fix against the upgraded baseline |
+ ## Reference Documentation
+
+ - [references/focused-fix-methodology.md](references/focused-fix-methodology.md) — principles, decision framework for minimal vs structural fixes, and more anti-patterns.