Umtri로 프로젝트 키우기4편

배포·리팩터링·버그를 Umtri에 기록하기

GitHub Actions로 배포를 자동 기록하고, 리팩터링 후 트리를 동기화하고, 버그의 영향 범위를 트리로 파악하고 등록하는 방법을 다룹니다.

기능을 쌓으며 트리에 기록하는 흐름을 익혔다면, 이제 개발 이후에 일어나는 이벤트들 — 배포, 리팩터링, 버그 — 도 Umtri에 남겨둘 차례입니다. 이 기록들이 쌓이면 “이 버그는 어느 배포 이후에 생겼지?” 같은 질문에 답할 수 있습니다.


배포할 때마다 자동으로 기록하기 (GitHub Actions)

배포가 성공했다면 이 사실을 Umtri 타임라인에 기록해두면 좋습니다. 언제 무엇을 배포했는지가 쌓이면 나중에 문제를 추적하는 데 도움이 됩니다.

이 기록을 수동으로 하는 것은 번거롭습니다. GitHub Actions를 사용하면 GitHub에 코드를 올릴 때마다 자동으로 Umtri에 기록할 수 있습니다.

GitHub Actions란

GitHub Actions는 코드를 저장소에 올리거나 특정 이벤트가 발생할 때 자동으로 작업을 실행하는 기능입니다. 테스트 실행, 빌드, 배포 등을 자동화할 때 사용합니다.

커밋이 트리에 붙는 방식

설정에 들어가기 전에 이 기록이 어떻게 노드와 연결되는지 알아둘 필요가 있습니다.

Umtri는 CI에서 job을 돌리거나 코드를 분석하지 않습니다. CI가 커밋 sha와 그 커밋에서 바뀐 파일 목록을 보내면, 서버가 그 파일들을 각 노드의 metadata.implements(그 노드를 실현한 파일 목록)와 대조합니다. 노드에 적힌 파일 중 하나라도 커밋에 들어 있으면 그 노드의 이력에 커밋이 쌓이고, 매칭되는 파일이 없는 노드는 건드리지 않습니다. 그래서 무관한 커밋이 트리를 어지럽히지 않습니다.

결과적으로 모든 노드가 자신을 만든 실제 커밋을 달고 있는 트리가 됩니다.

여기서 순서가 중요합니다. 커밋 기록은 언제나 마지막 단계입니다. 이번 작업에서 새로 생긴 단위가 있다면 노드를 먼저 만들고(metadata.implements에 파일 경로를 적고), 파일을 옮겼다면 그 노드의 implements를 먼저 고쳐야 합니다. 순서가 뒤집히면 커밋은 마침 존재하던 노드에만 붙고, 정작 이번에 생긴 부분은 트리에서 계속 보이지 않습니다. 트리를 코드에 맞추는 작업은 뒤의 리팩터링 절에서 다룹니다.

Umtri 자동 기록 설정

프로젝트 루트에 .github/workflows/ 폴더를 만들고, 아래 내용의 umtri-record-commit.yml 파일을 저장합니다.

name: Record commit to Umtri

on:
  push:
    branches: [main]

jobs:
  record:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Record commit to Umtri
        env:
          UMTRI_PAT: ${{ secrets.UMTRI_PAT }}
          UMTRI_GROUND_SLUG: ${{ vars.UMTRI_GROUND_SLUG }}
          BEFORE_SHA: ${{ github.event.before }}
          COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
        run: |
          # 설정이 없으면 조용히 건너뛴다
          if [ -z "$UMTRI_PAT" ] || [ -z "$UMTRI_GROUND_SLUG" ]; then
            echo "Umtri 설정이 없어 건너뜁니다."
            exit 0
          fi

          # 이번 push에서 바뀐 파일 목록 (새 브랜치면 커밋 하나만)
          if git cat-file -e "$BEFORE_SHA^{commit}" 2>/dev/null; then
            FILES=$(git diff --name-only "$BEFORE_SHA" "$GITHUB_SHA")
          else
            FILES=$(git diff --name-only "$GITHUB_SHA^" "$GITHUB_SHA")
          fi

          if [ -z "$FILES" ]; then
            echo "변경된 파일이 없습니다."
            exit 0
          fi

          printf '%s\n' "$FILES" \
            | jq -R . \
            | jq -sc --arg sha "$GITHUB_SHA" --arg message "$COMMIT_MESSAGE" \
                '{sha: $sha, message: $message, files: .}' \
            | curl -sS -X POST \
                "https://api.umtri.io/api/projects/$UMTRI_GROUND_SLUG/record-commit" \
                -H "Authorization: Bearer $UMTRI_PAT" \
                -H "Content-Type: application/json" \
                -d @-

그리고 GitHub 저장소의 Settings → Secrets and variables → Actions에서 두 가지를 등록합니다. 하나는 Secret, 하나는 Variable로 종류가 다릅니다.

이름종류
UMTRI_PATSecretwrite 스코프로 발급한 Umtri PAT (Bearer 토큰으로 쓰입니다)
UMTRI_GROUND_SLUGVariable이 저장소에 대응하는 그라운드 slug (예: bookmark-app)

토큰은 반드시 Secret으로 두고, 커밋되는 YAML 파일에 직접 적지 마세요. 유출됐다면 app.umtri.io 설정에서 폐기하고 새로 발급하면 됩니다.

설정이 끝나면 main 브랜치에 push할 때마다 그 커밋이 관련 노드에 자동으로 기록됩니다. 둘 중 하나라도 없으면 워크플로는 API를 호출하지 않고 그냥 넘어가므로, 아직 설정 전이라도 CI가 빨갛게 실패하지는 않습니다.

이 엔드포인트는 sha 기준으로 멱등합니다. 같은 커밋을 여러 번 보내도 노드에 중복으로 쌓이지 않으니 워크플로를 재실행해도 안전합니다.

기록이 쌓이면 부수적인 소득도 있습니다. 한 커밋이 아직 서로 연결되지 않은 여러 노드를 동시에 건드리면, 응답에 그 노드 쌍이 의존성 후보로 딸려 옵니다. 늘 같이 바뀌는 것들은 대개 관계가 있기 때문입니다. 자동으로 연결해주지는 않으니, 실제로 관계가 맞다면 직접 엣지를 걸어주면 됩니다.

Claude Code에 이 파일을 만들어달라고 요청해도 됩니다.

.github/workflows/umtri-record-commit.yml 파일을 만들어줘.
main 브랜치에 push할 때마다 바뀐 파일 목록을 모아서
https://api.umtri.io/api/projects/<slug>/record-commit 으로 POST하는 워크플로야.
토큰은 UMTRI_PAT Secret, slug는 UMTRI_GROUND_SLUG Variable로 읽어와.

CI를 붙이지 않았다면 같은 기능이 MCP에도 record_commit 도구로 있으니, 작업이 끝난 뒤 Claude Code에 직접 기록해달라고 해도 됩니다.

CI에 맡길 수 있는 것과 없는 것

CI는 sha를 절대 잊지 않는다는 점에서 기록에 적합합니다. 반대로 판단은 맡길 수 없습니다. 워크플로가 도는 시점엔 이미 커밋이 만들어진 뒤이고, 로그는 아무도 읽지 않으며, 어느 쪽이든 초록불이 뜹니다. 새 노드를 만들지, 옮긴 파일의 implements를 고칠지, 이 커밋이 어떤 버그를 닫는지는 사람이나 AI가 아직 작업 중일 때 결정돼야 합니다.

그래서 Umtri는 이 습관을 저장소의 규칙 파일(CLAUDE.md, AGENTS.md 등)에 적어두라고 권합니다. 매 세션이 알아서 읽어가는 파일이기 때문입니다.

### Umtri 동기화 (커밋 전)

Umtri는 git을 읽지 않는다. 커밋을 만들기 전에 이 변경이 트리에도 반영돼야 하는지 확인한다.

1. 바뀐 파일 경로를 노드의 `metadata.implements`와 대조한다.
   - 새 단위(라우트·화면·테이블·외부 연동)가 생겼다면 → `create_node` + `metadata.implements`.
     연결(`create_edge` / `create_api`)도 함께 건다. 연결 없는 노드는 영향 분석에 잡히지 않는다.
   - 파일을 옮기거나 이름을 바꿨다면 → 해당 노드들의 `implements``update_node`로 수정한다.
   - 동작만 바뀌고 구조는 그대로면 → 할 일 없음.
2. 이 커밋이 추적 중인 버그를 닫는가? → `update_bug` (상태, 그리고 실제로 한 조치를 `solution`에).
3. 그 다음 `record_commit`으로 sha와 바뀐 파일 목록을 기록한다.

애매한 것은 노드를 만들기 전에 사람에게 물어본다. 잘못 만든 노드가 빠진 노드보다 알아채기 어렵다.

이렇게 적어두면 3번은 CI가 자동으로 처리하고, 1·2번은 커밋을 만드는 그 자리에서 걸러집니다.


리팩터링 결과를 트리에 반영하기

리팩터링은 새 기능을 추가하는 것이 아니라 구조를 바꾸는 작업이므로, Umtri 트리도 바뀐 구조에 맞게 업데이트해야 합니다.

리팩터링이 완료됐어. Umtri bookmark-app 트리를 아래와 같이 업데이트해줘:

- storage.js를 새 leaf 노드로 추가 (BookmarkList twig 아래)
- useBookmarks.js를 hooks twig 아래 leaf로 추가
- App.jsx leaf 설명을 "UI 레이아웃만 담당"으로 업데이트
- 삭제된 loadData() 함수 관련 노드 제거

트리가 코드와 동기화되어 있으면 나중에 AI에게 “지금 구조가 어떻게 되어 있어?”라고 물었을 때 Umtri 트리를 가져오는 것만으로 정확한 현재 상태를 파악할 수 있습니다.


버그의 영향 범위를 트리로 파악하기

버그의 원인을 찾았다면 이제 “이 버그가 얼마나 많은 부분에 영향을 주는가”를 파악해야 합니다. 하나의 버그가 여러 기능에 걸쳐 영향을 줄 수 있습니다.

예를 들어 localStorage에 데이터 저장 방식에 문제가 있다면 북마크 저장뿐만 아니라 태그 저장, 설정 저장 등 localStorage를 쓰는 모든 기능에 영향을 줄 수 있습니다.

이 범위를 파악하는 전용 도구가 get_impact입니다. 트리를 통째로 가져와 눈으로 훑는 대신, 노드 하나에서 출발해 엣지와 API 연결을 따라가며 “이게 깨지면 어디까지 확인해야 하는가”를 돌려줍니다.

Umtri bookmark-app에서 storage.js 노드의 영향 범위를 확인해줘.
localStorage 저장 로직이 깨지면 어느 기능들까지 확인해야 해?

여기서 한 가지 짚고 갈 것이 있습니다. 영향은 화살표 방향대로 흐르지 않습니다. 의존성 엣지는 거꾸로(대상 → 출발), 데이터 흐름 엣지는 그대로, API는 호출당하는 쪽에서 호출하는 쪽으로 거슬러 올라갑니다. “A가 B에 의존한다”면 B가 깨졌을 때 아픈 쪽은 A이기 때문입니다. 반대로 “이 노드가 무엇에 기대고 있나”가 궁금하면 방향을 dependsOn으로 주면 됩니다.

그리고 결과는 확인해야 할 목록이지, 확정된 피해 범위가 아닙니다. 기록된 연결만큼만 정확하기 때문입니다. 특히 출발 노드에 연결이 하나도 없으면 결과가 빈 채로 돌아오는데, 이건 “영향 없음”이 아니라 “기록이 없음”입니다. 이럴 때는 엣지부터 채워야 합니다.


버그를 Umtri에 등록하기

발견한 버그를 Umtri에 등록해두면 나중에 “언제 어떤 버그가 있었고 어떻게 해결했는가”를 추적할 수 있습니다.

MCP가 연결된 상태에서 Claude Code에 이렇게 요청합니다.

아래 버그를 Umtri bookmark-app에 등록해줘.

제목: 북마크 삭제 후 새로고침 시 복구되는 현상
대상 노드: storage.js (leaf)
위험도 점수: 5
상태: open
해결 방향: 삭제할 때 localStorage 갱신이 빠진 것으로 보임 — 삭제 핸들러의 저장 호출 확인

버그는 노드나 API에 붙이고, 특정 노드로 좁히기 어려운 문제라면 프로젝트 전체에 붙일 수도 있습니다.

Umtri는 high·medium 같은 심각도 라벨 대신 0~8의 위험도 점수를 씁니다. 기능적 영향도와 수정 난이도를 함께 본 값으로, 기본값은 4이고 8이 가장 위험합니다. 시급도는 일부러 넣지 않았습니다 — 그건 상황에 따라 사람이 정할 몫이기 때문입니다.

점수
0–1기능에 영향 없음 (아이디어 메모, 문구 수정)
2오류는 아니지만 사용성이 달라질 수 있음
3–4사소한 기능 문제 (수정이 간단 / 복잡)
5–6눈에 띄는 기능 문제 (간단 / 복잡)
7–8치명적 문제 (간단 / 복잡)

상태는 open → in_progress → resolved 순으로 한 단계씩 옮깁니다. 특히 in_progress는 “누군가 이미 이 버그를 붙잡고 있다”는 유일한 표시라, 다 고친 뒤가 아니라 고치기 시작하는 순간에 바꿔두는 편이 좋습니다. 다른 세션이나 다른 사람이 같은 버그를 중복해서 파고드는 일을 막아줍니다.

해결 방향으로 적은 내용은 solution 필드에 들어갑니다. 등록 시점엔 “아마 이렇게 고치면 될 것 같다”는 계획이고, 실제로 고친 뒤에는 정말로 한 조치로 덮어씁니다. 같은 칸을 계속 고쳐 쓰는 구조라, 나중에 비슷한 현상이 생겼을 때 참조하게 되는 것도 이 필드입니다. 옛 계획을 그대로 남겨두는 건 아예 비워두는 것보다 나쁩니다. 모르겠으면 비워두세요.

등록하고 나면 응답에 그 노드의 영향 범위가 함께 딸려 옵니다. 어떤 노드까지 닿는지, 몇 홉 떨어져 있는지, 어떤 연결을 타고 갔는지, 그 반경 안에 이미 떠 있는 다른 버그는 무엇인지까지. 앞 절의 get_impact를 따로 부르지 않아도, 버그를 등록하는 것만으로 “이것 때문에 어디까지 QA해야 하나”가 같이 나오는 셈입니다.


다음 편에서

마지막 편에서는 이렇게 쌓인 기록 — 트리, 타임라인, 버그 이력 — 이 혼자든 팀이든 어떤 차이를 만드는지, Umtri의 시각화가 왜 유용한지를 정리합니다.