gwimong's blog Software Engineer

artifact-transformer 작업 일지 #34

도구를 자기 자신에게 겨눈 날 — 그린필드 부트스트랩, 정적 표를 버린 Notion 발행, 그리고 문서 전체를 다시 쓰지 않는 AI 편집


이 글은 2026-08-11 하루의 기록이다. 오전엔 이 도구로 이 도구 자신의 산출물을 처음부터 만들었고, 오후엔 Notion에 깔리던 정적 산출물 표를 인라인 DB로 갈아엎어 9개 프로젝트 342종을 다시 올렸다. 그 사이 다른 두 세션이 같은 워킹트리에서 AI 응답 속도와 통계 대시보드를 동시에 건드렸고, 그 동시성 자체가 오늘의 가장 값비싼 함정이 됐다.

배경 — 남의 프로젝트만 만들어 온 도구

이 도구는 지금까지 아홉 개 남짓한 프로젝트의 산출물을 만들어 왔다. 그런데 정작 자기 자신은 대상이 아니었다. env/project.artf.env가 없었고, 자기 시스템의 요구사항·업무·유스케이스가 어디에도 없었다.

동시에 Notion 발행 쪽에는 오래된 빚이 있었다. 발행 도구는 산출물 목록을 정적 표로 깔았다. 발행할 때마다 통째로 다시 쓰이니 그 위에 사람이 아무것도 적을 수 없고, 정렬·필터도 안 되고, 갱신이 멈추면 그 자리에서 낡는다.

세 번째는 속도다. 웹 UI의 AI 편집이 분 단위로 걸린다는 불만이 있었지만, 어디가 느린지 아무도 재 본 적이 없었다.

문제

  1. 그린필드 부트스트랩이 처음부터 끝까지 도는가 — 실제로 돌려 보면 어디서 걸리나.
  2. 산출물 목록을 DB로 바꿀 때, 사람이 직접 쓴 블록과 도구가 쓴 블록을 무엇으로 가르나.
  3. AI 편집이 느린 진짜 원인은 모델인가, 하네스인가, 아니면 설계인가.
  4. 세 프로세스가 한 워킹트리를 공유할 때 내 변경만 어떻게 골라 내나.

결정 1 — 도구를 자기 자신에게 겨눈다

SYSTEM_CODE=ARTF로 env를 만들고 artf-docs를 새 저장소로 열었다. Jira 프로젝트가 없어 요구사항은 CSV 폴백으로 13건을 만들었고(합성 issueKey ARTF-001~013), 영역은 파이프라인·다이어그램·협업·외부 연동 4종으로 갈랐다.

원천부터 규약대로 썼다. BCDG·UCDG 각각 partial 4장 + OVERVIEW, C4 3레벨(CTXDG/CNTDG/CMPDG), 시퀀스 11장. 여기서 나온 입력 JSON 16종에 논리 9엔터티(속성 61)·물리 9엔터티(컬럼 62)를 채웠다.

결과는 check.py 기준 스키마 0 / 추적성 ERROR 0 / 드리프트 0, 다이어그램 31종 렌더 성공, generate.py all --check로 산출물 39건 전부 생성이다.

핵심은 결과가 아니라 가는 길에 걸린 것들이다. 도그푸딩이 잡아낸 실제 버그가 두 건 나왔다.

# ① init.py:231 — 아키텍처 준비 검사가 비재귀 glob 이었다
- arch.glob("*.puml")     # level1/2/3 하위를 못 본다 (공식 지원 구조인데)
+ arch.rglob("*.puml")

# ② --force 가 도출기 모듈까지 전달되지 않아 재도출이 조용히 no-op

둘 다 남의 프로젝트를 돌릴 때는 드러나지 않는 종류다. level1/2/3을 실제로 쓰는 프로젝트가 없었고, --force는 재도출이 필요한 상황 자체가 드물었다.

파서 쪽에서도 하나 배웠다. 유스케이스 다이어그램에서 액터를 actor writer처럼 맨몸으로 쓰면 파서가 무시한다. 인용형 actor "writer"가 필요하다. 기존 프로젝트에서 이게 안 터진 이유는 파서가 JSON의 기존 값을 보존하기 때문이다 — 즉 한 번 채워진 뒤에는 파싱이 실패해도 성공한 것처럼 보인다.

빈 저장소에서 처음부터 돌려 보는 것은 기능 검증이 아니라 전제 검증이다. 이미 값이 들어 있는 프로젝트는 깨진 경로를 덮어 준다.

결정 2 — 목록은 표가 아니라 DB다, 단 사람 글은 건드리지 않는다

발행 코드에는 원래 두 모드가 있었다. 인라인 「산출물」 DB 모드와 단계별 하위 페이지 모드. 자동 판별이라고 돼 있었지만 DB 모드는 DB가 이미 있을 때만 동작했고, 없으면 하위 페이지 모드로 떨어져 정적 표가 깔렸다.

그래서 DB를 기본으로 뒤집고, 없으면 만들도록 했다. ARTIFACT_DB_SCHEMA를 발행·복원의 단일 출처로 두면서 select 옵션의 색까지 고정했다. Notion API는 없는 옵션 값을 만나면 옵션을 새로 만들며 색을 무작위로 붙이는데, 그러면 목록의 시각적 구분이 조용히 무너진다.

전환 대상을 조사하니 Projects DB 16개 행이 세 갈래로 갈렸다. 도구가 pages 모드로 발행한 9개가 진짜 대상이고, 이름만 비슷한 2건은 문서별 목록이 아니라 단계별 집계 요약표(4행)라 DB로 바꾸면 오히려 나빠진다. 3개는 이미 DB였다.

전환하려니 갭이 있었다. DB 모드는 “행 본문을 건드리지 않는다”가 원칙이라, pages 모드가 남긴 정적 표가 그대로 남는다. 지우긴 지워야 하는데 사람이 쓴 표까지 지우면 사고다. 경계를 이렇게 그었다.

규칙이유
개요 앵커 콜아웃이 있을 때만 동작그 콜아웃은 도구만 쓴다 = 이 페이지는 도구 소유
지우는 범위는 heading + 표 + 안내 문단까지다음 섹션을 만나면 즉시 중단
하위 페이지는 제목이 발행 대상과 정확히 일치할 때만사람이 만든 회의록·참고자료를 남긴다
전체 발행이 전부 성공했을 때만 잔재 정리부분 발행 상태에서 정리하면 원본이 사라진다

FDSP 한 개로 시범 전환하고, DB 35행이 전부 본문을 갖고 있는지 검증한 뒤에야 하위 페이지를 보관 처리했다. 이 순서가 전부다 — 지우기 전에 옮겨진 것을 확인한다.

나머지 8개로 넘어가기 전에 하나 더 걸렸다. 네 프로젝트의 documents/가 입력 JSON보다 3~24일 낡아 있었다. 그대로 변환해 올리면 7월 판이 Notion에 발행된다. 데이터를 손대는 대신 generate.py all --check로 재생성했고 전부 검증 게이트를 통과했다.

최종적으로 9개 프로젝트 342종 발행, 실패 0. 페이지 구조는 모두 발행 안내 → 프로젝트 개요 표 → [DB] 산출물로 정리됐다.

덤으로 버그 두 건이 딸려 나왔다. 배포 상태 확인이 하위 페이지만 뒤져서 DB로 관리되는 프로젝트는 웹 UI에서 **늘 “전부 배포 필요”**로 보였고, 버전 마커를 첫 블록에서만 읽어서 사람이 쓴 머리말 콜아웃이 있는 페이지는 버전을 못 읽었다. 둘 다 DB 모드에서만 드러나는 것들이라, 모드를 기본으로 바꾸지 않았으면 계속 숨어 있었을 것이다.

결정 3 — 문서 전체를 다시 쓰지 않는다

AI 편집 지연을 재 봤다. 사용량 로그 166건 집계에서 평균 출력이 12,074 토큰, 최대 28,055 토큰이었다. 같은 형태 작업의 실측 처리량이 134 tok/s니 평균만으로 90초, 최대치는 3분이다.

원인은 모델이 아니라 설계였다. propose()가 문서 전체 JSON을 넣고 수정된 문서 전체를 받는 구조였다. 요구사항 1건을 고쳐도 100건을 전부 다시 쓴다.

하네스도 의심했지만 아니었다. claude -p 왕복 3.4초 중 프로세스 기동이 1.2초이고, 40k 토큰짜리 하네스 컨텍스트는 캐시 히트라 TTFT 기여가 작았다. CLI는 호출당 1~2초 고정비일 뿐 분 단위 지연의 원인이 아니다.

그래서 출력 형식을 바꿨다.

{"explanation": "…",
 "upsert": {"requirements": [ …바꾼 항목만, 각 항목은 전체… ]},
 "remove": ["REQ-COM-003"]}

병합 로직은 새로 만들 필요가 없었다. 범위 강제에 쓰던 식별자→항목 맵이 이미 순서를 보존하며 합치는 일을 하고 있어서, 그 경로를 재사용했다. 표현할 수 없는 구조 재편만 기존 전체 출력으로 폴백한다.

실측은 suoms requirement.json(82KB·75건)에서 1건 수정으로 잡았다.

이전(로그 평균)델타 적용 후
출력 토큰12,0742,053
응답 본문문서 전체807자
변경 결과—75건 중 의도한 1건만

부수 효과가 더 컸다. 코드 주석에 *“문서가 커지면 모델이 봉투 형식을 흘리고 문서만 출력하는 일이 잦다”*고 적혀 있던 증상이 사라진다. 그건 모델의 변덕이 아니라 과대 출력의 증상이었다. 검증 실패 재시도 비용도 같이 줄었다.

여기에 용도별 모델 라우팅을 붙였다. 채팅·요약은 Haiku, 편집은 Sonnet, 영향성·리뷰 검토는 Opus다. 이게 필요했던 이유가 로그에 있었다 — 166건 중 114건이 모델 미지정으로 CLI 기본값(가장 큰 모델)에서 돌고 있었다. 기계적 편집에 최상위 모델을 쓰고 있었던 것이다.

정작 마지막에 걸린 건 사람 쪽 설정이었다. ai-settings.json에 전체 고정 모델이 박혀 있어 등급별 라우팅이 통째로 무력화됐다. 비용을 의식해 일부러 고정한 것일 수 있어 임의로 바꾸지 않고 물어본 뒤 비웠다.

지연을 재기 전에 고치면 하네스를 최적화하다가 하루를 보낸다. 병목은 대개 “얼마나 많이 쓰게 했는가”에 있다.

결정 4 — 통계는 새 수집 인프라 없이

같은 시간대에 다른 세션이 통계 대시보드를 붙였다. 기존에 현재 상태 스냅샷과 서버 운영 지표는 있었고, 비어 있던 건 시간축 추이와 분포였다.

수집 테이블을 새로 두지 않고 이미 있는 원천만 집계했다 — git 이력, 문서의 개정이력, 단계 이력, 리뷰 기록, 입력 JSON. 새 수집기를 붙이면 “집계 시작 시점” 이전이 영영 비고, 그 시점을 따로 관리해야 한다.

창 크기는 축마다 다르게 잡았다. 활동은 주 단위, 개정은 월 단위다. 커밋은 잦고 개정은 드물어서 같은 창에 넣으면 한쪽이 뭉갠다.

차트는 라이브러리 없이 갔다. 필요한 형태가 막대와 시간축 막대뿐이라 번들을 늘릴 이유가 없었고, 계열 색을 돌려쓰지 않는 대신 상태색은 상태 분포에만 썼다.

같은 맥락에서 프로젝트 설정(저장 경로·구현 코드 저장소)을 관리자 화면에서 프로젝트 창으로 옮겼다. 이건 SSO·연동 키 같은 시스템 설정이 아니라 프로젝트마다 다른 설정이다. 다만 경로 변경은 서버 파일시스템을 지정하는 일이라 쓰기 권한은 관리자로 유지하고, 읽기 전용 창구를 따로 냈다. 비관리자에게는 편집 버튼이 아예 보이지 않는다 — 눌러도 403인 버튼을 만들지 않기 위해서다. 게이트와 화면 표시가 어긋나지 않도록 관리자 판정은 한 함수로 모았다.

의도치 않은 함정 — 한 워킹트리, 세 프로세스

오늘의 진짜 사고는 기능이 아니라 동시성이었다.

통계 작업 세션이 기준선을 확인하려고 git stash를 썼다가, 다른 세션의 미커밋 작업까지 통째로 쓸어 담았다. 그 사이 그쪽이 같은 파일을 다시 써서 pop이 중단됐다. 스태시에서 파일별로 꺼내고 충돌 파일은 수기로 재적용해 복구했지만, 이 저장소에서 stash를 쓰지 않는다는 규칙을 메모리에 남겼다.

반대편에서는 오진이 있었다. AI 속도 작업 세션이 git status를 보고 “내 수정이 전부 사라졌다”고 판단했는데, 실제로는 병렬 프로세스가 파일을 되돌렸다 복원하는 과도기 상태를 본 것이었다. 마커를 직접 확인해 정정했다. 동시 편집 중에 git status 한 장면은 사실이 아니다.

커밋도 파일 단위로는 불가능했다. 훙크 단위로 자기 변경만 골라 스테이징했고(한 파일은 7개 중 4개, 다른 파일은 8개 중 5개), 인덱스 내용을 별도 트리로 꺼내 컴파일·테스트까지 돌려 커밋될 상태 그 자체를 검증한 뒤 커밋했다.

함정이 하나 더 있었다. git pull로 받은 커밋이 로컬 env 파일 11개를 지웠다. 커밋 메시지에는 “로컬 파일은 그대로 남아 있다”고 적혀 있었지만, 그건 git rm --cached를 실행한 본인에게만 참이다. 그 커밋을 받는 쪽에서는 평범한 추적 파일 삭제로 적용된다.

git rm --cached는 실행자와 수신자에게 다르게 동작한다. 커밋 메시지에 “로컬은 안전하다”고 쓰는 순간 그 문장은 절반만 맞다.

마무리

오늘 확인한 것은 세 가지다.

  • 도그푸딩은 기능이 아니라 전제를 검증한다. 빈 저장소에서 처음부터 돌리자 남의 프로젝트에서는 값이 덮어 주던 버그 두 건이 바로 드러났다.
  • 지우는 코드는 소유권 표식이 있을 때만 지운다. 앵커 콜아웃 하나가 도구 소유와 사람 소유를 가르는 유일한 근거였고, 그 규칙 덕에 342종을 무사히 옮겼다.
  • LLM 지연은 대개 출력량 문제다. 전체 재출력을 델타로 바꾸자 응답 본문이 56배 줄었고, 별개 문제로 보이던 형식 이탈까지 같이 사라졌다.

남은 것은 표준 35종을 넘는 프로젝트의 단계 분류 확인, 그리고 아직 커밋하지 않은 통계·설정 이전 작업분 정리다. 병렬 세션이 정리되는 시점을 골라야 한다.


Comments

Content