$npx -y skills add Borda/AI-Rig --skill rename-refsAtomic rename of Python symbols or modules via the structural index — static callers, import sites, __all__ re-exports, Sphinx cross-refs; optional deprecated alias (--deprecate) or hard-delete (--remove-if-no-callers). TRIGGER: "rename X to Y" (function/class/method/module), "mo
| 1 | <objective> |
| 2 | |
| 3 | Rename Python symbol or module atomically. Coverage: |
| 4 | - Definition site (def/class line) |
| 5 | - `__all__` re-exports in `__init__.py` files |
| 6 | - Import call sites across all callers (fn-rdeps + symbol line-range narrowing) |
| 7 | - Sphinx docstring cross-refs across `.py` and `.rst` |
| 8 | - Optional `@deprecated` alias via pyDeprecate (`--deprecate`; requires `pyDeprecate` installed) |
| 9 | - Optional hard-delete when exhaustive=true, zero callers |
| 10 | |
| 11 | **Subcommands**: |
| 12 | - `symbol <old_qname> <new_qname>` — function, class, or method. qname = bare (`MyClass`), qualified (`MyClass.method`), or full (`mypackage.auth::validate_token`) |
| 13 | - `module <old_module_path> <new_module_path>` — dotted path (`mypackage.old_name`). Renames file + all import lines. |
| 14 | |
| 15 | **Flags**: |
| 16 | - `--dry-run` — print sites that would change; no edits |
| 17 | - `--deprecate` — symbol only: keep old name as pyDeprecate `@deprecated` wrapper → new name; requires `pyDeprecate` |
| 18 | - `--since <ver>` / `--removed-in <ver>` — passed to deprecation decorator; default `"?"` |
| 19 | - `--remove-if-no-callers` — symbol only: delete definition when exhaustive=true + zero callers; requires confirmation |
| 20 | |
| 21 | **Hard limits** (static analysis boundary — not fixable): |
| 22 | - `getattr(obj, "old_name")` — string not statically bound; Step 6 emits grep advisory |
| 23 | - Cross-repo callers — out of scope; use `--deprecate` + semver bump for public APIs |
| 24 | |
| 25 | Routing: IDE/LSP rename wanting coverage preview only → run `--dry-run`. Handles 1:1 renames only. |
| 26 | |
| 27 | NOT for: building index (`/codemap:scan-codebase`); querying without rename intent (`/codemap:query-code`); non-Python files; renaming symbols in ABCs/Protocols where subclass overrides exist — overrides not tracked by static import analysis; review `fn-rdeps` manually, rename overrides explicitly. **Note**: no `--index <path>` support — always uses default project index. Monorepo: run `/codemap:scan-codebase --root <pkg>` first, then rename. |
| 28 | |
| 29 | </objective> |
| 30 | |
| 31 | <workflow> |
| 32 | |
| 33 | ## Step 0: Parse arguments |
| 34 | |
| 35 | Parse `$ARGUMENTS` in one Bash block; write all tokens to project-qualified tmpfiles — later steps read tmpfiles (shell vars die at each Bash() boundary): |
| 36 | |
| 37 | ```bash |
| 38 | # timeout: 5000 |
| 39 | export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}" |
| 40 | _CM_PROJ=$(git rev-parse --show-toplevel 2>/dev/null | xargs basename 2>/dev/null || echo "cm") |
| 41 | |
| 42 | SUBCOMMAND=$(echo "$ARGUMENTS" | awk '{print $1}') |
| 43 | OLD_REF=$(echo "$ARGUMENTS" | awk '{print $2}') |
| 44 | NEW_REF=$(echo "$ARGUMENTS" | awk '{print $3}') |
| 45 | |
| 46 | echo "$ARGUMENTS" | grep -q -- '--dry-run' && DRY_RUN="true" || DRY_RUN="false" |
| 47 | echo "$ARGUMENTS" | grep -q -- '--remove-if-no-callers' && REMOVE_IF_ZERO="true" || REMOVE_IF_ZERO="false" |
| 48 | |
| 49 | # POSIX sed — avoids PCRE lookbehind, works on macOS BSD sed |
| 50 | SINCE_VER=$(echo "$ARGUMENTS" | sed -n 's/.*--since \([^ ]*\).*/\1/p' || echo "") |
| 51 | REMOVED_IN_VER=$(echo "$ARGUMENTS" | sed -n 's/.*--removed-in \([^ ]*\).*/\1/p' || echo "") |
| 52 | |
| 53 | case "$SUBCOMMAND" in |
| 54 | symbol|module) ;; |
| 55 | *) printf "Usage: /codemap:rename-refs symbol <old> <new> [flags] | module <old_path> <new_path> [--dry-run]\n" >&2; exit 1 ;; |
| 56 | esac |
| 57 | |
| 58 | # conflicting flags — --deprecate creates alias, --remove-if-no-callers deletes; incompatible |
| 59 | echo "$ARGUMENTS" | grep -q -- '--deprecate' && [ "$REMOVE_IF_ZERO" = "true" ] && { printf "⚠ conflicting flags: --deprecate creates alias, --remove-if-no-callers deletes target; these are incompatible\n" >&2; rm -f "${TMPDIR:-/tmp}/codemap-${_CM_PROJ}-rename-REMOVE_IF_ZERO-${CSID}"; exit 1; } |
| 60 | |
| 61 | printf '%s' "$SUBCOMMAND" > "${TMPDIR:-/tmp}/codemap-${_CM_PROJ}-rename-SUBCOMMAND-${CSID}" |
| 62 | printf '%s' "$OLD_REF" > "${TMPDIR:-/tmp}/codemap-${_CM_PROJ}-rename-OLD_REF-${CSID}" |
| 63 | printf '%s' "$NEW_REF" > "${TMPDIR:-/tmp}/codemap-${_CM_PROJ}-rename-NEW_REF-${CSID}" |
| 64 | printf '%s' "$DRY_RUN" > "${TMPDIR:-/tmp}/codemap-${_CM_PROJ}-rename-DRY_RUN-${CSID}" |
| 65 | printf '%s' "$REMOVE_IF_ZERO" > "${TMPDIR:-/tmp}/codemap-${_CM_PROJ}-rename-REMOVE_IF_ZERO-${CSID}" |
| 66 | printf '%s' "$SINCE_VER" > "${TMPDIR:-/tmp}/codemap-${_CM_PROJ}-rename-SINCE_VER-${CSID}" |
| 67 | printf '%s' "$REMOVED_IN_VER" > "${TMPDIR:-/tmp}/codemap-${_CM_PROJ}-rename-REMOVED_IN_VER-${CSID}" |
| 68 | |
| 69 | # bare names |