# How the Refactor Tool Previews and Applies Renames in code-review-graph

> Learn how the refactor tool previews and applies renames in code-review-graph. Discover its atomic edit plan, path-traversal protection, and automatic expiry cleanup for safe refactoring.

- Repository: [Tirth Kanani/code-review-graph](https://github.com/tirth8205/code-review-graph)
- Tags: how-to-guide
- Published: 2026-07-25

---

**The refactor tool implements a thread-safe preview-then-apply workflow where `rename_preview` generates an atomic edit plan stored in memory, and `apply_refactor` executes or dry-runs those changes with path-traversal protection and automatic expiry cleanup.**

The `tirth8205/code-review-graph` repository provides a static-analysis-driven refactoring engine for Python codebases. Its refactor tool enables safe, auditable renaming of symbols by combining graph-based dependency tracking with atomic file operations and concurrency controls.

## Thread-Safe Preview Storage Architecture

The refactoring workflow centers on a global registry that maintains pending operations while preventing race conditions. According to the source code in [`code_review_graph/refactor.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/refactor.py), the system uses:

- **`_pending_refactors`**: A global dictionary that holds pending refactor previews (lines 51-55).
- **`_refactor_lock`**: A `threading.Lock` instance that guards all access to the pending dictionary.
- **`_cleanup_expired`**: A background routine that removes previews older than `REFACTOR_EXPIRY_SECONDS` (10 minutes).

This architecture ensures that multiple threads can safely generate and apply renames without corrupting the preview state.

## Generating Rename Previews with `rename_preview`

The `rename_preview` function (lines 73-78) initiates the workflow by analyzing the code graph to build a complete list of required edits.

### Locating Symbol Definitions and References

The function accepts a `GraphStore` instance, the `old_name` symbol, and the desired `new_name`. It locates the target node via `store.search_nodes`, preferring an exact name match but falling back to the first candidate if necessary.

### Building the Edit List

Once the symbol is identified, the tool collects four categories of modification sites:

1. **Definition sites**: The node itself where the symbol is defined.
2. **Call sites**: Edges of kind `CALLS` targeting the symbol.
3. **Bare-name call sites**: Direct invocations without module qualification.
4. **Import sites**: Edges of kind `IMPORTS_FROM` referencing the symbol.

Each site is converted into a dictionary describing the file path, line number, old text, new text, and confidence level. A unique `refactor_id` (generated via `uuid.uuid4().hex[:8]`) tags the complete preview, which is then stored in `_pending_refactors` before being returned to the caller.

## Applying Renames with `apply_refactor`

The `apply_refactor` function (lines 702-78) consumes the stored preview to modify files or generate diffs.

### Safety Validation and Path Protection

Before touching any files, the function validates the `refactor_id` against the registry, checking both existence and expiry. It then implements **path-traversal protection**: each edit’s target file is resolved and verified to lie within the specified `repo_root`. If any file attempts to escape the repository boundary, the operation aborts immediately.

### Grouping and Executing Edits

Edits are **grouped by file** to handle multiple replacements within a single document efficiently. For each file, the tool applies edits sequentially against the evolving content, ensuring that line number shifts from earlier edits do not corrupt later replacements.

### Dry-Run Mode and Diff Generation

When `dry_run=True`, the function uses `difflib.unified_diff` to generate line-level diffs without writing to disk. This mode preserves the preview in `_pending_refactors`, allowing developers to inspect changes before committing. In non-dry-run mode, the new content is written back to each file, edit counts are tallied, and the preview entry is atomically removed from the registry.

## Complete Workflow Example

The following demonstrates the full preview-and-apply lifecycle:

```python
from pathlib import Path
from code_review_graph.refactor import rename_preview, apply_refactor

# 1️⃣ Generate a preview for renaming `old_func` → `new_func`

preview = rename_preview(store, "old_func", "new_func")
print(preview["refactor_id"], preview["edits"])

# 2️⃣ Inspect the preview (dry‑run) – see diffs without touching files

dry_result = apply_refactor(
    preview["refactor_id"],
    repo_root=Path("/path/to/repo"),
    dry_run=True,
)
print(dry_result["diffs"]["some/file.py"])

# 3️⃣ Actually apply the rename

apply_result = apply_refactor(
    preview["refactor_id"],
    repo_root=Path("/path/to/repo"),
    dry_run=False,
)
print(apply_result["files_modified"])

```

## Summary

- **Thread-safe storage**: A global locked dictionary `_pending_refactors` stores previews for 10 minutes, guarded by `_refactor_lock` (lines 51-55).
- **Graph-driven analysis**: `rename_preview` uses `GraphStore.search_nodes` and edge traversal to collect definition, call, and import sites, generating a unique ID for each operation (lines 73-78).
- **Safety mechanisms**: Path-traversal validation, expiry enforcement, and atomic file grouping prevent unsafe or partial updates.
- **Dry-run support**: `apply_refactor` generates unified diffs via `difflib.unified_diff` when `dry_run=True`, enabling inspection before permanent writes.

## Frequently Asked Questions

### How does the refactor tool prevent stale previews from being applied?

The tool enforces a 10-minute expiry window via `REFACTOR_EXPIRY_SECONDS` and the `_cleanup_expired` routine. Before applying any changes, `apply_refactor` validates that the `refactor_id` exists in `_pending_refactors` and has not exceeded the expiry limit, returning an error if the preview is missing or outdated.

### What protects against directory traversal attacks when applying renames?

The `apply_refactor` function in [`code_review_graph/refactor.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/refactor.py) resolves each edit’s target file path and verifies that it lies within the specified `repo_root` directory. If any file path attempts to escape the repository root, the operation aborts immediately to prevent directory-traversal vulnerabilities.

### Can I preview changes before writing them to disk?

Yes. By passing `dry_run=True` to `apply_refactor`, the tool generates unified diffs using `difflib.unified_diff` for each affected file without persisting changes. This preserves the preview in `_pending_refactors` for a subsequent non-dry-run application.

### How does the tool handle concurrent rename operations?

Access to the global `_pending_refactors` dictionary is synchronized via `_refactor_lock`, a `threading.Lock` instance defined in [`code_review_graph/refactor.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/refactor.py). This ensures that only one thread manipulates pending previews at a time, preventing race conditions during both preview generation and application.