# Project export: Refactorika

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2026
- Tagline: Agentic harness for efficient codebase refactoring
- Devpost: https://devpost.com/software/refactorika
- GitHub: https://github.com/Tanush-A/Refactorika
- Video: https://www.youtube.com/embed/uu6amcmRtzg?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Best Use of Cognition)
- Team: 5 GitHub contributor(s) — Claude Opus 4.8 (1M context) (32 commits), Jay-Thpr (32 commits), Tanush (28 commits), Anav Bordia (27 commits), anikaact (20 commits)

## Devpost submission (written by the team)

### Inspiration

According to Stripe's Developer Coefficient — a study conducted with Harris Poll across thousands of engineers and C-suite executives in 30+ industries — 42% of every developer's working week is spent dealing with technical debt and bad code. That's nearly $85 billion in lost productivity every year, not from building the wrong things, but from fighting the accumulated mess in codebases that already exist. Every codebase we've worked on has the same graveyard: god-files no one touches, duplicate logic scattered across five modules, functions that were "temporary." Linters tell you what's wrong but not how to fix it. AI suggests restructuring, but is disconnected from the filesystem, hallucinates edits that don't apply, misses call sites, and forgets everything the moment the session ends. We wanted a tool that could act — read the code, propose a structural change, apply it, prove it's safe, and remember what it did. Something that makes mechanical cleanup as frictionless as running a linter. The hard lesson behind Refactorika: an LLM is brilliant at deciding what to refactor and dangerous at doing it. So we split the job — and that split is the whole product. The LLM reasons about what and how. Deterministic refactoring tools do the actual transformation, reference-correctly. A verification gate proves nothing broke — or reverts it. What it is Refactorika is an agent harness delivered as an MCP server, riding on a deterministic, graph-driven refactoring engine. It plugs Claude directly into your codebase, gives it a reference-correct model of the whole program to reason over, and routes every change it proposes through real refactoring tools and a verification gate stack — so cleanups land proven safe or not at all. It gives every repo the four capabilities they desperately need: Organization — splits god-files into coherent modules, reorders and deduplicates imports (stdlib → third-party → local), extracts helpers from bloated call sites. Complexity reduction — breaks long functions into named units, flattens deep nesting with guard clauses, replaces repeated blocks with parameterized helpers. Duplicate & dead-code removal — finds structural clones via AST fingerprinting and semantic near-clones via vector embeddings, then finds functions nothing reaches via call-graph reachability. Every removal goes through verification before it lands. Living documentation — generates and self-updates .refactorika/context/<module>.md files that compound across sessions, so the why doesn't evaporate when people leave. Three ways to run it, all over one verified spine: the MCP server (drive it from Claude), the engine CLI (refactorika <dir>), and the agent campaign (--agents: audit → dependency-ordered plan → specialist agents). How it works — the pipeline The whole design is a hard line between AI judgment and deterministic execution. The LLM never edits a file; it emits a typed TransformSpec. Real tools execute it. Gates decide whether it lives. 1 · The graph — reference-correct understanding. We build a whole-program symbol graph with Jedi's real static name resolution. Nodes are functions/classes/methods; edges are true references resolved through imports, aliases, and scopes — not a regex name match. This is the make-or-break: it's why a rename updates every real reference and nothing that merely shares the name, and why dead-code analysis (reachability from entry points like public API, __all__, __main__, tests, and route/fixture decorators) is trustworthy. The graph also gives us leaf-to-root ordering (refactor on top of already-verified code) and impact analysis (the exact set a change can affect → we re-run only the impacted tests). 2 · The plan — where the LLM reasons. The planner finds real smells — god functions detected by a three-axis cohesion signal (cyclomatic complexity ≥ 6 or length ≥ 30 lines or nesting ≥ 4, not a naive line count), duplicates, dead code — and asks the model how to fix them. The model returns a TransformSpec (parameters), never a diff. Before deciding, it recalls the most semantically similar prior decision from memory and reuses the same helper names, so the 2nd, 5th, Nth similar function is refactored consistently. 3 · The engines — real refactoring tools do the work (deterministic). These are battle-tested engines, not LLM text edits — so a cross-file rename is provably complete in a way prompting never is. Each returns an EditMap ({path: contents}) and touches nothing on disk — the gate stack owns disk and git. 4 · The verification gate stack — determinism's teeth. Every edit passes a cheapest-first, short-circuiting pipeline. Tools are the arbiter; no LLM decides whether an edit is safe. parse (tree-sitter, before touching disk) → lint (ruff, rejects only new violations vs. a pre-edit baseline) → type (pyright, only new errors — touching a file with pre-existing noise won't spuriously fail) → behavior (pytest, scoped to the impacted tests; no test coverage is recorded as a skip, never a silent pass). All green → git commit (one atomic commit per verified edit). Any red or crash → byte-for-byte restore. The full suite runs once at baseline (the repo must start green) and once at the finale ("all N still pass") as the authoritative backstop. 5 · The agent harness — bounded, self-repairing autonomy. The agent campaign drives the whole loop as an explicit state machine: discover the target with bounded exploration, select a change, execute a multi-file patch, verify it, and on gate failure repair-and-retry — escalating to skipped-needs-human rather than ever force-committing. 6 · Memory — Redis Iris, decision memory not a cache. Every refactoring decision (the pattern it acted on → the transform → the names chosen) is stored, keyed by an embedding of the code, so the engine stays consistent across a whole repo. This is one of four Redis Iris subsystems (below). Kill Redis and everything degrades transparently to local JSON — the engine never depends on it. Tech stack Core engine & the program graph Python 3.11+ Jedi — real static name binding → the reference-correct symbol graph (replaced a regex call-graph that mislinked same-named symbols) tree-sitter + tree-sitter-python — AST parsing, the parse gate, structural fingerprints, nesting depth FastMCP — MCP server framework (12 tools, JSON in/out) Typer — the engine CLI shell Deterministic refactoring tools rope — reference-correct cross-file rename/move LibCST — lossless AST-node replacement (decomposition) + surgical dead-code removal ruff + autoflake — deterministic cleanup radon — complexity metrics + god-function detection Verification gates ruff — lint + format normalization (new violations only) pyright — type checking (new errors only, vs. pre-edit baseline) pytest — behavior gate, impact-scoped; exit 5 (no tests) recorded as a skip, never a silent pass git — atomic commit per verified edit; rollback via byte-for-byte snapshot restore Duplicate & dead-code detection tree-sitter AST fingerprinting — exact structural clones OpenAI text-embedding-3-small (+ sentence-transformers keyless fallback; provider-agnostic, also Ollama) — semantic near-clone detection Redis FT.HYBRID (BM25 + vector, RRF-fused) — hybrid search; strictly better than pure cosine on code Custom call-graph reachability — dead-symbol detection with confidence levels Memory — Redis Iris Redis 8+ (local Docker redis:8 / redis-stack for the Query Engine) — primary state backend RedisVL — vector index + hybrid search client Four subsystems: AST-keyed LangCache · Hybrid Search Index (FT.HYBRID, RRF) · Agent Memory (cross-session decisions + history) · Context Retriever (hybrid + tag filters) Graceful fallback to local .refactorika/ JSON + brute-force vectors when Redis is unavailable Provider-agnostic LLM Generation (Anthropic Claude | Ollama) and embeddings (sentence-transformers | Ollama | OpenAI) are separate providers — Anthropic has no embeddings API, so the embedding backend works regardless of the generation provider. A record/replay cache keyed by (provider, model, prompt) makes any run reproducible. Language support LanguageAdapter registry — per-language parse/lint/typecheck dispatch. Python: full gate stack. Any other language: gates skip (recorded as null), the test suite still runs. TypeScript/Go adapters drop in via optional deps, no core changes. Infrastructure Git — atomic commits + snapshot rollback · .env / .env.example — secrets, never committed Benchmarking — does the harness actually help? We measured it the only honest way: the same agent, with the harness OFF vs ON, graded by an independent oracle (the repo's own tests), not by the harness itself. Simple proposer: harness lifts success 71.1% → 86.7% (32/45 → 39/45) — and spends fewer tokens (37k → 25k) and less time (190s → 160s). Safety that's also cheaper. Full agentic loop: harness lifts 75.6% → 83.3% while cutting tokens 1.37M → 806k and time 1,655s → 1,208s — the verified spine keeps the agent from thrashing. We also run on RefactorBench (microsoft/RefactorBench — 100 real multi-file refactoring tasks across Django, FastAPI, Celery, Scrapy, Salt, Ansible, Requests, Flask, Tornado, each verified by its own AST tests). Refactorika has a fixed transform menu, so we decline out-of-scope tasks explicitly rather than hallucinate, and report three honest numbers — in-scope pass rate, in-scope subtask completion, and out-of-scope count — never a single inflated figure. (Baseline LM agents solve ~22% of RefactorBench; it's a credibility signal, not an ace-it target.)

### Challenges we ran into

The verification paradox. A change that looks clean to pyright can still silently break behavior. Getting pytest to run scoped to the impacted tests (fast enough to feel interactive), making the type/lint gates baseline-aware (reject only new errors, so touching a file with pre-existing noise doesn't revert a good edit), and handling missing coverage honestly (record a skip, never a silent pass) took real iteration. skipped-needs-human was a deliberate line: never force-commit, never pretend a gate passed that didn't run. Reference-correctness on real repos. This is the make-or-break. A regex call-graph cheerfully renames a same-named-but-unrelated symbol; rope crashes on intentionally-broken fixtures (Django ships syntax-error test files); the dead-code locator initially only found def/class, missing renamed constants. Real codebases are far messier than toy examples — we moved to Jedi binding for the graph and hardened every tool against the mess. Refactoring should make code less, not more. Our first LLM pass naively decomposed every long function and added hundreds of lines. "Decompose" is a structural trade, not a reduction — so we rebuilt god-function detection around a three-axis cohesion signal and biased the planner toward reduction (dead code, dedup, cleanup) over restructuring. Duplicate detection at the right granularity. Structural fingerprinting catches exact clones; semantic embeddings catch near-clones. Tuning the threshold to surface actionable duplicates without drowning in false positives (getter/setter pairs should look similar) required calibrating against real messy repos. Redis Iris offline fallback. We wanted Redis Iris as the primary memory layer but needed the demo to run anywhere — including a laptop with no network. Building a fallback that's genuinely transparent (same interface, same behavior, just slower and non-persistent) without a maintenance split was harder than expected. Tree-sitter sees syntax, not semantics. Call-graph reachability works for direct calls but misses dynamic dispatch, decorators that register functions implicitly, and __all__ exports. We set explicit confidence levels and surface uncertainty rather than silently over-delete.

### What we learned

The hardest part of a safety-first refactoring tool isn't the refactoring — it's the division of labor. The LLM is the right tool for judgment (what's worth changing, how to name the pieces) and the wrong tool for execution (it can't guarantee a rename is complete). Deterministic engines are the reverse. Put the model in charge of "what," rope/LibCST in charge of "how," and the test suite in charge of "is it still correct" — and you get something you can actually trust. We also had to define "safe" precisely enough to enforce mechanically: malformed output (parse), style drift (ruff), type regression (pyright), and behavior change (pytest) are four different failures that need four different responses. And we learned that visible checking is the product. An agent that silently refactors and hands you a diff to trust is just a faster way to introduce bugs. Rendering the gate log — especially the catch-and-rollback moment — is what turns "AI did something to my code" into "I watched it get checked."

### What's next

Broader duplicate consolidation — the flow surfaces duplicates and proposes a merge today; we want to close the loop by auto-generating the unified implementation and running it through the full gate stack (rope already does reference-correct moves; we're wiring consolidate as a first-class transform). Richer dead-code analysis — handle dynamic dispatch, decorator-registered entry points, and __all__-exported symbols more precisely to push confidence levels up. Per-module context cards — generate_docs already emits .refactorika/context/ files; make them queryable via get_context_map so Claude can answer "why does this module look this way?" with grounded, session-persistent memory. More languages — the LanguageAdapter registry already abstracts parse/lint/typecheck; TypeScript and Go gates drop in as optional deps, no core changes. Built with python · jedi · rope · libcst · tree-sitter · ruff · autoflake · pyright · pytest · radon · redis · redisvl · fastmcp · anthropic · ollama · openai · sentence-transformers · typer · git

## README (from the GitHub repository)

# Refactorika

> 📚 **Full documentation: [`docs/`](docs/README.md).** New here? **Refactorika has two branches** —
> `working` (the demo: MCP agent harness + four-arm benchmark) and `main` (this branch: the v3
> graph engine). Read **[docs/branches.md](docs/branches.md)** first.

A **graph-driven, verified refactoring engine** for Python. Point it at a repo; it builds a
reference-correct model of the whole program, plans a safe dependency order, applies
deterministic transforms, and **proves nothing broke** — committing each verified change and
reverting anything that fails its tests.

The pitch: *refactoring is a whole-program graph problem, not a per-file one.* The LLM brings
judgment, deterministic engines bring correctness at scale, the graph connects them, and the
test suite proves behavior is preserved.

## What makes it correct

- **Real reference resolution, not regex.** A symbol graph built from Jedi static analysis: a
  rename updates *every true reference and nothing that merely shares the name*; dead code is
  removed only when reachability proves it dead.
- **Deterministic engines own the edits.** rope (cross-file rename), LibCST (node replacement,
  dead-code removal), ruff + autoflake (cleanup). The LLM emits compact specs, never diffs.
- **Verified, then committed.** Every edit passes **parse → ruff → pyright → pytest** (tests
  *impact-scoped* to what the change can affect) before `git commit`; any failure reverts the
  files byte-for-byte. The full suite gates the run at **baseline** and **finale**.
- **Efficient by construction.** Leaf-to-root ordering means each step builds on already-verified
  code; only impacted tests run per edit.

## Quickstart

> **Full how-to (every flag, the MCP tools, the agent campaign, Redis, eval): [`docs/usage.md`](docs/usage.md).**

```bash
python3 -m venv .venv
.venv/bin/python -m pip install -e ".[dev]"        # add ".[semantic]" for embeddings/vector recall
docker compose up -d redis                          # optional: live Redis (redis-stack); else JSON fallback

# Dry-run on the bundled messy repo (no changes written): see the plan, the verified
# edits (dead code removed + cleanup), and the before/after metrics.
.venv/bin/refactorika demo_repo

# Inspect without running:
.venv/bin/refactorika demo_repo --show-graph     # the symbol graph, entry points, dead code
.venv/bin/refactorika demo_repo --show-plan      # the leaf-to-root worklist

# Apply in place (commits each verified edit to git):
.venv/bin/refactorika demo_repo --apply

# Reference-correct rename across the whole repo (the centerpiece) — deterministic:
.venv/bin/refactorika demo_repo --rename orders.compute_total=calculate_order_total

# Add LLM judgment: god-function decomposition with consistent naming via decision memory.
# The first run with ANTHROPIC_API_KEY records responses to .refactorika/llm_cache.json;
# subsequent runs replay that cache offline (no key needed).
.venv/bin/refactorika demo_repo --llm

# Run the agentic campaign (audit -> plan -> specialist agents) through the verified engine.
# Applies in place; complexity decomposition needs ANTHROPIC_API_KEY.
.venv/bin/refactorika demo_repo --agents

# Inspect decision memory / semantic neighbors:
.venv/bin/refactorika demo_repo --show-memory
.venv/bin/refactorika demo_repo --show-similar orders.compute_total

# Tests (offline — no Redis, no API key needed):
REFACTORIKA_OFFLINE=1 .venv/bin/python -m pytest -q
```

## Run as an MCP server (use inside an agent)

```bash
.venv/bin/python -m refactorika.mcp_server   # stdio MCP server
claude mcp add refactorika -- .venv/bin/python -m refactorika.mcp_server
```
Tools: `build_graph`, `get_plan`, `run_pipeline`, `run_agents`, `analyze_file`,
`find_duplicates`, `find_dead_code`, `apply_and_verify(_multi)`, `generate_docs`,
`get_context_map`, `get_log`. Full reference in [`docs/usage.md`](docs/usage.md).

## Providers, memory, and evaluation

- **Provider-agnostic LLM** — generation via Claude or local **Ollama**, embeddings via local
  MiniLM or Ollama (separate, since Anthropic has no embeddings API). Selected by env
  (`REFACTORIKA_LLM_PROVIDER`, `REFACTORIKA_EMBED_PROVIDER`); a record/replay cache makes any
  provider reproducible. See `.env.example`.
- **Redis as the shared brain** — decisions are stored in Redis (`REDIS_URL`, e.g. Redis Cloud)
  and recalled by semantic similarity so refactors stay consistent; local-JSON fallback for
  offline (`REFACTORIKA_OFFLINE=1`). Inspect with `refactorika <dir> --show-memory`;
  `docker compose up -d redis` runs a local instance.
- **RefactorBench eval** — `make fetch && make eval-inscope` runs the engine on real OSS
  refactoring tasks; results in `eval/results/`. See `docs/11-benchmarks-and-eval.md`.

## How it works

```
CLI / MCP  →  orchestrator  →  graph (Jedi)  →  planner (+LLM judgment)  →  engines (rope/LibCST/ruff)
                                                                              →  checker (gates + git)
                              Redis Iris = graph + decision memory + vectors (local-JSON fallback)
```

## Layout

```
refactorika/graph/       reference-correct symbol graph + leaf-to-root order + impact
refactorika/transforms/  deterministic engines (rename, cleanup, dead_code, node_replace)
refactorika/pipeline/    orchestrator · planner · planner_llm · checker
refactorika/llm/         Anthropic client with record/replay cache + stub seam
refactorika/memory/      Redis Iris: agent/decision memory, vectors (JSON fallback)
refactorika/core/        schema · gates · storage
refactorika/cli.py       standalone Typer CLI      refactorika/mcp_server.py   MCP server
demo_repo/               deliberately messy target + its tests
docs/v3_spec.md          the source-of-truth spec (as built)
```

Python only; behavior-preserving structural refactors. See `docs/v3_spec.md` for the full spec
and `CLAUDE.md` for project memory.


## Detected evidence (automated analysis)

Indexed codebase: 182 recognized source files, 1244 KB.
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code
- Redis (technology) — detected in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository; commit authorship or trailers

## Codebase structure (from repository index)

### Files (120 of 192)

```
.env.example
.gitignore
CLAUDE.md
demo_repo/billing.py
demo_repo/orders.py
demo_repo/pyrightconfig.json
demo_repo/test_orders.py
docker-compose.yml
docs/01-problem-statement.md
docs/02-scope.md
docs/03-tech-stack.md
docs/04-architecture.md
docs/05-redis-iris.md
docs/11-benchmarks-and-eval.md
docs/12-benchmark-display-spec.md
docs/12-harness-benchmark.md
docs/13-full-system-benchmark.md
docs/13-v3-roadmap.md
docs/14-benchmark-case-catalog-and-stress-plan.md
docs/15-four-arm-agent-benchmark-contract.md
docs/agents-and-languages.md
docs/architecture.md
docs/branches.md
docs/cli-and-mcp.md
docs/configuration.md
docs/devpost.md
docs/evaluation.md
docs/module-reference.md
docs/pipeline.md
docs/project-overview.md
docs/README.md
docs/refatorika_plan.md
docs/semantic_index_design.md
docs/testing.md
docs/usage.md
docs/v2_spec.md
docs/v2-worklog.md
docs/v3_spec.md
docs/v3-worklog.md
eval/__init__.py
eval/agents/__init__.py
eval/agents/campaign.py
eval/agents/driver.py
eval/agents/harness_tools.py
eval/agents/loop.py
eval/agents/metrics.py
eval/agents/prompts.py
eval/agents/providers.py
eval/agents/schema.py
eval/agents/tools.py
eval/fetch_benchmarks.sh
eval/full_system_bench.py
eval/full_system_cases/__init__.py
eval/full_system_cases/behavior.py
eval/full_system_cases/multifile.py
eval/full_system_cases/recovery.py
eval/full_system_cases/scale.py
eval/full_system_cases/stress_contracts_extra.py
eval/full_system_cases/stress_semantics_extra.py
eval/full_system_cases/stress_systems_extra.py
eval/full_system_cases/stress.py
eval/harness_bench.py
eval/harness_tasks.py
eval/PLAN_agentic_mcp_arm.md
eval/README.md
eval/refactorbench.py
eval/requirements.txt
eval/results/base_all_memoff.json
eval/results/base_all_memoff.md
eval/results/base_inscope_memoff.json
eval/results/base_inscope_memoff.md
eval/results/base_inscope_memon.json
eval/results/base_inscope_memon.md
eval/run_eval.py
eval/run_eval.sh
LICENSE
Makefile
pyproject.toml
README.md
refactorika/__init__.py
refactorika/agents/__init__.py
refactorika/agents/base.py
refactorika/agents/complexity_agent.py
refactorika/agents/dead_code_agent.py
refactorika/agents/duplicate_agent.py
refactorika/agents/import_agent.py
refactorika/agents/orchestrator.py
refactorika/analysis/__init__.py
refactorika/analysis/audit.py
refactorika/analysis/call_graph.py
refactorika/analysis/dead_code.py
refactorika/analysis/duplicates.py
refactorika/analysis/embeddings.py
refactorika/analysis/parser.py
refactorika/analysis/related.py
refactorika/cli.py
refactorika/core/__init__.py
refactorika/core/analyze.py
refactorika/core/apply.py
refactorika/core/gates.py
refactorika/core/schema.py
refactorika/core/storage.py
refactorika/dashboard.py
refactorika/docs_gen.py
refactorika/graph/__init__.py
refactorika/graph/model.py
refactorika/graph/order.py
refactorika/graph/resolver.py
refactorika/harness.py
refactorika/languages/__init__.py
refactorika/languages/base.py
refactorika/languages/generic_adapter.py
refactorika/languages/python_adapter.py
refactorika/languages/registry.py
refactorika/llm/__init__.py
refactorika/llm/client.py
refactorika/llm/providers.py
refactorika/mcp_server.py
refactorika/memory/__init__.py
refactorika/memory/agent_memory.py
[72 more files omitted for size]
```

### Dependencies

- eval/requirements.txt: pyright@==1.1.389, pytest@==8.3.3, ruff@==0.7.4, sentry-sdk@>=2.0, tree-sitter@==0.23.2, tree-sitter-python@==0.23.6
- pyproject.toml: anthropic[mcp]@>=0.111.0, autoflake@>=2.3.0, fakeredis@>=2.20.0, jedi@>=0.19.0, libcst@>=1.4.0, numpy@>=1.24, openai@>=1.0, pyright@>=1.1.0, pytest@>=8.0.0, radon@>=6.0.0, redis@>=7.1, redisvl@>=0.13, rope@>=1.13.0, ruff@>=0.6.0, sentence-transformers@>=2.0, sentry-sdk@>=2.0, tree-sitter@>=0.23.0, tree-sitter-python@>=0.23.0, typer@>=0.12.0

### Recent commits (newest first)

- Make main the main (working is uselsess)
- proejct overview
- docs: branch-aware doc set synthesizing working (demo) + main (engine)
- Merge branch 'main' of https://github.com/Tanush-A/Refactorika
- benchmark
- Unify vector_index: restore RedisVL hybrid + keep v3 namespace/provider API
- Merge branch 'v3-refactoring-engine'
- docs: add complete usage guide (docs/usage.md) + refresh README
- removed md
- Merge branch 'main' of https://github.com/Tanush-A/Refactorika
- agent fine tuning + devpost
- benchmarking visual
- docs(pipeline): rewrite as a reachability map; add audit tool
- Merge branch 'main' of https://github.com/Tanush-A/Refactorika
- added image to docs
- Wire the agentic campaign into the front doors (run like main, verified)
- Wire ComplexityAgent through the deterministic engine + verified checker
- fix: support bounded agent replanning
- Merge main into v3-refactoring-engine (bring main's infra onto the engine)
- fix: harden agentic benchmark workflows

## Key source files (fetched from GitHub, selected and truncated for size)

### CLAUDE.md

```markdown
# CLAUDE.md — Refactorika (project memory)

> Self-contained context for every Claude Code session. `docs/v3_spec.md` is the full
> source-of-truth spec; this file is the fast orientation. Keep it short and current.

## What we're building
- **Product:** **Refactorika** — a **graph-driven, verified refactoring engine** for Python.
  Point it at a repo; it builds a reference-correct whole-program model, plans a safe
  dependency order, applies deterministic transforms, and proves nothing broke (commit each
  verified edit; revert anything that fails its tests). Python target, Python tool.
- **One-liner:** *Refactoring is a whole-program graph problem. The LLM brings judgment,
  deterministic engines bring correctness at scale, the graph connects them, the test suite
  proves behavior is preserved.*
- **Two north stars:** **properly** (reference-correct + behavior-preserving) and
  **efficiently** (leaf-to-root order, impact-scoped tests, token-lean LLM).
- **Two front doors:** standalone Typer CLI `refactorika <dir>` (primary) + MCP server (secondary).

## Architecture (as built — see docs/v3_spec.md §4 for the module map)
- **graph/** — `resolver.py` builds the symbol graph via **Jedi** static analysis (real name
  binding; replaces the old regex call-graph). `model.py` = Symbol/Graph. `order.py` = Tarjan
  SCC leaf-to-root topo + `impact_of` (reverse reachability) + `reachable_from` (dead code).
- **transforms/** — deterministic engines, the ONLY code that mutates source. `rename.py`
  (rope, cross-file, extracted without touching disk), `cleanup.py` (autoflake+ruff),
  `dead_code.py` (LibCST removal), `node_replace.py` (LibCST function replacement). Each takes a
  `TransformSpec`, returns an `EditMap` ({path: new_contents}); commits nothing.
- **pipeline/** — `orchestrator.py` (plain loop: plan→dispatch→check; dead-code cascade; dry-run
  copy vs `--apply`), `planner.py` (deterministic: dead-code + cleanup), `planner_llm.py` (LLM
  god-function decomposition + **decision-memory consistency**), `checker.py` (multi-file atomic
  apply + gate stack + impact-scoped tests + git commit/revert).
- **llm/client.py** — Anthropic, temp 0, **record/replay cache** + stub seam + no-key fallback.
- **memory/** + **core/storage.py** — Redis Iris (graph, decisions, vectors) with mandatory
  local-JSON fallback. **core/** = schema (contracts), gates, storage, apply (v2 single-file).

## The verified spine (trust + the demo)
Per edit, cheapest-first, short-circuit: **parse (tree-sitter) → ruff → pyright → pytest**.
Tests are **impact-scoped** (only tests reachable from the changed symbol). All green → `git
commit`; any red/crash → restore every file byte-for-byte. The **full suite** runs at
**baseline** (must start green) and **finale** ("all N still pass") as the authoritative backstop.

## Ordering rules
- **Refactor** leaf-to-root (build on verified deps). **Dead-code removal** root-to-leaf (caller
  before callee, else undefined name), then **cascade** reach
[truncated — 3376 more characters]
```

### docs/04-architecture.md

```markdown
> **⚠ HISTORICAL — not maintained.** Preserved as a record of how the project evolved. For current docs see [docs/README.md](README.md).

# Architecture

> **Moved.** The architecture of the as-built engine is documented authoritatively in
> **[v3_spec.md](v3_spec.md)** — see §3 (architecture diagram), §4 (module map), §5 (the
> transform contract), §6 (the verification model), and §7 (ordering rules).

In one paragraph: Refactorika is one interface-agnostic core (graph + transforms + checker +
memory) wrapped in two thin front doors — a **standalone Typer CLI** (primary) and an **MCP
server** (secondary). The orchestrator builds a reference-correct symbol graph (Jedi), a planner
turns it into a leaf-to-root worklist of `TransformSpec`s (the LLM adding judgment), deterministic
engines (rope/LibCST/ruff) produce an `EditMap`, and the checker runs the gate stack and commits
or reverts via git. State lives in Redis Iris with a mandatory local-JSON fallback.

The earlier "Claude proposes whole-file `new_content`, the core verifies" model described here
previously is **superseded** — see [v2_spec.md](v2_spec.md) for that historical design.

```

### docker-compose.yml

```yaml
# Local Redis for Refactorika so the tool runs standalone (no cloud needed).
# redis-stack-server bundles RediSearch, so the decision vector index works locally and is
# viewable in Redis Insight (http://localhost:8001). For the real run, instead point the tool
# at Redis Cloud by setting REDIS_URL=rediss://... in your .env (no compose needed).
#
#   docker compose up -d redis
#   REDIS_URL=redis://localhost:6379/0 refactorika demo_repo --llm
#
# The optional agent-memory-server profile wires Redis Iris Agent Memory (semantic long-term
# memory) on top of the same Redis. Enable with:  docker compose --profile memory up -d
services:
  redis:
    image: redis/redis-stack:latest
    ports:
      - "6379:6379"     # Redis
      - "8001:8001"     # Redis Insight UI
    volumes:
      - refactorika-redis:/data

  agent-memory-server:
    image: ghcr.io/redis/agent-memory-server:latest
    profiles: ["memory"]
    depends_on:
      - redis
    environment:
      # Point Agent Memory at the same Redis; generation via Anthropic, embeddings local.
      REDIS_URL: redis://redis:6379/0
      GENERATION_MODEL_PROVIDER: anthropic
      ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
    ports:
      - "8000:8000"

volumes:
  refactorika-redis:

```

### pyproject.toml

```
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "refactorika"
version = "0.2.0"
description = "MCP server that gives Claude verified structural-refactoring powers over Python codebases"
authors = [{name = "Anikathapar", email = "anikathapar22@gmail.com"}]
license = {file = "LICENSE"}
readme = {file = "README.md", content-type = "text/markdown"}
requires-python = ">=3.11"
classifiers = [
    "Development Status :: 4 - Beta",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Topic :: Software Development :: Quality Assurance",
    "Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [
    "anthropic[mcp]>=0.111.0",
    "tree-sitter>=0.23.0",
    "tree-sitter-python>=0.23.0",
    "libcst>=1.4.0",
    "rope>=1.13.0",
    "jedi>=0.19.0",
    "autoflake>=2.3.0",
    "radon>=6.0.0",
    "typer>=0.12.0",
    "redis>=7.1",
    "numpy>=1.24",
    "sentry-sdk>=2.0",
]

[project.optional-dependencies]
semantic = [
    "openai>=1.0",
    "redisvl>=0.13",
    "sentence-transformers>=2.0",
]
dev = [
    "pytest>=8.0.0",
    "pyright>=1.1.0",
    "ruff>=0.6.0",
    "fakeredis>=2.20.0",
]

[project.urls]
Homepage = "https://github.com/Tanush-A/Refactorika"
Repository = "https://github.com/Tanush-A/Refactorika"
Issues = "https://github.com/Tanush-A/Refactorika/issues"

[project.scripts]
refactorika = "refactorika.cli:main"
refactorika-scan = "refactorika.cli:main"

[tool.hatch.build.targets.wheel]
packages = ["refactorika"]

[tool.pyright]
pythonVersion = "3.11"
include = ["refactorika"]
strict = true

[tool.pytest.ini_options]
# Only collect our suite — never the fetched benchmark repos under eval/external/.
testpaths = ["tests"]

[tool.ruff]
src = ["refactorika"]
target-version = "py311"
line-length = 100
# demo_repo is a deliberately messy fixture; eval/external is fetched third-party code.
extend-exclude = ["demo_repo", "eval/external"]

[tool.ruff.lint]
select = ["E", "F", "I"]

```

### eval/requirements.txt

```
# Refactorika evaluation dependencies.
# Pinned loosely for the hackathon; tighten before relying on results.

# Parsing / analysis (mirrors docs/03-tech-stack.md)
tree-sitter==0.23.2
tree-sitter-python==0.23.6

# Verification-harness gates
pyright==1.1.389
ruff==0.7.4
pytest==8.3.3
sentry-sdk>=2.0

```

### refactorika/cli.py

```python
"""Standalone CLI — point it at a repo and watch it refactor, verified.

``refactorika <dir>`` runs the full pipeline on a throwaway copy (dry-run) and prints
the leaf-to-root plan, each verified edit, and a before/after metrics table. ``--apply``
runs in place and commits. No agent required; Redis is optional (falls back to files).
"""

from __future__ import annotations

from typing import Optional

import typer

from refactorika.core.storage import Storage
from refactorika.metrics import metrics_delta

_DIM = "\033[2m"
_BOLD = "\033[1m"
_GREEN = "\033[32m"
_RED = "\033[31m"
_YELLOW = "\033[33m"
_RESET = "\033[0m"


def _c(text: str, color: str) -> str:
    return f"{color}{text}{_RESET}"


def _tri(value: Optional[bool]) -> str:
    if value is True:
        return _c("pass", _GREEN)
    if value is False:
        return _c("FAIL", _RED)
    return _c("skip", _YELLOW)


def _entry(
    path: str = typer.Argument(..., help="Path to the Python repo/dir to refactor."),
    apply: bool = typer.Option(False, "--apply", help="Write changes in place and commit."),
    show_graph: bool = typer.Option(False, "--show-graph", help="Print the symbol graph and exit."),
    show_plan: bool = typer.Option(False, "--show-plan", help="Print the plan and exit."),
    show_memory: bool = typer.Option(False, "--show-memory",
                                     help="Print stored refactor decisions (from Redis) and exit."),
    show_similar: str = typer.Option(None, "--show-similar", metavar="QUALNAME",
                                     help="Embed the codebase and print a symbol's nearest "
                                          "semantic neighbors, then exit."),
    no_tests: bool = typer.Option(False, "--no-tests", help="Skip the test gates (faster)."),
    use_llm: bool = typer.Option(False, "--llm", help="Use the LLM planner (needs API key)."),
    agents: bool = typer.Option(False, "--agents",
                                help="Run the agentic campaign (specialist agents) through the "
                                     "verified engine. Applies in place."),
    rename: list[str] = typer.Option(
        None, "--rename",
        help="Reference-correct rename, repeatable: 'module.qualname=new_name'."),
) -> None:
    """Refactor a Python repo with verified, graph-driven transforms."""
    renames = _parse_renames(rename)
    if show_graph:
        _print_graph(path)
        return
    if show_plan:
        _print_plan(path, use_llm, renames)
        return
    if show_memory:
        _print_memory()
        return
    if show_similar:
        _print_similar(path, show_similar)
        return
    if agents:
        _run_agents(path, run_tests=not no_tests)
        return
    _run(path, apply=apply, run_tests=not no_tests, use_llm=use_llm, renames=renames)


def _run_agents(path: str, *, run_tests: bool) -> None:
    """Run the agentic campaign: audit -> dependency-ordered plan -> specialists via the engine."""
    from refactorika.agents.orchestrator import run_campaign

    storage = Storage()
    typer.echo(f"\n{_BOLD}Refactorika · agents{_RESET}  ·  {path}  ·  "
               f"{_c('APPLY (in place)', _RED)}  ·  storage={storage.backend}")
    summary = run_campaign(path, storage, run_tests=run_tests)
    if "error" in summary:
        typer.echo(f"  {_c(summary['error'], _RED)}")
        return
    typer.echo(f"\n  finding: {_DIM}{summary.get('dominant_finding')}{_RESET}  "
               f"· {summary.get('tasks', 0)} task(s)")
    typer.echo(f"\n{_BOLD}Campaign{_RESET} — "
               f"{_c(str(summary['committed']) + ' committed', _GREEN)}, "
               f"{summary['rolled_back']} reverted, {summary['skipped']} skipped")
    for r in summary["records"]:
        if "error" in r:
            typer.echo(f"  {_c('error', _RED):>22}  {r['file']}: {r['error']}")
        else:
            _print_record(r)
    typer.echo("")


def _parse_renames(rename: Optional[list[str]]) -> list[tuple[str, str]]:
    pairs: list[tuple[str, str]] = []
    for r in rename or []:
        if "=" in r:
            qual, new = r.split("=", 1)
            pairs.append((qual.strip(), new.strip()))
    return pairs


# --------------------------------------------------------------------------- actions
def _run(path: str, *, apply: bool, run_tests: bool, use_llm: bool,
         renames: Optional[list[tuple[str, str]]] = None) -> None:
    from refactorika.pipeline.orchestrator import run_pipeline

    planner = _build_planner(use_llm, renames)
    storage = Storage()
    mode = _c("APPLY (in place)", _RED) if apply else _c("dry-run (copy)", _DIM)
    typer.echo(f"\n{_BOLD}Refactorika{_RESET}  ·  {path}  ·  {mode}  ·  storage={storage.backend}")

    res = run_pipeline(path, apply=apply, planner=planner, storage=storage, run_tests=run_tests)

    typer.echo(f"\n  baseline suite: {_tri(res.baseline_tests)}  "
               f"{_DIM}{res.baseline_detail}{_RESET}")
    if res.cycles:
        typer.echo(f"  {_c('cycles', _YELLOW)}: {res.cycles}")

    committed = [r for r in res.records if r["status"] == "committed"]
    reverted = [r for r in res.records if r["status"] == "rolled-back"]
    typer.echo(f"\n{_BOLD}Edits{_RESET} — {len(committed)} committed, {len(reverted)} reverted")
    for r in res.records:
        _print_record(r)

    typer.echo(f"\n{_BOLD}Metrics{_RESET}")
    delta = metrics_delta(res.metrics_before, res.metrics_after)
    for k in res.metrics_before:
        b, a, d = res.metrics_before[k], res.metrics_after[k], round(delta[k], 2)
        arrow = "" if d == 0 else _c(f"  ({d:+g})", _GREEN if _is_improvement(k, d) else _DIM)
        typer.echo(f"  {k:16} {b:>7} -> {a:>7}{arrow}")

    typer.echo(f"\n  finale suite:  {_tri(res.finale_tests)}  {_DIM}{res.finale_detail}{_RESET}")
    if not apply:
        typer.echo(f"\n{_DIM}dry-run — working copy at {res.path};"
                   f" re-run with --apply to commit.{_RESET}")
    typer.echo("")


def _print_record(r: dict) -> None:
    status 
[truncated — 5305 more characters]
```

### eval/__init__.py

```python
"""Refactorika evaluation package."""


```

### refactorika/__init__.py

```python
"""Refactorika — verified structural refactoring for Python, exposed to Claude over MCP."""

__version__ = "0.2.0"

```

### demo_repo/test_orders.py

```python
from orders import compute_total


def test_gold_bulk_discount() -> None:
    items = [{"price": 60.0, "qty": 3}]  # 180 -> gold>100 -> *0.85 = 153
    assert compute_total(items, "gold", None) == round(153 * 1.08, 2)


def test_silver_and_coupon() -> None:
    items = [{"price": 50.0, "qty": 1}]  # 50 -> silver *0.95 = 47.5 -> SAVE10 *0.9 = 42.75
    total = 42.75 * 1.08
    import math

    assert compute_total(items, "silver", "SAVE10") == math.floor(total * 100) / 100


def test_skips_nonpositive() -> None:
    items = [{"price": 10.0, "qty": 0}, {"price": 20.0, "qty": 2}]  # only second counts
    assert compute_total(items, "bronze", None) == round(40 * 1.08, 2)


def test_shipping() -> None:
    from orders import _compute_shipping  # noqa: PLC0415
    assert _compute_shipping(0.5) == 3.99
    assert _compute_shipping(2.0) == pytest.approx(5.49)


import pytest  # noqa: E402

```

### tests/test_full_system_case_registry.py

```python
from eval.full_system_cases import (
    ALL_CASES,
    BEHAVIOR_CASES,
    CONTRACT_STRESS_CASES,
    MULTIFILE_CASES,
    RECOVERY_CASES,
    SCALE_CASES,
    SEMANTIC_STRESS_CASES,
    STRESS_CASES,
    SYSTEM_STRESS_CASES,
    USER_PROMPT,
)


def test_registry_contains_non_overlapping_case_families() -> None:
    assert len(BEHAVIOR_CASES) == 3
    assert len(MULTIFILE_CASES) == 3
    assert len(RECOVERY_CASES) == 3
    assert len(STRESS_CASES) == 8
    assert len(SEMANTIC_STRESS_CASES) == 10
    assert len(CONTRACT_STRESS_CASES) == 10
    assert len(SYSTEM_STRESS_CASES) == 10
    assert len(SCALE_CASES) == 2
    assert len(ALL_CASES) == 49
    assert len({case.name for case in ALL_CASES}) == 49


def test_every_case_starts_from_the_exact_same_generic_prompt() -> None:
    assert USER_PROMPT == "refactor this codebase"
    assert {case.user_prompt for case in ALL_CASES} == {USER_PROMPT}


def test_every_case_exposes_runner_metadata() -> None:
    for case in ALL_CASES:
        assert case.baseline_files
        assert case.hidden_tests
        assert case.structural_expectations

```

[166 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]