Install
$ agentstack add skill-rolandbrecht-agent-skills-structural-refactor ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ● Dynamic code execution Used
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Structural Code Refactoring
This skill covers three complementary tools for structural code transformation. Use the right one for the job — they're not competitors, they're specialists.
Pick the Right Tool
| Situation | Best tool | |-----------|-----------| | Cross-language pattern replace (JS, Go, SQL, HTML…) | comby | | Rearrange/capture subexpressions across many files | comby | | Python 2 → 3 migration (print, unicode, exec, dict…) | 2to3 | | Modernizing Python 3 idioms (f-strings, type hints…) | pyupgrade | | True AST analysis (dead code, call graphs, imports) | ast-code-graph skill | | Simple string search | grep / ripgrep |
comby — Structural Search and Replace
comby understands code shape: balanced delimiters, string boundaries, comment regions. Unlike sed, you write patterns that look like the code you're targeting.
Match: request.get(':[url]')
Rewrite: request({ url: ':[url]', method: 'get' })
Wins over grep/sed when:
- The pattern spans multiple lines or contains nested brackets
- The same text appears in comments/strings and you want to skip it
- You need to capture and rearrange subexpressions
- You're working across multiple languages in one pass
Installation
which comby # check first
brew install comby # macOS
bash Linux PCRE note: if comby complains about libpcre, run:
> `sudo ln -s /usr/lib/libpcre.so /usr/lib/libpcre.so.3`
### Hole Syntax
Holes are the wildcards in comby templates. Choose carefully to avoid over-matching.
| Hole | Matches | Use when |
|------|---------|----------|
| `:[x]` | Zero or more chars (lazy, respects delimiters) | General expressions, argument lists, bodies |
| `:[[x]]` | One or more word chars (`[a-zA-Z0-9_]`) | Identifiers, variable names |
| `:[x~regex]` | PCRE regex | Constrained patterns (e.g., `:[n~[0-9]+]`) |
| `:[?x]` | Optional — can be absent | Optional arguments, trailing commas |
| `...` or `:[_]` | Unnamed match (discard) | Skipping parts you don't need |
`:[x]` respects nested delimiters: `foo(:[args])` captures `bar(a, b), c` correctly
even though there are inner parentheses.
Reusing a hole name enforces equality: `(:[x], :[x])` only matches identical values.
**String converters** (rewrite side only): `:[x.to_camelCase]`, `:[x.to_snake_case]`,
`:[x.uppercase]`, `:[x.lowercase]`, `:[x.Capitalize]`, `:[x.length]`
Full reference: [references/hole-syntax.md](references/hole-syntax.md)
### Basic Usage
```bash
# Match-only (no changes)
comby 'console.log(:[msg])' '' .js -match-only
comby 'console.log(:[msg])' '' .js -match-only -json-lines # structured output with line numbers
# Preview changes
comby 'var :[[n]] = :[v]' 'const :[[n]] = :[v]' .js -diff
# Apply
comby 'var :[[n]] = :[v]' 'const :[[n]] = :[v]' .js -i # in-place
comby 'var :[[n]] = :[v]' 'const :[[n]] = :[v]' .js -review # interactive y/n per match
# Scope to a directory
comby 'oldFunc(:[args])' 'newFunc(:[args])' .js -d src/
# Force a language parser
comby 'fmt.Println(:[args])' 'log.Printf("[DEBUG] %v", :[args])' -matcher .go -i
See [references/language-matchers.md](references/language-matchers.md) for all ~40 supported languages.
Standard Safe-Refactoring Loop
- Search — preview matches, zero risk:
comby 'pattern' '' .ext -d src/ -match-only - Inspect — verify hole captures with JSON: add
-json-lines | jq '.matches[].environment' - Diff — see exact changes:
comby 'pattern' 'rewrite' .ext -d src/ -diff - Apply — interactively or bulk:
-reviewor-i - Verify — re-run match-only and confirm zero results
Multi-Pattern Migrations (templates directory)
For several related patterns (e.g., an API overhaul), use a templates directory:
migrations/
├── get-to-fetch/
│ ├── match ← plain text match template
│ └── rewrite ← plain text rewrite template
└── post-to-fetch/
├── match
└── rewrite
comby -templates migrations/ .js -i
Name directories with 1-, 2- prefixes when order matters (more-specific patterns first).
JSON Pipeline for Scripting
# Extract all hardcoded strings from logger.error() calls with file + line
comby 'logger.error(":[msg]", :[rest])' '' .ts -d src/ -match-only -json-lines \
| jq -r '.uri + ":" + (.matches[].range.start.line | tostring) + " " + .matches[].environment.msg'
# Cover both quote styles
{
comby 'logger.error(":[msg]":[?rest])' '' .ts -d src/ -match-only -json-lines
comby "logger.error(':[msg]':[?rest])" '' .ts -d src/ -match-only -json-lines
} | jq -r '.uri + ":" + (.matches[].range.start.line | tostring)' | sort -u
JSON structure: { "uri": "...", "matches": [{ "range": { "start": { "line": N } }, "environment": { "varname": "..." } }] }
Pre-filtering Large Codebases
rg -l 'createStore' --include='*.js' \
| xargs comby 'createStore(:[args])' 'configureStore(:[args])' -matcher .js -i
Advanced: Rules, Regex Holes, Parallelism
# where clauses filter after structural match
comby ':[obj].:[method](:[args])' ':[obj].:[method]_v2(:[args])' .js \
-rule 'where :[method] != "config"'
# regex constraint in hole
comby 'setTimeout(:[fn], :[ms~[0-9]+])' 'setTimeout(:[fn], 0)' .js -i
# optional hole (matches with or without the argument)
comby 'db.query(:[sql], :[?cb])' 'db.queryAsync(:[sql])' .js -i
# parallel jobs for speed
comby 'pattern' 'rewrite' .py -i -jobs 8
comby Quick Reference
| I want to… | Command | |---|---| | Find matches (no changes) | comby 'pattern' '' .ext -match-only | | Preview as diff | comby 'pattern' 'rewrite' .ext -diff | | Interactive apply | comby 'pattern' 'rewrite' .ext -review | | Bulk apply | comby 'pattern' 'rewrite' .ext -i | | Scope to directory | add -d src/ | | Get JSON with line numbers | add -match-only -json-lines | | Multiple patterns | comby -templates dir/ .ext -i | | Filter matches | add -rule 'where :[var] != "value"' |
Common Pitfalls
:[x]vs:[[x]]: use:[[x]]for identifiers;:[x]captures spaces and expressions too.- Don't start templates with a hole:
:[x] = valis slow. Lead with a literal token. - Python / indentation-sensitive languages: comby doesn't model indentation. Always use
-reviewor-difffirst, and consider2to3/pyupgradefor Python-specific migrations (see below). - Empty rewrite = deletion:
comby 'pattern' '' .ext -ideletes all matches.
2to3 — Python 2 → 3 Migration
2to3 ships with Python and is the right tool for Python 2 → 3 migrations. It is AST-based, meaning it understands the language precisely — it handles every edge case that comby would struggle with: print >> sys.stderr, trailing commas, bare print, unicode, exec, dict.iteritems(), and more.
Use 2to3 instead of comby when migrating Python 2 code. Use comby for cross-language or library-API refactoring in Python 3 codebases.
2to3 Installation
Included with Python. Verify:
2to3 --version # should print "2to3 X.Y"
Common fixers
Each -f flag applies a specific transformation:
| Fixer | What it does | |-------|-------------| | print | print "x" → print("x"), handles all edge cases | | unicode | u"string" → "string", unicode → str | | exec | exec "code" → exec("code") | | dict | .iteritems() → .items(), .itervalues() → .values() | | xrange | xrange() → range() | | raise | raise E, V → raise E(V) | | except | except E, e → except E as e | | has_key | d.has_key(k) → k in d | | basestring | basestring → str |
Run 2to3 --list-fixes for the full list.
2to3 Usage
# Preview diffs without writing (safe)
2to3 -f print lib/
# Apply print fixer to all .py files in lib/
2to3 -f print -w lib/
# Apply ALL Python 2 → 3 fixers at once
2to3 -w lib/
# Apply multiple specific fixers
2to3 -f print -f unicode -f dict -w lib/
# No backup files (safe if you're in git)
2to3 -f print -w -n lib/
Safe Migration Workflow
# 1. Commit first — gives you a clean rollback point
git add lib/ && git commit -m "chore: snapshot before Python 3 migration"
# 2. Preview what will change
2to3 -f print lib/ 2>&1 | less
# 3. Apply
2to3 -f print -w -n lib/ # -n skips .bak files since you're in git
# 4. Verify no statements remain
grep -rn '\bprint ' lib/ --include='*.py' | grep -v 'print('
# 5. Syntax check every file
find lib/ -name '*.py' | xargs python3 -m py_compile && echo "All OK"
# 6. Run tests
python -m pytest lib/
Edge cases 2to3 handles correctly (that comby/sed would not)
| Python 2 pattern | Python 3 equivalent | |-----------------|---------------------| | print "msg" | print("msg") | | print "a", "b" | print("a", "b") | | print "msg", (suppress newline) | print("msg", end="") | | print >> sys.stderr, "msg" | print("msg", file=sys.stderr) | | print (bare, no args) | print() |
pyupgrade — Modernize Python 3 Code
pyupgrade upgrades Python 3 code to use modern idioms. It is not about Python 2 → 3 — it is about moving from old Python 3 style to current best practices. Use it on codebases that are already Python 3 but were written a long time ago.
pyupgrade Installation
pip install pyupgrade
# or: pipx install pyupgrade
pyupgrade Usage
# Upgrade to minimum Python 3.8+ idioms
find lib/ -name '*.py' | xargs pyupgrade --py38-plus
# Upgrade to Python 3.10+ (adds match statement patterns, etc.)
find lib/ -name '*.py' | xargs pyupgrade --py310-plus
# Preview mode (dry run)
pyupgrade --py38-plus --check myfile.py
What pyupgrade does
| Old style | Modern equivalent | |-----------|------------------| | 'hello %s' % name | f'hello {name}' (f-strings) | | '{}'.format(x) | f'{x}' | | from typing import List, Dict | list, dict (built-in generics, 3.9+) | | Optional[X] | X \| None (union syntax, 3.10+) | | super(Cls, self).__init__() | super().__init__() | | six.text_type | str | | Old-style u"" string prefixes | plain "" |
Combine with 2to3 for a full Python 2 → modern Python 3 migration
# Step 1: Migrate Python 2 → 3
2to3 -w -n lib/
# Step 2: Modernize Python 3 idioms
find lib/ -name '*.py' | xargs pyupgrade --py38-plus
# Step 3: Format
black lib/
# Step 4: Verify
python -m pytest lib/
Reference Files
- [references/hole-syntax.md](references/hole-syntax.md) — Full comby hole type reference with examples for every variant
- [references/language-matchers.md](references/language-matchers.md) — All ~40 supported comby language matchers
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: rolandbrecht
- Source: rolandbrecht/agent-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.