# Change Discipline

> Applies to every edit of something that already exists, however small — an in-place bug fix, a refactor, a config or default value, a schema, a test expectation, a contract. Establish why the current behavior exists before changing it, treat a symptom or a reported root cause as a lead, not a spec, check callers and blast radius, and choose how wide a bug fix should be. Also when removing a resource, path, or symbol, when applying fixes from a review round, before a command touching login state, auth files, network egress, or a process the user is running, when an empty search or clean git status may be an ignore-rule illusion, for read-only subagent audits, when a finding would reverse the user's own call, for unexplained working-tree changes, for a file synced across repos, and for an edit's boundary or style dimension — dependency direction, public interfaces, config migrations, splitting a module that mixes responsibilities, idempotent re-runs, new abstractions, naming, input validation, comments.

- Skill: `ly87ing/change-discipline` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add ly87ing/change-discipline`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ly87ing/change-discipline/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: ly87ing (https://skillmd.com/u/ly87ing)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/ly87ing/change-discipline

---


# Change Discipline

Applies whenever you edit something that already exists. The hard gate below is a
lightweight always-on habit and runs on every such edit. The two facets — boundary
and style — apply only when the change actually has that dimension, and live in
bundled references so a routine edit does not carry them.

## Before you change anything that already exists (hard gate)

Applies to EVERY edit of existing code, a config/contract value, a default, a test expectation, or any behavior — not only bug fixes. The mistake this prevents: treating a symptom (a failing test, a "wrong-looking" value/regex/field name, a reported defect) as the spec and flipping the code to match it, thereby re-introducing a bug a previous commit deliberately fixed.

- **Establish WHY the current state is what it is, before changing it.** When version-control history exists, run `git log -S "<symbol/value>"`, `git log -L`, or `git blame` on the exact line/symbol/default you intend to change. Current behavior is often the deliberate result of an earlier `fix:[#…]` / `feat:`; the commit message + bug id tell you the intent. When the file or symbol is untracked or history is unavailable, state that limit and reconstruct intent from the primary evidence that remains — current callers and consumers, schemas or contracts, neighboring tests and docs, and observed behavior. Missing history is neither permission to guess nor an automatic blocker. A real near-miss: a removed dangerous-char `;` (its removal had fixed a log-download bug) — re-adding it to satisfy a stale test would have re-opened the bug.
- **A symptom is not a spec — decide DIRECTION from history + the real contract, not from the symptom.** Current behavior is a deliberate later decision → the test/expectation is **stale**: update it and record which commit/bug voided the old one. The test/contract is right and the code is incomplete (e.g. committed together yet contradicting) → change the code. Never flip a value or loosen a check just to make a symptom disappear.
- **Grep every caller/consumer of a shared symbol, signature, contract field, or config key before changing it, and run the neighboring tests.** A change that satisfies one site can break a real caller (e.g. a guard added for one test that blocks an automatic background path). Widening/loosening can't break existing-valid inputs but can have a security blast radius — check that too. When removing a resource, path, mount, or name, also grep its literal string across manifests, scripts, command strings, config, and docs — structured references are not the only references. Case: a shared volume was removed from a manifest's mounts and volume list, but the init container's `chown -R` argument list still carried the path; the container failed on the missing directory and the service never started.
- **An empty search and a clean `git status` can both be ignore-rule illusions.** Before concluding "nothing matches" from a search run at a repo or hub root, check whether ignore rules silently excluded the directories you meant to search (nested sub-repos, dist dirs) and re-run with ignore rules off or with explicit paths. Symmetrically, a new file on an ignored path leaves `git status` clean without being tracked — `git add -f` (or fix the rule) and confirm it actually entered the commit. A real case: a hub-root search nearly concluded a whole codebase did not exist.
- **Proving the change works belongs to `verification`.** The narrowest failing test read from its real result, no new regression against a clean baseline, real-input comparison against the pre-change build, and the deployed-versus-verified distinction are that skill's ladder. When it is unavailable, run the narrowest test that exercises the change and read the real result, never a piped command's exit code.
- **Review fixes are new changes and get the same review.** After applying a review round, re-review the fixed diff with the same rigor before declaring it done; a fix that changes a name, an identity, an ordering, or a concurrency property is a new design decision, not a patch. Case: across four review rounds nine severe findings surfaced and six of them were introduced by the previous round's fixes, including a content-hash file name that turned a rare overwrite into a routine one.
- **Confirm the diagnosis from independent angles before changing code — a wrong root cause yields a confident wrong fix.** Reason from the system's own observed behavior, not from the first hypothesis offered: a report that "it's X" — from the user, a teammate, or another agent — is a lead, not a verdict, so think it through from a blank slate. When two sources disagree about the cause, reconcile them with evidence rather than adopting the convenient one; the more consequential or non-obvious the cause, the more independent confirmation it needs (ideally two lines of evidence converging) before you act on it. If the fix that follows is large, cross-cutting, or otherwise high-risk, surface the diagnosis and the options and let the user choose the direction instead of proceeding silently. Choose the fix's scope on a ladder: never a special case for the reported sample; a generic rule inside the component you control; and never a contract pushed onto producers or upstreams you do not control. Case: a versioned-name filter was first patched for one sample name, rejected as a special case, then redesigned as a cross-producer identity contract, rejected as too large; the accepted fix was a field-level generic rule inside the one service that owned the comparison.
- **If history shows the change sits on an unresolved design / security / contract decision, stop and escalate with the evidence instead of silently picking a side.** Leaving it unchanged-and-flagged is a valid outcome; quietly satisfying a symptom you lack the authority to interpret is not.
- **Escalation is for genuine uncertainty, not for a call the user has already made — and that call stays made when a review wants to reverse it.** When the user has explicitly decided the direction or authorized the action, carry it out — do not re-confirm, re-litigate, or stall because a subagent or another agent disagrees; the stop-and-escalate above is reserved for an unconfirmed root cause, an unresolved design/security/contract decision, or a large, cross-cutting, or high-risk change. A verified finding — from a review pass, a subagent, or your own later re-read — about something the user deliberately removed, rejected, or chose is a recommendation to raise, not a defect to apply: check history for the deliberate decision (`git log -S`, `git log --diff-filter=D`), surface it, and let the user rule. Naming it in your summary while applying it in the same pass is not consent. A real case: a review flagged a line the user had explicitly deleted as "missing", and the agent restored it — reversing the user's call and then defending the reversal in the file.
- **Working-tree changes you did not make belong to a human until proven otherwise.** When files you never touched show up `deleted`/`modified`, assume the user edited them by hand and ask before acting on them; never `git restore .` / `git checkout .` / `git clean` over unexplained changes, and never attribute them to a sync service, an editor, or an environment glitch without first gathering evidence for that attribution. If you must clear the tree, `git stash` to preserve the state first. A real case: an agent blamed a batch of unexplained deletions on cloud-sync drift — on a machine with no such sync configured — and bulk-restored, silently undoing the user's own manual cleanup.
- **Commands that touch login state, auth files, network egress, or a process the user is running are irreversible-class.** `login`, `logout`, writing or replacing an auth file, changing a fail-closed or routing rule, restarting a router or browser, and exercising an agent the user is currently using each need the impact stated first and an explicit go-ahead; never run them as a side effect of exploration or testing. Case: a background probe of the CLI login commands invalidated the user's own session, and a proposed auto-login-and-poll flow was rejected for the same reason.

## Bundled Resources

Load the facet the change actually has instead of carrying both on every edit:

- When the change touches a boundary — a shared symbol, a public interface, dependency direction, a new abstraction about to be introduced, a config/schema contract with a rollback path, a file that exists as copies across repos, a module that mixes responsibilities, a re-runnable setup/migration that must stay idempotent, or an audit or exploration delegated to subagents that must stay read-only — read [references/boundaries-and-contracts.md](references/boundaries-and-contracts.md).
- When the change has a style, naming, input-validation, or comments dimension — including serialized config where an unquoted value can change how the file parses, and the final diff re-read before declaring done — read [references/style-and-validation.md](references/style-and-validation.md).

