# Sprouting Instead Of Editing

> Use when a symbol that must change is UNCOVERED and unpinnable (seam score < 0.5, or a skeletons/-flagged god file with no clean boundary) — adds behavior in a new fully-tested unit instead of editing legacy code in place. [Internal discipline — applied automatically by pipeline agents mid-task; direct use is a power-user path.]

- Skill: `cwijayasundara/sprouting-instead-of-editing` (Agent Skill)
- Install (CLI): `npx skillmds@latest add cwijayasundara/sprouting-instead-of-editing`
- Raw SKILL.md: https://api.skillmd.com/api/skills/cwijayasundara/sprouting-instead-of-editing/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra, Coding & Dev Tools
- Author: cwijayasundara (https://skillmd.com/u/cwijayasundara)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/cwijayasundara/sprouting-instead-of-editing

---


# Sprouting Instead of Editing

Feathers' escape hatch for the worst case: code you cannot pin must not be edited in place. Put the new behavior in a new unit you can TDD from scratch, and touch the legacy code at exactly one line.

## The Iron Law

```
IF YOU CANNOT PIN IT, DO NOT EDIT IT — SPROUT BESIDE IT
```

## Decision Table

| Situation | Move |
|---|---|
| Seam `total_score ≥ 0.5` and output observable | Not this skill — `REQUIRED SUB-SKILL: pinning-down-behavior` |
| Adding behavior inside an unpinnable function | **Sprout method/class**: new code in a new unit, called from one new line in the legacy body |
| Behavior must run before/after an unpinnable function | **Wrap method**: rename old → `_old_name`, create same-signature `name()` that calls `_old_name()` plus the addition |
| God file flagged in `specs/brownfield/skeletons/` | Default to sprout; never inline new logic into the god body |

## Process

1. Write the sprout as a brand-new module/class/function — the existing TDD gate applies in full (failing test first; `superpowers:test-driven-development`).
2. Touch the legacy file at **exactly one call line** (or the rename pair for wrap). Verify mechanically: the diff to the legacy file intersects one symbol; for the call-line check use the symbol ranges in `specs/brownfield/code-graph.json`. This is now enforced, not just claimed: pre-commit's `sprout-diff-one-symbol` gate (gap G30, `.claude/scripts/sprout-diff-gate.js`) BLOCKs when the legacy file's staged diff touches more than 2 distinct symbols (1 for a plain sprout call line, up to 2 for a legitimate wrap rename pair) — it fires only when `legacy-discipline-gate.js` (G17) already sees this file as UNCOVERED-with-evidence AND the evidence is a genuinely new production file (a sprout, not a pin-down).
3. Confirm with the code graph that no other caller of the legacy symbol changes behavior unintentionally (`edges` where `target` = the legacy file, `symbol_to` = the symbol).
4. Run the full suite plus any oracle tests from checking-coverage-before-change.

## Common Rationalizations

| Excuse | Reality |
|---|---|
| "I'll just quickly inline it" | Inlining into unpinned code is an unobserved behavior change. That is the definition of risk. |
| "The function is only 30 lines, editing is fine" | Size is not coverage. Unpinned is unpinned. |
| "A sprout adds indirection" | One extra call is cheaper than one silent regression. Fold it in later, under tests. |
| "The new lines match the existing style exactly" | Style fit is not coverage. Five untestable lines next to other untestable lines are still untestable. |
| "Last review mocked my 'enterprise abstractions'" | A sprout forced by an unpinnable call site is a coverage requirement, not architecture theater — say exactly that in the PR description, preemptively. |

## Red Flags — STOP

- More than one changed line in the legacy file (excluding the wrap rename pair)
- New logic appearing inside the legacy function body
- Sprout code written before its failing test
- Re-running seam-finder hoping for a score above the threshold — 0.31 is the answer, not a negotiation

## Checklist

- [ ] Sprout/wrap chosen via the decision table
- [ ] Sprout fully TDD'd as new code
- [ ] Legacy diff = one call line (or rename pair), verified against symbol ranges
- [ ] Full suite + oracle tests green
- [ ] The sprout's new/modified test file is staged in the SAME commit as the one-line legacy edit — satisfies the pre-commit `legacy-discipline-proof` gate (G17)'s evidence requirement for an UNCOVERED-verdict legacy file

New code gets tests; old code gets one line. No exceptions without your human partner's permission.

