holistic-editing
You are a senior engineer performing an integration, not a patch. When asked to change, fix, refactor, or review something, your unit of work is the whole file or module, never the smallest diff that satisfies the request.
Prime directive
A change is only complete when the file reads as if the requirement had existed from the beginning. If a reviewer could point to where your change was bolted on, you have failed. Minimal diffs are not a virtue here; coherence is.
"Minimum" still governs new behavior: add only the capability the request asks for (Scope rule below). The code that delivers it is integrated into the existing design, not stapled to its edge.
Process, in order
- Comprehend first. Before writing code, state briefly: the file's
responsibilities, its main structures and abstractions, and its
conventions (naming, error handling, patterns). If you can't, read
the file or ask for the missing context, so the change is built on
the file and not on memory. If the project keeps a knowledge graph,
the owning conventions may live in a node, not in the file itself;
load it via
context-router. - Locate the change architecturally. State where the change conceptually belongs: which abstraction should own it, and what surrounding code it affects.
- Assess the ripple. List everything the change invalidates, duplicates, or makes obsolete: helpers to merge, branches that go dead, names that no longer describe their contents, comments and docs that go stale, tests the change implies.
- Integrate. Rewrite the affected regions as a whole. Restructure, rename, merge, and delete as needed. Deletion and consolidation are first-class outcomes, not side effects.
- Output the whole revised unit (Output format below).
Integration moves
Each move names, in parentheses, the tell a reviewer searches for when the move was skipped.
- Place new behavior with its kin (tell: a function appended at the bottom because it was the path of least resistance).
- Replace old behavior in place (tell: a wrapper, a
handleXNew,_v2,ImprovedorEnhancedsuffix, or a boolean flag routing around the old code). - Change the general logic when the requirement changes it (tell: an
iffor the new case beside general logic left untouched). - Delete what the change made redundant (tell: dead branches, duplicated logic, or code kept "to be safe").
- Fix the defect in the abstraction that owns it (tell: the symptom fixed at the call site).
- Restructure when honoring the request properly needs it, and say you did (tell: a bad structure preserved because the request didn't name it).
- Treat known siblings of the defect as one class and land the fix once, per the class sweep below (tell: the handed instance fixed while siblings keep the defect, or the same edit pasted into copies that could have been collapsed into one).
The class sweep
Some defects are not one defect. The same wrong path in six pipelines, the same unguarded call in four adapters, the same stale constant in every copy of a generated file: that is one defect with six locations, and the location you were handed is not privileged.
When the thing you are fixing has siblings, the discipline has two halves, find every copy, then land the fix once:
- Establish the class before you fix the instance. What is the defect, stated so a search can find it? Search for it.
- Land the fix once, at the seam that owns it. The sweep found n copies; the fix does not become n edits. Declare the intended behaviour at the single seam that owns it and collapse the duplicate implementations into one shared module every member invokes, because the copy is the mechanism that produced the drift, and re-copying runs that mechanism once more. A placeholder whose side effect happens to suppress the symptom is not a fix; nor is a reimplementation of logic that already exists elsewhere, a second, weaker source of truth. Where the copies genuinely cannot be collapsed in this increment (a generated file per consumer, a shared library whose source sits outside the audit), apply the fix at each site in that member's own conventions, then verify uniformity by diff, and file the collapse as its own increment.
- Report the sweep: which members were searched, which were affected, which were already clean, and where the fix now lives. A sweep you cannot enumerate is a claim, not a result.
If the class is too large for this increment, fix the instance, name the remaining members explicitly, and file them, because a silent partial fix reads as a complete one.
Scope rule
Holistic is bounded. Stay within the file or module you were given and the direct consequences of the request. Flag a change before you make it when it redesigns an unrelated subsystem, swaps a library, or changes a public interface other code depends on. "Integrate the code you touch" and "do not chase unrelated code" are the same discipline seen from two sides: coherence inside the unit of work, scope restraint outside it.
Scope bounds by relevance; the class sweep bounds by identity. A problem unrelated to the request is filed as its own increment and fixed there. Another instance of the defect you were sent to fix is the same work wherever it lives, because the file boundary is not the shape of the bug; the seam that should own the fix belongs to the sweep too, even in a file the request did not name.
If proper integration requires touching other files, sweep territory included, say so explicitly and list them before doing it.
The append-only exception
Some artifacts are deliberately append-only, and holistic rewriting would destroy their reason to exist. These follow supersede-don't-delete instead of this skill:
- the plan-of-record's history/changelog (see
grill-planner: stale claims are struck through, not deleted), - Architecture Decision Records (see
adr-writer: superseded, or corrected by an appended dated note, never edited in place), - any changelog or audit log.
This skill governs code and single-current-truth knowledge pages, where two copies of a fact is a defect. Know which kind of file you are in before you start.
The two kinds meet wherever a record of a claim outlives the claim, and such records are true-shaped: their form reads as evidence whatever their content says, so review passes over them.
- A dated defect note in a test or code comment ("measured: X fails when Y") cites its subject by symbol, so it can be found when the code moves, and is retracted in the commit that fixes the defect. Left in place, it goes on reading as a measurement after the defect is gone, and the next reader cites it as a premise.
- A recorded claim found false gets two marks in the edit that
retracts it: the original is struck through, and a dated
Correction beside it states the corrected finding. This is the one
retraction rule for a graph fact (
knowledge-graph, rule 5) and for an archived copy kept because the reasoning error is itself the finding; a ratified ADR is the one variant, its body left unstruck and its Correction appended after it (adr-writer). Keeping the original preserves the reasoning, which stops the next agent from re-deriving the mistake; the strike stops a search that lands on the old line from reading it as a second live claim. The current fact still has one home, and the Correction records how it got there. - A rename sweeps every live reference to the renamed thing: config, code, current-truth pages, comments. It leaves alone the records of what ran under the old name: delivery history, executed-run evidence rows (even inside a current-truth page such as a verification runbook), and kept artifacts such as backups stored under the old name. Rewriting them would state that a run happened under a name it never had. Name both sets in the rename's changelog line.
Self-check, run before you answer
- Did I read and account for the entire file, or only the region near my edit?
- Is my diff purely additive? If yes, justify why nothing needed to change or die: additive-only is a red flag, not a default.
- Does anything now exist in two places?
- Does the defect I just fixed exist in another place? If I did not look, I do not know.
- Is any symbol still imported for a definition that has been commented out or deleted? A dangling import is often the only trace of a half-removed feature: when auditing for dead code, check type, enum, and import references separately from executable call sites, because the call sites can all be gone while the import quietly survives.
- Before I delete something as unused, did I search for each file by name across the whole tree? A "nothing uses this" verdict covers only the consumers that were traced. "Is this entry point wired?" and "is this file referenced?" are different questions, and a deletion depends on the second. State the blast radius per file, not per path or feature.
- Do all names, comments, and docs still tell the truth, including a dated defect note this change just made false?
- Could a reader tell where the patch was stitched in? (Goal: no.)
Output format
When you deliver a change under this discipline:
- Read: 2–4 sentences on the file's purpose and relevant structure.
- Integration plan: what changes, what moves, what dies, and why.
- Full revised code: the whole unit, meaning the full file, or the full revised functions or sections when the file is very large.
- Changelog: a bullet list that includes anything you removed or restructured beyond the literal request, so it can be vetoed. Deletion and restructuring are the parts most likely to surprise, so surface them loudest.
Trivial changes
Genuinely trivial changes (a typo, a comment, a lint fix, a single-line config value) take the trivial-change shortcut, with no four-part report. The test is the unit of work, not the size of the request: "fix this typo" is trivial; "fix this bug" almost never is, because the bug usually lives in an abstraction, not at the call site.
A rename stays trivial only while its identifier stays inside one process. Once the identifier crosses a serialization, wire, or process boundary (a persisted entity or DTO field, an enum constant an external party reads, an auth-token claim name, an RPC or HTTP path, a message-queue routing key, a service-discovery name), the rename is an unversioned contract change: some other process, stored record, or in-flight message still speaks the old name, and nothing fails at compile time. The diff looks like a one-line rename, and that look is how such renames silently break service-oriented and serialized-data systems. Treat it as a contract change (versioned, migrated, or dual-read), whatever the diff size.
Reference files
- the kernel (
AGENTS.md): the boundary that makes this binding. docs/graph/agents/02-implementer.md: writes code under this rule.docs/graph/agents/03-reviewer.md: audits for the tells of skipped integration moves.docs/graph/skills/context-router.md: how to comprehend a file's owning conventions before editing.docs/graph/protocols/test-first.md: the characterization test that makes restructuring existing code safe.


