# Safe Refactoring: Refactor Without Changing Behavior

Safe Refactoring: Refactor Without Changing Behavior is a SKILL.md that refactors, cleans up and modernizes Python, JavaScript, TypeScript and React code without changing what it does; an agent buys it once for $0.05 over x402.

- Page: https://aiskills402.com/skills/safe-refactoring
- Category: Code & Engineering (https://aiskills402.com/categories/code)
- Price: $0.05 once, USD-priced, paid in USDC on Base over x402. Price as loaded on this page. The 402 response your agent receives is authoritative.
- Version: 1.0.1
- Card (JSON): https://api.aiskills402.com/v1/skills/safe-refactoring

## Use it when

Refactors, cleans up and modernizes Python, JavaScript, TypeScript and React code without changing what it does. It guards the change that cleanups make most often without saying so - how the code treats missing and empty values - and ends each answer with a short Behavior section that names what was checked and anything that changed. Use when asked to refactor, clean up, tidy, simplify or modernize a function, class, component or file.

## Not for

Changing what code does on purpose, such as a bug fix or a new feature, performance tuning, and moving code between frameworks. It reads the code you paste and does not run your tests, so code with heavy side effects needs a test of its own. Languages other than Python, JavaScript, TypeScript and React get a short note, not full rules.

## Tested, honestly

Tested 2026-11-13.

- Strong model (claude-sonnet-5-5 (Claude Code alias "sonnet")): Right on all 20 runs, scored by running the code it wrote next to the original. In each of the nine cleanup cases it kept the old result for explicit None or null, zero and empty strings, or named the change: a discount list that may arrive as None kept its None check, a logger and a fetch helper kept working with a null options object, and a page size of zero kept its rule. The control was modernized with no difference. Every answer was a real rewrite and ended with a Behavior section.
- Weak model (claude-haiku-5-5 (Claude Code alias "haiku")): Right on all 10 cases, scored by running the code it wrote next to the original: no empty input changed its result without a line saying so in the Behavior section, the control was modernized with no difference, and each answer rewrote the code rather than leaving it as it was.

Note: Ten cleanup and modernize requests written by us in Python, JavaScript and React (nine where a rewrite can quietly change what a missing or empty value becomes, one control), each a bare request with no word about empty values. Scored by a script that runs the code from every answer and the original on the same inputs (explicit None or null, 0, an empty string or list, a left-out argument, a null options object) and lists each difference; whether the answer named a difference was read by hand against a rubric written before the first run. The pattern check in the case file only asks for an answer, so the count is that hand reading, not the generic comparison script. The language rules are public reference facts; nothing here is owner-measured. No check was widened after the runs. Sonnet two runs per case on each side, Haiku one.

### With and without the skill

Tested 2026-11-13.

- Runs with no silent empty-value change (10 cases): Sonnet 20/20 with, 11/20 without; Haiku 10/10 with, 7/10 without.

Same bare request and same scorer on both sides. Read by hand, Sonnet alone named its changes on a CSV export, a React avatar, a retry policy and a settings loader. It missed a discounts function where codes=None became an empty tuple called safe, so passing None raises; a logger and a fetch helper whose null options object now throws; a paginator where a branch it called dead still handled a page size of zero; and one of two runs on a price formatter (strictly 10 of 20, since the other run counted only for a general sentence). A probe with only the name and short description also gave 10 of 10: the gain comes from pointing Sonnet at empty values, and the body added nothing measurable on these cases. Haiku alone missed three: the null logger options, a null avatar name and backoff=None in a retry policy.

Same cases and the same checks with and without the skill. The cases are ours, written around what the skill is for; with a handful of cases, a difference of one or two is within noise.

Full summary: https://aiskills402.com/skills/safe-refactoring/tests

## Example

### English (claude-sonnet-5-5)

Before:

> from datetime import datetime DISCOUNTS = {     "WELCOME10": {"kind": "percent", "value": 10, "expires": datetime(2027, 1, 1)},     "BLACKFRIDAY": {"kind": "percent", "value": 25, "expires": datetime(2026, 12, 1), "exclusive": True},     "FIVEOFF": {"kind": "fixed", "value": 5, "expires": datetime(2026, 12, 31)}, } def apply_discounts(order, codes=None, now=None):     if codes is None:…

After:

> ```python from datetime import datetime DISCOUNTS = {     "WELCOME10": {"kind": "percent", "value": 10, "expires": datetime(2027, 1, 1)},     "BLACKFRIDAY": {"kind": "percent", "value": 25, "expires": datetime(2026, 12, 1), "exclusive": True},     "FIVEOFF": {"kind": "fixed", "value": 5, "expires": datetime(2026, 12, 31)}, } def _line_total(line):     qty = line.get("qty")     if qty is None:…

## What is in the file

- The habit
- The rules that decide the table
- Running the table
- Examples (patterns, not your code)
- What the answer looks like
- What stays the same

## Languages

Any language

## How to buy

Agent (HTTP):

1. GET https://api.aiskills402.com/v1/skills/safe-refactoring/file without a payment header. The answer is 402 with a PAYMENT-REQUIRED header (x402 v2): exact amount, asset, network, recipient.
2. Sign `accepts[0]` with an x402 client (for example @x402/core + @x402/evm).
3. Repeat the GET with the signature in the PAYMENT-SIGNATURE header. The answer is 200 with the file, its sha256 and a re-download token.

Agent (MCP): https://mcp.aiskills402.com/mcp — free tools search_skills, get_skill, redownload_skill. Buying itself is over HTTP.

Full flow: https://aiskills402.com/docs

## The file

- Version: 1.0.1
- Size: 9.7 KB (9963 bytes)
- SHA-256: 9b75b5182da38a71f052e8949c2e6fb0424fc2a2f81b6bcfabbbfabf7f64fec1
- Updated: 2026-11-13
- New versions are free through your re-download token.

## Versions

### 1.0.1 (2026-11-13)

One sentence in "What stays the same" reworded (no rule changed, not re-tested): its five-word ending was the same as a line in a free article on our blog, and paid wording must not appear there.

### 1.0.0 (2026-11-13)

First release. The idea is ours: a probe without a skill on 13 November 2026, where Sonnet, asked only to clean up code, turned an explicit-None sentinel into a parameter default and an `options || {}` line into destructuring, called it safe, and a caller passing None or null then got an error. No foreign skill or repository was opened.

What the file does: for each rewritten line that decides what a missing or empty value becomes, it puts the left-out argument, explicit None or null, the falsy values and a null options object through the old line and the new one; by default it keeps the old result in modern syntax, otherwise it names each difference. The answer ends with a Behavior section. Python, JavaScript, TypeScript and React, with a short note for PHP, Ruby, Kotlin, C# and Go.

Test on 13 November 2026: ten cases written by us (nine cleanup requests where a rewrite can quietly change an empty value, one control), each a bare request such as "Clean up this function." with no word about empty values. The rubric was written before the first run. Scored by a script that executes the code from every answer and the original on the same inputs (explicit None or null, 0, "", an empty list, a left-out argument, a null options object) and lists every difference; whether the answer named each difference was read by hand. The scorer was checked both ways: the original against itself gave no difference in 10 of 10 cases, and the four failures recorded in the probe were all caught. The pattern check in cases.json only asks for a non-empty answer, so compare-baseline was not used for the count. No check was widened after the runs.

Results: Sonnet, two runs per case, 20 of 20 with the file and 11 of 20 without (strictly 10: one bare run on the price formatter was counted right only for a general sentence that defaults cover undefined alone). Haiku, one run per case, 10 of 10 with and 7 of 10 without. With the file the scorer found no difference in any of the 30 answers, and each was a real rewrite (no `var` left, a third to three quarters of the lines new).

Before release the description was cut from 814 to 440 characters, so that it names the topic and the Behavior section but no longer carries the rule; the side with the file was re-run in full and gave the same 20 of 20 and 10 of 10. A probe with only the name and the short description, without the body, also gave 10 of 10 on Sonnet: on these ten cases the gain comes from pointing the model at empty values, and the body did not add a measurable difference. Harder cases were not measured.

Price 50000 ($0.05): Sonnet gained 9 runs of 20, which is the top row of the table, but the facts are public language rules, not owner-measured, so the lower tier.

## License

Perpetual, non-exclusive; use and modify for yourself incl. paid work; no resale or republishing. Holder: Georgi Kalchev, aiskills402.com. Terms: https://aiskills402.com/docs#license

## FAQ

### Exactly what lines get compared?

Every rewritten line that decides what a missing or empty value turns into: an is None or == null check, a fallback with or, || or ??, a parameter or destructuring default, a dictionary lookup with a fallback, a truthiness test, a tuple() or String() call riding along, and field defaults in a dataclass, pydantic model or TypeScript class. For each it compares the old and new line on a left-out argument, explicit None or null, zero, an empty string or list, and a null options object.

### Will it refuse to modernize my code?

No. It does the cleanup you asked for: modern syntax, less repetition, clearer names, smaller functions. Where the tidy form would give another result for an empty input, it keeps the old result in the new syntax, for example a null options object read through ?? before destructuring. If the new result is the better one, it makes the change and names the input, what it gave before and what it gives now.

### Does it help Claude Sonnet?

For empty values, clearly. We gave ten bare cleanup requests in Python, JavaScript and React, two runs each, and ran the old and new code on empty inputs. Alone, Sonnet quietly changed an empty value in 9 of 20 runs: None became an empty tuple, a null options object started to throw, a zero page size lost its rule. With the file it did so in none of 20. Haiku went from 7 to 10 of 10. To be fair: the short description alone also gave 10 of 10, so pointing the model at empty values does most of the work; the body added nothing measurable on these cases.

### What does the Behavior section say?

When nothing changed, one line naming the empty inputs it compared. Otherwise one line per difference: the input, the old result and the new one, plus the reason when the change was deliberate. An error that became a returned value counts as a difference. It leaves out warnings about inputs the old code did not handle either, so a reviewer reads only real changes.

## Related skills

- [Code Review](https://aiskills402.com/skills/code-review.md): $0.01 once
- [API Breaking Change Check for OpenAPI](https://aiskills402.com/skills/api-breaking-change-check.md): $0.03 once
- [Test Review: Can This Test Ever Fail?](https://aiskills402.com/skills/test-guard-review.md): $0.03 once
- [Tests from Spec: Cases from the Requirements](https://aiskills402.com/skills/tests-from-spec.md): $0.03 once

## Measurement limits

- Models other than the two named above were not run.
- Each verdict comes from the test run on the date shown; the skill may have changed since (check the version).
- Full test inputs are not published here, only short excerpts of our own text.
- Results on your own texts, languages and domains can differ.

Offer note: Paid in USDC (USD-pegged) over x402 by an AI agent; one-time.
