Przejdź do głównej zawartości

Refaktoryzacja na dużą skalę z worktree w Codex

Refaktoryzacja na dużą skalę z Codex dzieli jedną zmianę zachowującą działanie kodu, rozlaną na setki plików, na spisany plan, fazę expand na jednej gałęzi i niezależne partie. Każda partia działa we własnym worktree Gita i trafia do main osobno. Niezmienione testy HTTP, zapadka licząca użycia starego API do zera i bramki CI dowodzą poprawności każdej partii.

Zespół zdecydował: przed kolejnym wydaniem API przechodzi z Express na Hono, a każde getUserById zmienia nazwę na findUser. To 300 plików w kilkunastu grupach tras. Jedna długo żyjąca gałąź oznacza zamrożenie kodu i review, którego nikt nie skończy. Ta strona jest dla developera, który prowadzi refaktoryzację w Codex, i dla tech leada, który musi zatwierdzić każdą partię bez czytania każdej zmienionej linii.

Strona dotyczy refaktoryzacji kodu, który ma już testy i zachowuje swoje działanie. Jeśli refaktoryzujesz legacy bez testów, zacznij od modernizacji legacy na dużą skalę z Codex. Mechanikę pojedynczego worktree (co przenosi, jak go sprzątnąć) opisuje strona izolowane zadania Codex w worktree Gita.

  • Mapę zadań na powierzchnie Codex: co robi codemod, co sesja interaktywna, co partia w worktree, a co idzie do Codex cloud.
  • Kolejność expand, migrate, contract, dzięki której main da się wdrożyć każdego dnia refaktoryzacji.
  • Skrypt zapadki i inwentarz tras, które mechanicznie dowodzą, że „nic się nie zmieniło”.
  • Pięć promptów do skopiowania: plan, faza expand, partia, zmiana nazwy przez codemod i sprzątanie w fazie contract.
  • Listę dowodów dla każdej partii, którą tech lead może podpisać, i sposoby wyjścia z typowych awarii.

Która powierzchnia Codex robi które zadanie refaktoryzacji?

Dział zatytułowany „Która powierzchnia Codex robi które zadanie refaktoryzacji?”

Komendy poniżej sprawdziliśmy na codex-cli 0.157.1 (26.09.2026).

ZadaniePowierzchniaDlaczego tam
Zaplanowanie partiiInteraktywny codex w trybie planowania (/plan)Przeglądasz graf zależności, zanim zmieni się jakikolwiek plik
Czyste zmiany nazw (getUserById na findUser)Skrypt codemod, który Codex pisze i uruchamiaZmiana nazwy jest deterministyczna; skrypt nie pomija wywołań i nie improwizuje
Expand: wspólne middleware, adapteryInteraktywny codex na jednej gałęzi bazowejWspólny kod potrzebuje jednego właściciela i twojej uwagi
Migracja grup trasOsobny checkout git worktree add na partię, po jednej sesji codexPartie idą równolegle i nie edytują nawzajem swoich plików
Contract: usunięcie Expresscodex cloud exec --env ENV_ID --branch BRANCHDługie, mechaniczne zadanie, które nie potrzebuje laptopa
Pierwsze review każdej partiicodex exec review --base BRANCHModel recenzujący czyta diff, zanim zrobi to człowiek

Krok 1: Zaplanuj refaktoryzację i zapisz plan w pliku

Dział zatytułowany „Krok 1: Zaplanuj refaktoryzację i zapisz plan w pliku”

Równoległe partie wykładają się na wspólnych plikach, a nie na trudnym kodzie. Plan istnieje po to, by znaleźć każdy wspólny plik, zanim ruszy pierwsza partia. Uruchom codex w repozytorium, przełącz się w tryb planowania poleceniem /plan i wklej:

Czytaj listę właścicieli plików, nie prozę. Jeśli dwie partie potrzebują src/lib/session.ts, przenieś go od razu do fazy expand. Zacommituj plik planu na gałęzi bazowej refactor/hono-base, bo każdy prompt partii się do niego odwołuje.

Zanim przesunie się jakikolwiek kod, zapisz w komendach, co znaczy „zachowanie bez zmian”. Służą do tego dwa artefakty.

Wyrocznia. Testy HTTP w tests/http/ wysyłają prawdziwe żądania do aplikacji i sprawdzają kody statusu, nagłówki i treść odpowiedzi. Nie obchodzi ich, który framework odpowiada, więc w każdej partii muszą przechodzić bez zmian. Jedyny plik, którego partia może dotknąć, to bootstrap budujący aplikację pod testem.

Zapadka. Skrypt liczy pliki, które wciąż używają starego API. CI odrzuca zmianę, gdy liczba przekroczy zacommitowaną wartość bazową, a każda partia tę wartość obniża. Część dotycząca Express liczy tylko src/routes/: faza expand zostawia powłokę Express (src/app.ts, src/middleware/express/), która montuje Hono aż do fazy contract, więc licznik obejmujący całe src/ nie mógłby wcześniej spaść do zera:

#!/usr/bin/env bash
# scripts/refactor-ratchet.sh: run in CI on every pull request
set -euo pipefail
count=$( { grep -rlE "from ['\"]express['\"]|require\(['\"]express['\"]\)" src/routes/ || true; grep -rl "getUserById" src/ || true; } \
| sort -u | wc -l | tr -d ' ')
baseline=$(cat .refactor-baseline)
echo "files on the old API: $count (baseline $baseline)"
if [ "$count" -gt "$baseline" ]; then
echo "old API usage went up"; exit 1
fi

Wzorzec łapie oba style cudzysłowów i require(). Przed pierwszą partią uruchom skrypt i sprawdź, że licznik jest większy od zera: zero pierwszego dnia znaczy, że grep nie widzi twoich importów, a każda późniejsza kontrola przejdzie, niczego nie dowodząc.

Dodaj też inwentarz tras: scripts/print-routes.sh wypisuje posortowane metody i ścieżki wszystkich zarejestrowanych tras. Zacommituj jego wynik jako docs/refactor/routes.txt. Po każdej partii wynik musi być identyczny, co wyłapuje trasę usuniętą lub przemianowaną bez czytania jakiegokolwiek handlera.

Faza expand pozwala obu frameworkom obsługiwać ruch jednocześnie, więc każda partia trafia do aplikacji, którą da się wdrożyć. @hono/node-server (2.1.1 w npm, 26.09.2026) eksportuje getRequestListener, który zamienia aplikację Hono w handler żądań Node, a ten Express może zamontować pod prefiksem. Zostań na refactor/hono-base i wklej:

Jedna pułapka montowania: Express usuwa prefiks montowania z req.url, zanim wywoła zamontowany handler, więc pod /auth aplikacja Hono widzi /login, a nie /auth/login, i trasa Hono zdefiniowana z pełną ścieżką zwraca 404. Definiuj trasy Hono względem prefiksu montowania albo zamontuj listener bez ścieżki (app.use((req, res) => listener(req, res))), żeby Hono widziało pełne ścieżki; zarejestruj go po wszystkich trasach Express, jako ostatnie middleware, bo odpowiada na każde żądanie, które do niego dotrze. Testy HTTP i diff inwentarza tras wyłapią oba błędy.

Zmerguj tę gałąź do main, zanim ruszą partie. Od tej chwili main działa na obu frameworkach i pozostaje gotowy do wdrożenia.

Dla każdej grupy tras utwórz nazwaną gałąź i osobny checkout ze zmergowanej bazy. W terminalu, raz na partię:

Okno terminala
git fetch origin
git worktree add -b refactor/hono-auth ../api-hono-auth origin/main
cd ../api-hono-auth && npm ci && codex

codex --worktree tworzy zamiast tego zarządzany worktree, ale na odłączonym HEAD. Gdy praca ma skończyć się nazwanym pull requestem, prostsza jest forma git worktree add -b. Wklej ten sam prompt w każdej sesji i zmieniaj tylko nazwę grupy:

Jeśli wolisz uruchamiać partie ze skryptu, codex exec -C DIR wykona ten sam prompt nieinteraktywnie w każdym checkoucie. Skrypt rozdzielający pracę i limit równoległych torów znajdziesz na stronie przepływy wieloagentowe w Codex.

Zmiana nazwy dotyka setek wywołań i nie wymaga osądu, więc zadaniem agenta jest napisać narzędzie, a nie być narzędziem. Agent poprawiający ręcznie 400 wywołań może jedno pominąć; skrypt albo obsłuży każde dopasowanie, albo się na nim wywróci. Uruchom to we własnym worktree i w chwili, gdy żadna partia nie jest otwarta: w ramach fazy expand, zanim ruszą partie, albo po zmergowaniu ostatniej partii, a przed fazą contract. Nigdy nie uruchamiaj tego równolegle z partiami, bo codemod edytuje pliki, które plan przypisuje poszczególnym partiom.

Recenzent czyta wtedy 40-linijkowy skrypt zamiast diffu na 400 plików i może go uruchomić ponownie na nowszej bazie, jeśli gałąź się zestarzeje.

Merguj w kolejności z planu, po jednej partii na pull request, i daj CI przejść między mergami. Po każdym mergu zrób rebase otwartych partii na nowy main i uruchom ponownie ich bramki: zmiana middleware w jednej partii potrafi zepsuć treść błędów w innej. Poproś Codex w worktree po rebase:

Rebase this branch onto origin/main. Resolve conflicts by keeping main's version of any file
this batch does not own. Then rerun every command from the batch prompt and report the results.

Gdy zapadka pokazuje zero, żadna trasa ani żadne wywołanie nie używa już starego API. Zostaje powłoka Express, którą plan wymienia w sekcji CONTRACT (src/app.ts, src/middleware/express/), a jej usunięcie jest mechaniczne. Codex cloud klonuje repozytorium z remote’a, więc najpierw zrób push i podaj gałąź jawnie. W terminalu, gdzie ENV_ID to identyfikator lub etykieta twojego środowiska Codex cloud:

Okno terminala
git fetch origin
git push origin origin/main:refs/heads/refactor/hono-contract
codex cloud exec --env ENV_ID --branch refactor/hono-contract < docs/refactor/contract.md

codex cloud exec bez argumentu czyta prompt ze standardowego wejścia i wypisuje adres URL zadania; diff i apply przyjmują URL albo sam identyfikator z jego końca. Komenda git push tworzy zdalną gałąź bez przełączania twojej kopii roboczej, więc worktree poniżej dostaje nazwaną gałąź, na której zacommitujesz nałożony diff. Obejrzyj i nałóż wynik w czystym checkoucie, a nie w swojej kopii roboczej:

Okno terminala
codex cloud diff TASK_URL
git fetch origin
git worktree add -b refactor/hono-contract-apply ../api-hono-contract origin/refactor/hono-contract
cd ../api-hono-contract && codex cloud apply TASK_URL
npm ci && npm run typecheck && npm test

--attempts od 2 do 4 zamawia kilka niezależnych prób; przy mechanicznym sprzątaniu zwykle wystarcza jedna. Zobacz, kiedy best-of-N jest wart swojej ceny.

Jak zweryfikować partię refaktoryzacji bez czytania każdej linii?

Dział zatytułowany „Jak zweryfikować partię refaktoryzacji bez czytania każdej linii?”

Refaktoryzacja jest poprawna, gdy obserwowalne zachowanie się nie ruszyło. Każda partia dowodzi tego tym samym zestawem dowodów, zebranym komendami, a nie czytaniem:

  1. Wyrocznia się nie ruszyła. git diff --stat origin/main...HEAD -- tests/http/ jest pusty albo pokazuje tylko tests/http/setup.ts.

  2. Bramki przechodzą w CI, a nie tylko w raporcie agenta: npm run typecheck, npm run lint i npm test.

  3. Zapadka spadła. scripts/refactor-ratchet.sh pokazuje mniej plików, a .refactor-baseline obniżono w tym samym pull requeście.

  4. Inwentarz tras jest identyczny. Diff względem docs/refactor/routes.txt jest pusty.

  5. Automatyczny recenzent przeczytał diff pierwszy. W worktree partii uruchom:

    Okno terminala
    codex exec review --base origin/main

    Opcji --base nie da się połączyć z własnymi instrukcjami recenzji (sprawdzone w codex-cli 0.157.1: komenda kończy się błędem the argument '--base <BRANCH>' cannot be used with '[PROMPT]'). Gdy chcesz nadać recenzji własny kierunek, uruchom /review w interaktywnej sesji partii albo przekaż diff zwykłemu codex exec, który dołącza standardowe wejście do promptu:

    Okno terminala
    git diff origin/main...HEAD | codex exec "Flag any behavior change: status codes, headers, error bodies, middleware order, or edits outside this batch's files. Say NO FINDINGS if there are none."

    Na pull requeście to samo robi @codex review. Zobacz code review z Codex.

  6. Konkretna osoba podpisuje. Właściciel partii merguje na podstawie powyższych dowodów. Partie z uwierzytelnianiem i rozliczeniami dostają dodatkowo ludzką lekturę handlerów, zgodnie z zasadą czytania dowodów zamiast kodu.

  7. Produkcję da się cofnąć. Każda partia wychodzi osobnym wdrożeniem, więc regresja cofa jedną grupę tras, a nie całą refaktoryzację.

Umieść dowody w opisie pull requesta, żeby zatwierdzający nie musiał niczego uruchamiać ponownie. Format podaje strona o pakiecie dowodów, który musi przynieść pull request agenta.

ObjawPrzyczynaNaprawa
Dwie partie mają konflikt w tym samym plikuPlan przeoczył wspólny importZmerguj pierwszą partię, zrób rebase drugiej i przenieś plik do fazy expand w planie. Przed kolejną falą powtórz sprawdzenie wspólnych plików z promptu planującego
Partia jest zielona, bo zmienił się testAgent osłabił asercję, by pasowała do nowego zachowaniaPusty diff tests/http/ blokuje merge. Uruchom ponownie z „fix the implementation, never the test” na początku promptu
Testy przechodzą w każdym worktree, ale padają po dwóch mergachKażda partia działała na bazie bez zmian drugiejPo każdym mergu rób rebase otwartych partii i uruchamiaj ich bramki, jak w kroku 5
Odpowiedzi z błędem zmieniają kształt po migracjiDomyślna obsługa błędów w Hono różni się od middleware błędów w ExpressDodaj test charakteryzacyjny dla treści błędu, potem przenieś handler błędów w fazie expand, a nie w partii
Zmiana nazwy pominęła wywołania albo zmieniła nazwę niewłaściwej funkcjiAgent edytował ręcznie albo dopasowywał tekst zamiast typówCofnij zmianę i uruchom prompt z codemodem: skrypt świadomy typów, najpierw liczba z dry-run
Licznik zapadki rośnie na mainGałąź funkcjonalna kolegi dodała nowe trasy w ExpressCI to blokuje; poproś o trasę w Hono albo podnieś wartość bazową świadomie, w commicie po review
Zadanie w chmurze nie widzi aplikacji HonoGałąź nie została wypchnięta albo pominięto --branch i zadanie ruszyło na bieżącej gałęzi lokalnejNajpierw push, potem jawne --branch
Commity zniknęły razem z usuniętym worktreeZarządzany worktree na odłączonym HEAD usunięto, zanim jego commity trafiły na gałąź; jego reflog zniknął razem z nimZanim ruszy git gc, uruchom git fsck --lost-found, znajdź swój commit przez git show SHA, a potem git branch rescue/BATCH SHA. Zapobiegaj temu, zawsze tworząc nazwaną gałąź (git worktree add -b); zobacz przewodnik po worktree w Codex

Ten sam przepływ w innych narzędziach: refaktoryzacja z Claude Code i strategie refaktoryzacji w Cursorze.