stepback: 실제 git을 건드리지 않고 AI 코딩 에이전트의 편집 내용을 되돌리기
요약
AI 코딩 에이전트의 작업 중 실수를 방지하기 위해 Git 저장소를 손상시키지 않고 작업 트리를 스냅샷으로 되돌릴 수 있는 CLI 도구 'stepback'을 소개합니다. 임시 인덱스와 Git 플러밍 명령을 활용하여 실제 브랜치나 커밋에 영향을 주지 않고 안전하게 상태를 복구합니다.
핵심 포인트
- Git 저장소의 HEAD나 브랜치를 건드리지 않고 안전하게 스냅샷 생성
- 임시 인덱스(GIT_INDEX_FILE)를 사용하여 실제 작업 환경 보호
- 삭제된 파일 및 바이너리를 포함한 정확한 시점 복구 지원
- 원자적 파일 시스템 rename을 통해 복구 중 중단 시에도 데이터 무결성 유지
AI 코딩 에이전트가 6개의 파일을 편집하고, 그중 하나는 완전히 새로 작성해야 한다고 결정하며, 그 과정에서 실수로 debug_output.json 파일을 저장소 루트에 남겨두었다고 가정해 봅시다. 이제 당신은 마지막으로 상태가 좋았던 시점으로 되돌리고 싶습니다. git stash는 도움이 되지 않습니다. 에이전트가 아무것도 커밋하지 않았기 때문에 스태시(stash)할 내용이 없기 때문입니다. 에디터의 실행 취소(undo) 스택은 한 번에 파일 하나씩만 되돌리며, 그마저도 6개 파일 전체를 커버할 수 있을지 불확실합니다. 결국 당신은 수동으로 작업을 수행합니다: git diff를 실행하고, 눈을 가늘게 뜨고 확인하며, 일부 변경 사항(hunks)을 되돌리고, 쓰레기 파일을 삭제하며, 놓친 것이 없기를 기도합니다.
저는 바로 그 순간을 위해 stepback을 만들었습니다. 이것은 에이전트를 감싸는 CLI(Command Line Interface)로, stepback run -- claude, stepback run -- codex, stepback run -- aider 또는 디스크 상의 파일을 편집하는 그 어떤 것이든 실행할 수 있습니다. 에이전트가 작업하는 동안 작업 트리(working tree)를 스냅샷(snapshot)으로 찍어두며, 삭제된 파일과 바이너리를 포함하여 정확히 원하는 스냅샷 시점으로 되돌릴 수 있게 해줍니다.
$ stepback run -- claude
... 에이전트가 작업하도록 둡니다 ...
$ stepback list
...
git을 건드리지 않고 스냅샷을 찍는 방법
전체 설계를 결정지은 제약 조건은 다음과 같습니다: stepback은 당신이 실제로 작업 중인 저장소(repo)를 손상시키거나 심지어 조금이라도 건드려서는 안 된다는 것입니다. HEAD에 쓰기를 수행하지 않고, 당신의 브랜치에 커밋하지 않으며, 스테이징된 변경 사항(staged changes)을 건드리지 않습니다. 따라서 실제 인덱스(index)를 전혀 사용하지 않습니다.
모든 체크포인트(checkpoint)는 .git/index가 아닌, 임시 인덱스를 가리키는 GIT_INDEX_FILE을 통해 실행됩니다:
GIT_INDEX_FILE=<tmp> git add -A . # 임시 인덱스로 저장하며, .gitignore를 준수함
GIT_INDEX_FILE=<tmp> git write-tree # -> tree SHA 생성, 콘텐츠 주소 지정(content-addressed)
해당 트리는 stepback 고유의 작성자(author) 신분으로 commit-tree를 통해 커밋되며(따라서 로컬에 git 사용자가 설정되어 있지 않아도 작동합니다), refs/checkpoints/<session>/<n> 아래의 ref(참조)로 가리켜집니다. 브랜치 ref나 HEAD를 절대 사용하지 않습니다. 동일한 트리는 자동으로 중복 제거(dedupe)되므로, 유휴 상태인 에이전트가 체크포인트를 남발하지 않습니다. git 저장소 외부인 경우에는 stepback이 .stepback/shadow.git에 프라이빗 베어 오브젝트 스토어(private bare object store)를 생성하고, 대신 그곳을 대상으로 동일한 플러밍(plumbing) 명령을 실행합니다.
restore는 이와 반대되는 과정입니다. 현재 트리(tree)와 대상(target)을 diff 하고, checkout-index를 사용하여 실제로 변경된 파일들만 임시 위치에 stage 합니다. 더 이상 존재해서는 안 되는 것들은 삭제한 다음, 원자적(atomic)인 동일 파일 시스템 rename을 통해 stage된 각 파일을 실제 파일 위로 이동시킵니다. 만약 restore가 중간에 중단되더라도, 각 개별 파일은 완전히 이전 상태이거나 완전히 새로운 상태일 뿐, 절반만 쓰인 상태(half-write)가 되지 않습니다. 그리고 이 모든 과정이 실행되기 전에, stepback은 현재 상태를 커밋하고 자체 ref로 보호되는 redo 스택에 푸시(push)하므로, 되감기(rewind) 자체가 중단되더라도 stepback redo가 작동합니다.
격리 보장(isolation guarantee) 및 검증 방법
"실제 git을 건드리지 않는다"는 말은 주장하기는 쉽지만, 누군가 머지(merge) 도중에 되감기를 시도할 때 실수로 위반하기 쉽습니다. 따라서 이는 단순히 주장하는 것이 아니라 직접 테스트됩니다. 테스트 스위트는 일반적인 저장소(repo), detached HEAD, MERGE_HEAD가 존재하는 저장소, 그리고 index에 stage되었지만 커밋되지 않은 변경 사항이 있는 저장소 환경에서, 체크포인트 및 되감기(checkpoint-and-rewind) 사이클 이후에 index, HEAD, 그리고 브랜치가 바이트 단위로(byte-for-byte) 변경되지 않았음을 확인합니다. 총 67개의 테스트가 해당 격리 보장을 비롯하여 바이너리, 심볼릭 링크(symlinks), 유니코드, 공백 및 leading-dash 파일명의 정확한 복구, .gitignore 처리, 충돌 복구(crash recovery), 그리고 프로세스 간 잠금(cross-process locking)을 다룹니다. 이를 통해 실행 중인 watcher와 다른 터미널에서 수동으로 입력한 rewind가 서로의 상태를 손상시키지 않도록 합니다.
두 개의 레이어, 그리고
그 부분에 대해 과장해서 말하지는 않겠습니다. 그것들은 안정성이 전혀 보장되지 않는, 벤더(vendor)가 어떤 릴리스에서든 변경할 수 있는 비공개적이고 문서화되지 않은 세션 형식(session formats)입니다. 레이어 2(layer 2)는 의도적으로 어댑터 인터페이스(adapter interface) 뒤에 격리되어 있습니다. 엔진 레벨에서 모든 어댑터 메서드가 try/except로 감싸져 있기 때문에, 호출할 때마다 에러를 발생시키는 어댑터라 할지라도 레이어 1(layer 1)에는 영향을 줄 수 없습니다. 만약 사용 중인 에이전트가 Claude Code나 Codex가 아니거나, 업데이트로 인해 형식이 변경되었다면, stepback은 조용히 파일 전용 되돌리기(file-only rewind)로 전환됩니다. 충돌도 없고, 반쯤 망가진 상태도 발생하지 않으며, 단지 편의 기능 하나가 줄어들 뿐입니다. 이것이 전체 정책입니다: 확실한 결과가 있거나, 깔끔하게 기능이 저하되거나, 그 중간은 없습니다.
직접 시도해 보세요
pip install stepback
stepback run -- claude # 또는 -- codex, -- aider, 또는 임의의 명령어
stepback status # 저장 모드(storage mode), 세션(session), watcher, 감지된 어댑터(detected adapters)
...
현재 버전은 0.1.0입니다. 파일 레이어(file layer)는 실제 엔지니어링 노력이 집중된 부분이며, 제가 면밀한 검토를 원하는 부분입니다. 이슈(issues)와 PR(Pull Requests)은 언제나 환영하며, 특히 복구(restore) 과정에서의 엣지 케이스(edge cases)를 환영합니다. 코드는 github.com/Archerkattri/stepback에서 확인할 수 있으며, 패키지는 PyPI에 등록되어 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기