# How the code‑review‑graph Refactoring Tool Previews and Applies Code Changes Safely

> Safely preview and apply code changes with the code-review-graph tool. Learn how its two-stage workflow and security features ensure verified refactors before files are modified.

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

---

**The `code-review-graph` tool uses a two‑stage preview‑then‑apply workflow with expiry‑based sessions, path‑traversal guards, and dry‑run diffs to ensure every refactor is verified before any file is modified.**

This article explains how `tirth8205/code-review-graph` implements safe, reversible code refactoring through its Model Context Protocol (MCP) tools. The system separates **preview generation** from **change application**, giving developers full visibility into edits before they touch disk.

## The Two‑Tool Architecture

The refactoring system is exposed through two coordinated tools in **[`code_review_graph/tools/refactor_tools.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/tools/refactor_tools.py)**:

| Tool | Purpose |
|------|---------|
| `refactor_tool` | Generates a preview of rename operations, dead‑code reports, or community‑driven suggestions |
| `apply_refactor_tool` | Commits a previously‑previewed edit set, with mandatory validation and optional dry‑run |

Both delegate to the core logic in **[`code_review_graph/refactor.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/refactor.py)**, which orchestrates graph‑driven analysis and safe file operations.

## Generating a Refactor Preview

When `mode="rename"` is requested, `refactor_func` invokes `rename_preview` (lines [70‑86] of [`refactor_tools.py`](https://github.com/tirth8205/code-review-graph/blob/main/refactor_tools.py)). The preview pipeline executes four distinct phases:

### Node Lookup via GraphStore

The system queries `store.search_nodes(old_name, limit=10)` to locate the target symbol, preferring exact matches over partial results (lines [73‑94] in [`refactor.py`](https://github.com/tirth8205/code-review-graph/blob/main/refactor.py)). The **GraphStore**—defined in **[`code_review_graph/graph.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/graph.py)**—maintains nodes for functions, classes, and variables with typed edges (`CALLS`, `IMPORTS_FROM`, `INHERITS`) that enable precise impact analysis.

### Edit List Construction

For every match, the system collects:

- **Definition sites** where the symbol is declared
- **Call sites** where the symbol is invoked
- **Import sites** where the symbol is referenced

These are assembled into dictionaries containing `file`, `line`, `old_text`, `new_text`, and a `confidence` flag (lines [100‑155]).

### Statistical Summary and Refactor ID

Confidence counters are aggregated (lines [158‑162]), and a **short UUID** (`refactor_id = uuid.uuid4().hex[:8]`) uniquely identifies the preview session (lines [162‑166]).

### Thread‑Safe Storage

The preview dict is stored in the global `_pending_refactors` dictionary, guarded by `_refactor_lock`. Expired entries are purged via `_cleanup_expired` on every operation (lines [172‑176]):

```python

# Simplified from code_review_graph/refactor.py

with _refactor_lock:
    _pending_refactors[refactor_id] = {
        "created_at": time.time(),
        "edits": edit_list,
        "summary": summary_dict
    }
    _cleanup_expired()

```

The response includes actionable next steps:

```json
{
  "status": "ok",
  "summary": "Rename preview: Foo → Bar, 12 edit(s). Use apply_refactor_tool(refactor_id='a1b2c3d4') to apply.",
  "refactor_id": "a1b2c3d4",
  "edits": [
    {"file": "src/foo.py", "line": 15, "old_text": "def Foo", "new_text": "def Bar", "confidence": 1.0}
  ]
}

```

**Dead‑code** (`mode="dead_code"`) and **suggestion** (`mode="suggest"`) modes follow identical patterns through `find_dead_code` and `suggest_refactorings` (lines [91‑120]).

## Safely Applying Refactor Changes

`apply_refactor_func` delegates to `apply_refactor` in [`refactor.py`](https://github.com/tirth8205/code-review-graph/blob/main/refactor.py), which enforces **six sequential safety checks** before any file modification:

| Safety Mechanism | Implementation Details |
|------------------|------------------------|
| **Repository root resolution** | `find_project_root()` or user‑provided `repo_root` resolved to absolute `Path` (lines [59‑63] in [`refactor.py`](https://github.com/tirth8205/code-review-graph/blob/main/refactor.py); also defined in [`code_review_graph/incremental.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/incremental.py)) |
| **Refactor ID validation** | Pending preview fetched from `_pending_refactors`; missing IDs raise `ValueError` (lines [49‑57]) |
| **Expiry enforcement** | `REFACTOR_EXPIRY_SECONDS = 600` (10 minutes) checked; expired previews removed and rejected (lines [54‑60]) |
| **Path‑traversal protection** | Every target file resolved and verified inside repo root via `Path.relative_to`; escaping paths abort operation (lines [71‑77]) |
| **Dry‑run mode** | When `dry_run=True`, `difflib.unified_diff` generates per‑file diffs without disk writes; preview preserved for subsequent real apply (lines [86‑101]) |
| **Atomic multi‑edit handling** | Edits grouped by file, applied to in‑memory copy, then written once; prevents later edits overwriting earlier ones (lines [90‑104]) |

### Dry‑Run Example

```python
from code_review_graph.main import apply_refactor_tool

dry = apply_refactor_tool(
    refactor_id="9f3a1c7b",
    repo_root="/path/to/repo",
    dry_run=True
)

print(dry["would_modify"])  # => ["src/services/user_service.py", "tests/test_user_service.py"]

print(dry["diffs"]["src/services/user_service.py"])

```

The dry‑run response structure (lines [96‑101]):

```json
{
  "status": "ok",
  "dry_run": true,
  "applied": 0,
  "edits_applied": 12,
  "would_modify": ["src/foo.py", "tests/foo_test.py"],
  "diffs": {
    "src/foo.py": "@@ -1,4 +1,4 @@\n- old_name\n+ new_name\n"
  }
}

```

### Committing Changes

When `dry_run=False`, the system:

1. Writes all files atomically (lines [124‑135])
2. Removes the preview from `_pending_refactors` to prevent replay
3. Returns `files_modified` list and `edits_applied` count

Errors during write are logged and reported per‑file without halting the entire operation.

## Complete Workflow Example

### Step 1: Preview a Rename

```python
from code_review_graph.main import refactor_tool

preview = refactor_tool(
    mode="rename",
    old_name="UserService",
    new_name="AccountService",
    repo_root="/path/to/repo"
)

refactor_id = preview["refactor_id"]  # e.g., '9f3a1c7b'

print(preview["summary"])  # "Rename preview: UserService → AccountService, 12 edit(s)..."

```

### Step 2: Verify with Dry‑Run

```python
from code_review_graph.main import apply_refactor_tool

verification = apply_refactor_tool(
    refactor_id=refactor_id,
    repo_root="/path/to/repo",
    dry_run=True
)

assert verification["status"] == "ok"
for path, diff in verification["diffs"].items():
    print(f"\n=== {path} ===\n{diff}")

```

### Step 3: Apply Changes

```python
result = apply_refactor_tool(
    refactor_id=refactor_id,
    repo_root="/path/to/repo",
    dry_run=False
)

print(f"Modified {len(result['files_modified'])} files")

# Preview automatically invalidated; refactor_id cannot be reused

```

## Key Architectural Safeguards

The **graph‑driven analysis** in `GraphStore` ensures no reference is missed. All relationships—calls, imports, inheritance—are explicitly modeled, eliminating the blind spots of text‑only search.

The **thread‑safe pending store** uses `threading.Lock` to protect `_pending_refactors`. Concurrent users receive isolated preview sessions with automatic expiry cleanup.

The **defense‑in‑depth validation** combines time limits, path containment, and dry‑run verification—no single point of failure can bypass the preview requirement.

## Summary

- **`refactor_tool`** generates graph‑aware previews with unique, expiring IDs stored in a thread‑safe registry
- **`apply_refactor_tool`** enforces 10‑minute TTL, path‑traversal guards, and mandatory dry‑run option before any file modification
- **All edits are grouped by file and applied to memory first**, preventing partial or conflicting writes
- **The GraphStore enables precise impact analysis** through explicitly modeled code relationships rather than fragile text matching

## Frequently Asked Questions

### What happens if I try to apply an expired refactor preview?

The operation fails with an error. `apply_refactor` checks `REFACTOR_EXPIRY_SECONDS` (600 seconds by default) and removes expired entries from `_pending_refactors` before validation. You must regenerate the preview using `refactor_tool`.

### Can multiple developers use the refactoring tools simultaneously?

Yes. The `_pending_refactors` dictionary is protected by `_refactor_lock`, ensuring thread‑safe concurrent access. Each preview receives a unique UUID, so developers' sessions remain isolated even on shared infrastructure.

### Why does dry‑run mode preserve the preview while real apply deletes it?

Dry‑run intentionally leaves the preview intact so you can inspect `difflib.unified_diff` output and immediately follow up with `dry_run=False`. The real apply path removes the entry from `_pending_refactors` to prevent accidental replay attacks or duplicate modifications.

### Does the tool protect against directory traversal attacks?

Absolutely. Every target file path is resolved and verified to be within the repository root using `Path.relative_to` (lines [71‑77] in [`refactor.py`](https://github.com/tirth8205/code-review-graph/blob/main/refactor.py)). Any path that escapes the root—via `../` sequences or symlinks—triggers immediate abortion with an error.