Monorepo 리팩터링: MCP 게이밍 에이전트 앱을 uv 워크스페이스 멤버로 apps/로 이동하기
README 링크에서 발견 · today
에이전트가 Python MCP 서버를 apps/로 옮기고 모든 경로를 다시 연결했습니다
What they did
에이전트는 단일 패키지 Python 저장소(AI 에이전트가 게이밍 PC를 제어할 수 있게 해주는 MCP 서버)를 모노레포 레이아웃으로 재구성했습니다. git mv를 사용해 examples/, idea.md, pyproject.toml, src/, tests/, uv.lock의 여섯 그룹을 apps/ai-gaming-agent-mcp/ 아래로 이동했고, 그 결과 git status는 30개 파일 전부를 rename으로 보고했으며 이전 경로에는 아무것도 남지 않았습니다. 이어서 저장소 루트에 새 pyproject.toml을 만들어 [tool.uv.workspace]를 선언하고 members = ["apps/ai-gaming-agent-mcp"]로 설정했으며, 앱 자체의 pyproject.toml 안에도 일치하는 [tool.uv.workspace] 블록을 추가해 앱 디렉터리가 자체 uv workspace 루트로 유지되고 uv가 루트 lockfile을 생성하는 대신 계속 apps/ai-gaming-agent-mcp/uv.lock을 사용하도록 했습니다. 이동된 pyproject.toml에서는 두 가지 내용 수정이 필요했습니다. readme = "README.md"는 앱 디렉터리 안에 README.md가 존재하지 않아 더 이상 해석되지 않았으므로 인라인 텍스트 readme와 content-type으로 대체했습니다. 그 외의 항목(packages, testpaths, ruff 설정, project.scripts)은 해당 경로들이 앱 기준 상대 경로이기 때문에 그대로 두었습니다. 이전 저장소 루트 위치를 가리키던 문서와 명령줄은 다시 작성했습니다. examples/README.md의 cd examples, uv run examples/terminal_demo.py, python examples/terminal_demo.py 같은 명령은 저장소 루트에서 cd apps/ai-gaming-agent-mcp && ... 형태로 바뀌었고, terminal_demo.py의 docstring도 새 경로로 갱신했습니다. Python import는 변경할 필요가 없었습니다. src/와 tests/의 모든 import는 절대 경로(from ai_gaming_agent.config import Config)이고 워크플로 디스패치 테이블은 파일이 아니라 모듈을 지정하므로, 패키지 이름은 디렉터리 이동 후에도 그대로 유지되었습니다. 에이전트는 요청에 담긴 세 가지 전제를 명시적으로 반박했습니다. 저장소 루트에 pyproject.toml이 이미 존재한다는 전제(실제로는 생성해야 했음), readme 키가 파일 기반으로 유지될 수 있다는 전제, import를 수정해야 한다는 전제입니다.
How it went
모든 acceptance check를 통과했습니다. ./bin/check (ruff와 unit test, app 디렉터리에서 실행)는 약 2초 만에 exit 0으로 종료되었습니다. app 디렉터리의 uv lock --check는 exit 0이었고 repository root에는 uv.lock이 없었으며, uv sync --extra dev도 exit 0으로 종료되어 inline readme 교체 후에도 package metadata가 정상적으로 빌드됨을 확인했습니다. git status에는 여섯 그룹에 대한 rename만 표시되었고, README.md, CLAUDE.md, docs/, bin/, .github/, .claude/, .dz/에 대해서는 rename이나 deletion이 없었습니다. grep으로 app 디렉터리 밖에 old-form path reference가 남아 있지 않음을 확인했습니다. 이 변경은 5-part stacked plan의 part 4로 반영되었습니다.
Worth knowing
저장소 루트의 README.md는 의도적으로 이동하지 않았기 때문에, 이제 앱의 readme 키는 파일이 아니라 인라인 문자열입니다. 나중에 README.md를 앱 디렉터리로 옮기는 작업이 있다면 파일 기반 키를 복원해야 합니다. 루트 pyproject.toml은 새로 추가된 최소 구성으로, 워크스페이스 멤버십만 선언하므로 향후 루트 수준의 도구 설정은 별도로 추가해야 합니다. 이번 이동은 ruff와 단위 테스트로만 검증되었고, 실제 게이밍 PC를 대상으로 한 MCP 서버의 엔드투엔드 실행으로는 검증되지 않았습니다. 또한 uv.lock은 앱이 자체 워크스페이스 루트로 유지된다는 가정하에 바이트 단위로 동일하게 보존되었으며, 이 가정이 깨지면 앱 디렉터리 안에서 uv lock을 다시 실행해야 합니다.
Wiring
순서가 중요합니다. 먼저 여섯 그룹(examples/, idea.md, pyproject.toml, src/, tests/, uv.lock)을 `git mv`로 apps/ai-gaming-agent-mcp/ 안으로 옮겨 git이 삭제-추가가 아닌 이름 변경으로 추적하게 합니다. 둘째, 저장소 루트에 `[tool.uv.workspace]` members = ["apps/ai-gaming-agent-mcp"]를 담은 pyproject.toml을 만듭니다. 셋째, 옮긴 앱의 pyproject.toml을 편집합니다: 파일 기반 readme 키를 인라인 text readme로 바꾸고, 빈 members를 가진 자체 `[tool.uv.workspace]` 블록을 추가해 uv가 앱 디렉터리를 워크스페이스 루트로 취급하고 루트 lockfile을 쓰는 대신 apps/ai-gaming-agent-mcp/uv.lock을 해석하도록 합니다. 넷째, examples/README.md와 terminal_demo.py docstring의 경로 및 명령 참조를 apps/ai-gaming-agent-mcp/... 형태로 다시 씁니다. 임포트는 절대 모듈 경로이므로 수정이 필요 없습니다. 검증 체인: 이름 변경만 있는 diff를 위한 git status, 앱 디렉터리 안에서 uv lock --check, 앱 디렉터리에서 실행하는 ./bin/check(ruff 및 pytest), 메타데이터 빌드 확인을 위한 uv sync --extra dev, 그리고 앱 디렉터리 밖에서 오래된 형태의 경로를 찾는 grep.
Try it yourself
I'll restructure the repo into a monorepo layout. Let me start by examining the current state. <||DSML|| calls> <||DSML|| invoke name="Bash"> <||DSML|| parameter name="command" string="true">cd /repo 2>/dev/null || cd "$(git rev-parse --show-toplevel)" && pwd && git status --short && ls -la</||DSML|| parameter> <||DSML|| parameter name="description" string="true">Inspect repo root and git status</||DSML|| parameter> </||DSML|| invoke> </||DSML|| calls>