ast-grep으로 리네임 작업을 처리하세요: Claude에게 수정할 파일만 맡기기
요약
Python의 `logger.warn()` 메서드 사용 중단에 대응하여, 코딩 에이전트에게 모든 파일을 수정하도록 맡기는 대신 코드모드(codemod)를 사용하는 것이 효율적입니다. 특히 ast-grep과 같은 도구는 텍스트가 아닌 구문 트리 기반으로 정확한 리네임 작업을 수행할 수 있게 합니다.
핵심 포인트
- 코딩 에이전트에게 모든 수정 작업을 맡기지 말고, 코드모드를 활용하여 분할하세요.
- ast-grep은 구문 트리를 기반으로 코드를 매칭하여 정확한 리네임을 가능하게 합니다.
- Ruff와 같은 린터도 `logging.warn`과 같은 리네임 작업에 유용합니다.
- 이 가이드는 린터가 감지하지 못하는 자체 클라이언트나 래퍼의 메서드 이름 변경에 초점을 맞춥니다.
Python의 logger.warn()은 Python 3.3부터 사용 중단(deprecated)되었습니다. CPython은 3.13 주기 동안 메인 브랜치에서 이를 제거했다가 다시 포함했는데, 그 이유는 단 한 줄로 "Python 3.13에서 사용 중단된 logging warn() 메서드를 유지합니다."였습니다. 현재의 사용 중단 페이지에는 여전히 버전이 지정되지 않은 채 "향후 버전에서 제거 예정(Pending removal in future versions)" 항목으로 남아 있습니다. 예전 철자법을 사용해도 작동합니다. 단지 아직 아무도 만료일을 정하지 않았을 뿐입니다.
만약 당신의 저장소에 수백 개의 이러한 호출이 있다고 가정해 봅시다. 가장 유혹적인 방법은 코딩 에이전트(coding agent)에게 프롬프트 하나를 주는 것입니다: 모든 곳에서 logger.warn을 logger.warning으로 교체하라고 지시하는 것입니다. 이 에이전트는 모든 호출을 찾고, 모든 파일을 열어 하나씩 수정해야 하며, 읽는 모든 파일은 컨텍스트에 포함됩니다. 그 과정에서 동일한 마지막 이름을 가진 다른 함수인 warnings.warn()을 마주치게 되며, 이 함수는 변경되어서는 안 됩니다. 그리고 에이전트가 여는 모든 파일은 그곳에 있는 동안 정리할 수 있다고 결정할 수 있는 추가적인 파일입니다.
대신 작업을 분할하세요. 코드모드(codemod)는 패턴이 결정할 수 있는 모든 호출을 처리합니다. 에이전트는 판단이 필요한 호출만 자신의 브랜치에서 받게 되며, 당신은 코드모드를 제외한 나머지 부분에 대해 검토를 수행할 수 있습니다.
여기서 사용되는 codemod 도구는 ast-grep이며, 텍스트가 아닌 구문 트리(syntax tree)를 기반으로 코드를 매칭합니다. 만약 이미 Ruff를 사용하고 있다면, 그 규칙 G010이 logging.warn과 Logger.warn을 플래그 지정하며 항상 수정 옵션을 제공합니다. 이 정확한 리네임 작업에는 그것을 사용하세요. 아래 단계들은 린터(linter)가 알지 못하는 리네임을 위한 것입니다: 자체 클라이언트, 자체 래퍼, 지난 스프린트에서 팀이 리네임했던 메서드입니다. logger.warn은 모든 단계를 확인할 수 있는 공개적인 예시입니다.
1단계: 한 줄 재작성(rewrite) 및 미리보기
brew install ast-grep, pip install ast-grep-cli 또는 npm i @ast-grep/cli -g을 사용하여 ast-grep을 설치하고, 빠른 시작 가이드를 참고하세요. 짧은 명령어는 sg이지만, Linux에서는 sg가 이미 setgroups 명령어이므로 이 글에서는 전체 이름을 사용합니다.
ast-grep run -l python \
-p '$OBJ.warn($$$ARGS)' \
-r '$OBJ.warning($$$ARGS)'
$OBJ는 임의의 단일 노드(node)를 매칭합니다: logger, self.log, logging.getLogger(__name__). $$$ARGS는 0개 이상의 노드를 매칭하며, 여기서는 모든 인자입니다 (패턴 구문). -U나 -i 없이 --rewrite run을 사용하면 적용될 diff만 보여줍니다 (재작성 가이드). 무언가를 작성하기 전에 그 diff를 읽어보세요.
문제는 여기서 발견할 수 있습니다. `warnings.warn(
ast-grep은 먼저 rule을 일치시킨 다음, constraints(YAML reference)를 사용하여 일치 항목을 필터링합니다. Constraints는 $OBJ와 같은 단일 메타 변수에 작동하며, $$$ARGS에는 작동하지 않습니다. regex는 사용자가 앵커(^ 및 $)로 지정하지 않는 한 노드 텍스트의 어느 곳에서나 일치시키므로, 이 경우 앵커가 중요합니다: 앵커가 없으면 "catalog"에 "log"가 포함되어 있기 때문에 catalog.warn(...)도 통과할 수 있습니다.
이 정규식은 사용자의 명명 규칙을 신뢰합니다. 만약 리포지토리 어딘가에서 log가 목록(list)이나 파일 핸들로 사용된다면, 이 규칙은 해당 호출도 재작성합니다. 적용하기 전에 log와 logger가 어디에 할당되는지 검색해 보세요.
그런 다음 브랜치를 만들고 미리보기, 적용 및 커밋을 수행하세요:
git switch -c rename/warn-codemod
ast-grep scan --rule codemods/warn-to-warning.yml
ast-grep scan --rule codemods/warn-to-warning.yml -U
...
--rule은 하나의 규칙 파일로 스캔하며 프로젝트 설정이 필요 없고, -U는 질문 없이 모든 재작성을 적용합니다(scan reference). 첫 번째 scan은 일치 항목을 보고할 뿐 아무것도 변경하지 않습니다. 두 번째, 즉 -U를 사용한 스캔이 수정 사항을 작성합니다. 규칙과 함께 변경 사항을 커밋하여 리뷰어가 수백 개의 동일한 덩어리(hunks)를 읽는 대신 다시 실행하여 동일한 diff를 얻을 수 있도록 하세요.
단계 3: 규칙이 남긴 것을 목록화하기
코담모드 브랜치에서 광범위한 패턴을 다시 실행합니다. warnings 이외의 수신자(receiver)에 대해 여전히 .warn(을 호출하는 것은 남아있는 잔여물입니다:
ast-grep run -l python -p '$OBJ.warn($$$ARGS)' \
--json=stream \
| jq -r 'select(.text | startswith("warnings.") | not)
...
--json=stream은 일치 객체 하나를 줄마다 출력하며, 각 객체는 일치한 text와 해당 파일(file)을 담고 있습니다(JSON output). jq는 warnings.warn 호출은 제거하고 파일 경로만 유지하며, sort -u는 파일당 한 줄만 남깁니다. 이 목록은 의도적으로 리포지토리 외부에 존재하므로 커밋에 포함되지 않습니다.
이것이 최종적으로 남게 되는 코드의 한 예입니다:
class Exporter:
def __init__(self, sink, audit):
self.sink = sink
...
패턴만으로는 self.sink가 logging.Logger인지, LoggerAdapter인지, 아니면 warn()이 다른 의미를 갖는 메트릭 클라이언트인지를 알려줄 수 없습니다. logging.getLogger(__name__)은 확실히 로거이지만, 당신의 정규식에는 이것이 포함되어 있지 않았습니다. 남아 있는 각 항목은 타입에 대한 질문이며, 호출 주변 코드를 읽어서 이 질문에 답하는 것은 에이전트가 잘 수행하는 작업입니다.
호출(Calls)만이 남는 것이 아닙니다. git grep -nw warn -- '*.py'도 getattr(log, "warn")나 handler = logger.warn 같은 것을 찾아내는데, 이는 어떤 호출 패턴에서도 포착하지 못합니다. 이 출력을 훑어보고 발견된 모든 파일을 수동으로 목록에 추가하세요.
4단계: 에이전트에게 오직 그 목록만, 독립적인 브랜치에서 전달하기
저는 Hermes IDE를 구축했으므로 이 단계를 염두에 두고 살펴보세요. 동일한 분할 작업을 위한 순수 git 명령어는 단계 끝에 있습니다.
⌘N을 누르고 Claude Code 또는 Codex를 선택하세요. Hermes는 에이전트를 실제 터미널에서, 자체 hermes/ 브랜치와 작업트리에서 시작하며, 당신의 작업을 첫 번째 프롬프트(README)로 설정합니다. 작업을 보내기 전에, 이것이 rename/warn-codemod에서 시작하는지 확인하세요. 이렇게 하면 코담(codemod)이 수정한 파일들은 에이전트가 열 때 이미 수정되어 있고, Land는 나중에 이 동일한 브랜치로 병합할 수 있습니다.
작업 텍스트는 파일 이름만 명시하고 다른 것은 아무것도 없습니다:
Finish renaming logger.warn( to logger.warning(
Only edit the files listed below. Do not touch any
other file. Do not reformat or refactor.
...

⌘N 새 작업 시트: 에이전트, 프로젝트, 새로운 hermes/ 작업트리, 승인 모드, 모델 및 노력, 그리고 Hermes가 실행할 명령어 미리보기.
Hermes 없이도 동일한 분할은 worktree와 하나의 명령어로 가능합니다:
git worktree add -b agent/warn-leftovers \
../warn-leftovers rename/warn-codemod
cd ../warn-leftovers
...
여기서 ~/warn-task.txt에는 위의 작업 텍스트가 목록과 함께 붙여넣어져 있습니다. claude "query"는 해당 프롬프트로 대화형 Claude Code 세션을 시작합니다. Codex의 경우, codex도 동일한 방식으로 선택적 프롬프트 인수를 받습니다.
단계 5: 차례별로 검토하고, 범위를 증명합니다
각 에이전트 답변은 세션 하단의 "Agent replies" 스트립에 작은 조각(chip) 형태로 나타나며, 추가하거나 제거한 라인들이 표시됩니다. 하나를 클릭하면 변경 사항을 보거나 해당 시점으로 파일을 복원할 수 있습니다(changelog). ⌘G (Ctrl+Shift+G)를 누르면 Review Desk가 열리며, 여기서 차례별 또는 파일별로 작업을 읽고, 라인 주석이 해당 라인을 작성한 에이전트에게 다시 전달됩니다.
차례당 두 가지 질문을 합니다: 지정된 파일만 건드렸는가? 그리고 남겨둔 각 호출에 대해, 그 이유가 타당한가? 메트릭 클라이언트의 warn()은 좋은 이유입니다. "불분명함(Unclear)"이라는 것은 해당 리시버를 직접 읽어봐야 한다는 의미입니다.

By turn 뷰의 Review Desk (⌘G): 파일과 라인이 있는 각 차례, 선택된 차례의 diff, 그리고 해당 차례들로부터 초안 작성된 커밋 메시지.
그다음 에이전트의 커밋이 자신의 브랜치에 있을 때 git을 사용하여 범위를 확인합니다. TASK를 세션에서 Hermes가 보여주는 브랜치 이름으로 설정하세요:
TASK=hermes/finish-renaming-logger-warn
git diff --name-only "rename/warn-codemod...$TASK" \
| grep -vxFf ~/warn-leftovers.txt
점 세 개짜리 diff는 작업 브랜치가 codemod 커밋을 떠난 이후 변경된 내용만 나열합니다(git diff). grep -vxFf는 목록에서 전체 라인이 아닌 모든 경로를 출력합니다. 출력이 없다는 것은 에이전트가 목록 안에 머물렀다는 의미입니다. 인쇄된 모든 라인은 당신이 그것에게 넘겨주지 않은 파일입니다: 해당 턴을 복원하거나, 에이전트에게 되돌리도록 요청하세요.
다음으로 Step 3 파이프라인을 다시 실행하되, > ~/warn-remaining.txt로 끝내어 원래 목록이 범위 확인(scope check)에 온전히 유지되게 하세요. 새 파일에는 오직 에이전트가 의도적으로 남겨두고 요약(summary)에서 이름을 지정한 수신자(receivers)를 가진 파일만 포함되어야 합니다. 마지막으로 pytest -W error::DeprecationWarning을 실행하세요. 이는 경고(warnings)를 테스트 실패로 변환하며(pytest docs), 따라서 테스트에서 도달하는 모든 warn() 호출이 명확하게 실패하도록 합니다. 다른 라이브러리의 사용 중단 경고(deprecation)에도 실패하므로, 당신이 어차피 알고 싶을 수도 있습니다.
모든 것이 깨끗하면, Land는 작업을 시작한 브랜치(당신이 rename/warn-codemod였음을 확인한)로 병합하며, 이는 되돌릴 수 있습니다. 그러면 풀 리퀘스트는 두 가지 작업 커밋을 포함하게 됩니다: 규칙 파일에서 누구나 다시 실행할 수 있는 codemod와, 라인별로 읽기에 충분히 작은 에이전트의 수동 편집본입니다.
이 분리가 깨지는 곳
ast-grep은 타입을 매칭하는 것이 아니라 구문(syntax)을 매칭합니다. 정규식 제약 조건(regex constraint)은 이름을 신뢰하므로, 이름이 잘못된 변수가 재작성되고 오직 당신의 테스트만이 이를 포착합니다. getattr(obj, name)과 같은 계산 속성 접근(Computed attribute access)은 패턴과 git grep 모두를 빠져나갑니다.
이 설정에서는 에이전트를 목록에 가두는 것이 아무것도 없습니다. 프롬프트가 요청하고, 그 이후의 git 확인이 그것이 경청했는지 알려주는 것입니다. 만약 남겨진 목록이 호출 지점(call sites)의 절반에서 나온다면, 패턴이 잘못된 것입니다: 무언가를 넘겨주기 전에 규칙을 강화하세요.
Hermes 역시 에이전트를 가두지 않습니다. 이는 에이전트가 보고하는 내용과 git이 보여주는 내용을 관찰할 뿐이며, 사용자에게 권한 프롬프트에 대해 절대 답변하지 않습니다 (README). 무료이며 BSL 1.1 하에서 소스 코드를 공개하고 있고, macOS, Windows, Linux에서 실행됩니다. Nous Research, Anthropic, OpenAI 또는 어떤 에이전트 공급업체와도 제휴하지 않았습니다. 저는 이 정확한 리네임 작업을 실제 코드베이스에 거쳐본 적이 없으므로, 만약 특정 단계가 사용자에게 다르게 작동한다면 issue를 열어주세요.
유용했다면
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기