gwimong's blog Software Engineer

artifact-transformer 작업 일지 #13

파이프라인 양끝을 닫다 — 다이어그램 기반 초기화, Drive 인도 자동화, 그리고 LLM으로부터의 독립


이 글은 2026-06-11 하루 동안 artifact-transformer 파이프라인의 입구(아키텍처 다이어그램 → participant.json 초기화)와 출구(Google Drive 업로드 + 관리 시트 갱신)를 자동화하고, 정합성 검증을 Claude 스킬에서 독립 실행 코드로 분리하고, 시퀀스 파서의 왕복 드리프트를 잡은 작업 기록이다.

배경

artifact-transformer는 Jira 이슈와 PlantUML 다이어그램을 입력으로 받아 요구사항·유스케이스·기능·시퀀스 명세서와 데이터 정의서/명세서 등을 docx/xlsx로 자동 생성·역파싱하는 도구다 (#4 참조).

#12에서 연동 데이터 라인을 닫고 검증을 CI 게이트로 묶어 두 시스템(IAMS·FDSP)에 투입했다. 산출물 생성과 검증의 가운데 토막은 이제 제법 단단한데, 양끝이 비어 있었다. 시작점인 participant.json은 컴포넌트 다이어그램 하나만 보고 만들어졌고, 끝점인 “생성된 산출물을 팀에 인도하는 일”은 여전히 수작업이었다. 그리고 정합성 검사 같은 결정적(deterministic) 작업이 Claude 스킬 안에 들어 있어, 도구가 LLM 없이는 온전히 돌지 않았다.

오늘은 이 세 가지를 손댔다.

문제

손대기 전 상태에서 풀어야 했던 질문들.

  1. participant.json의 출처가 절름발이였다 — parse_cdg.py는 컴포넌트 다이어그램(CMPDG) 하나만 파싱했다. 컨텍스트(CTXDG)·컨테이너(CNTDG) 다이어그램에만 등장하는 외부 시스템·액터는 카탈로그에 들어오지 못했다.
  2. 결정적 작업이 LLM 안에 갇혀 있었다 — 추적성 검사(check-traceability)가 Claude 스킬로만 존재해, Claude 세션 밖에서는 검증을 돌릴 수 없었다.
  3. 인도가 수작업이었다 — 생성된 docx/xlsx를 Drive에 올리고, 관리 시트의 경로·버전·날짜를 손으로 고치고 있었다. 산출물 35종이면 35번의 클릭이다.
  4. 시퀀스 파서의 왕복이 더러웠다 — sdsp 문서를 재파싱할 때마다 relatedUc 드리프트 18건이 반복해서 떴고, 손으로 채운 메시지 remarks는 파싱 한 번에 날아갔다.

결정 1 — parse_cdg를 initializer로 승격: 다이어그램 3종을 병합해 participant.json을 만들다

parser/de/parse_cdg.py를 initializer/init_participant.py로 이관(git mv로 이력 보존)하고, diagrams/design/architecture 하위의 CTXDG·CNTDG·CMPDG 세 다이어그램을 전부 찾아 병합 파싱하도록 확장했다. level1/2/3 중첩 폴더도 재귀 탐색하고, 구 구조와 legacy 단일 파일 폴백도 유지했다.

병합에는 규칙이 필요했다.

  • 레벨 순서 파싱 — CTXDG → CNTDG → CMPDG 순으로 읽고, 컬렉션별 id 기준 first-wins 중복 제거. 상위 레벨(더 추상적인 다이어그램)의 정의가 우선한다.
  • 자기 시스템 제외 — CTXDG에는 설계 대상 시스템 자신이 System(fdsp)으로 등장한다. 이것을 systems[]에 넣으면 “자기 자신과 연동하는 시스템”이 돼 버린다. CNTDG의 System_Boundary alias와 대조해 자기 자신은 걸러냈다. System_Ext는 항상 유지.
  • 새 매크로 지원 — ContainerDb/ContainerQueue(→ systems), Enterprise_Boundary, together { } 같은 익명 블록의 중괄호 짝 맞춤까지.

이름도 의도적으로 바꿨다. 이건 산출물을 역파싱하는 parser가 아니라 프로젝트 데이터를 초기화하는 initializer다. 다이어그램은 산출물이 아니라 정보의 원천이고, participant.json은 그 결과물이다. 폴더 이름이 역할을 말하게 했다.

FDSP(level1/2/3 구조)에서 다이어그램 3건 → components 12 · actors 2 · systems 12, SDSP(단일 폴더 구조)에서 3건 → components 30 · layers 3 · systems 28로 검증했고, 두 경우 모두 자기 시스템 제외가 확인됐다. 전체 테스트 67건 통과.

카탈로그의 출처가 다이어그램 한 장이면 카탈로그가 아니라 발췌다. 레벨별 다이어그램을 전부 읽어야 시스템 경계 전체가 데이터로 들어온다.

이후 이 카탈로그를 기반으로 iam-docs·fdsp-docs의 시퀀스 다이어그램들을 일괄 개선하고, participant.json을 확장해 아키텍처·시퀀스 다이어그램에 역반영했다. design/architecture 하위를 level1/2/3으로 구조화하는 재배치도 세 저장소(iam·fdsp·sdsp)에 걸쳐 마쳤다.

결정 2 — 결정적 작업을 LLM에서 분리하다: check.py

“정합성을 체크하는 코드를 만들어 달라”로 시작한 작업이 도구의 방향을 명확히 하는 결정이 됐다. skills 하위에 Claude 스킬로만 존재하던 check-traceability를 프로젝트 내 독립 실행 코드로 분리하고, 슬래시 커맨드도 그 코드를 호출하도록 뒤집었다.

목표를 한 문장으로 정리하면 이렇다.

사용자의 개입이 필요한 부분만 LLM으로 채우고, 그 외에는 사용자가 명령어로 코드를 실행해 워크플로우를 수행하는 SW.

스키마 검증, 추적성 검사, 왕복 비교 같은 일은 결정적이다. 입력이 같으면 결과가 같아야 하고, LLM이 끼어들 이유가 없다 — 끼어들면 오히려 재현성만 잃는다. LLM의 자리는 유스케이스 서술을 채우거나 시퀀스 메시지를 다듬는, 사람의 판단을 대행하는 부분뿐이다.

  • check.py를 신설해 스키마 검증과 추적성 검사를 단일 진입점으로 묶었다.
  • 슬래시 커맨드(스킬)는 이제 check.py를 호출하는 얇은 래퍼다. 검사 로직의 정본은 코드 한 곳.
  • 다양한 생성 케이스(전체 생성, 단계별, 산출물별, 다이어그램 동기화 포함 여부 등)에 대한 명령어 체계를 정의하고 README에 반영했다.

스킬과 코드에 같은 로직이 두 벌 있으면 반드시 어긋난다. #12에서 샘플과 스키마의 드리프트를 게이트로 잡았듯, 오늘은 스킬과 코드의 드리프트를 구조적으로 없앴다 — 한쪽을 다른 쪽의 래퍼로 만들어서.

결정 3 — 인도를 자동화하다: upload.py와 관리 시트

생성·검증 다음 단계, 산출물을 팀에 인도하는 끝단을 자동화했다. upload.py 하나로 다음이 한 번에 돈다.

  1. 변경된 산출물(docx/xlsx)을 Google Drive의 단계 폴더(AN.분석/CO.구현/DE.설계/TE.테스트/TO.인도)로 분류 업로드 — 산출물 식별자의 단계 코드가 폴더를 결정한다.
  2. 업로드 시 docx → Google 문서, xlsx → Google 시트로 변환 — 팀이 Drive에서 바로 열람·코멘트할 수 있게.
  3. 관리 시트의 해당 프로젝트 탭에서 산출물 행을 찾아 링크·버전·날짜를 갱신 — 링크는 =HYPERLINK(URL, "[프로젝트] 코드. 제목") 수식으로 기존 표시 형식을 유지.

설정은 자격증명·시트 ID 같은 시스템 공통 값은 system.env, 대상 폴더 ID 같은 프로젝트별 값은 project.env로 갈랐다. 변경 감지는 원본 MD5를 Drive 파일의 appProperties에 심어 두고 대조한다 — 같으면 건너뛰고, --force로 강제 교체.

실전 투입까지 갔다. IAMS 산출물 39건 생성(검증 2단 통과) 후 35건을 Drive에 업로드하고 관리 시트 35행을 갱신했다. 이제 python upload.py 한 번이면 변경분만 올라가고 버전이 1.0 → 1.1로 올라간다.

동명 파일 처리는 한 번 뒤집었다. 처음엔 같은 파일 ID에 새 revision으로 갱신하는 방식이었는데, “기존 파일 제거 후 새로 업로드”로 바꿨다(영구 삭제 대신 휴지통 — 30일 내 복구 가능). 트레이드오프는 분명하다 — 파일 ID가 매번 바뀌므로 Drive 파일을 직접 공유한 링크는 다음 교체 때 끊긴다. 최신 문서로 가는 단일 진입점은 관리 시트의 링크라는 운영 규칙이 전제다. 진입점을 하나로 강제하는 효과와 직접 링크가 끊기는 비용을 맞바꾼 셈인데, 불편해지면 revision 방식으로 되돌릴 수 있게 코드는 남겨 뒀다.

산출물 파이프라인은 생성에서 끝나지 않는다. 팀이 보는 곳에 도달해야 끝난다. 그리고 그 도달 지점(관리 시트)이 단일 진입점이 되도록 설계하는 것까지가 인도 자동화다.

결정 4 — 시퀀스 파서의 왕복 드리프트를 잡다

저녁에 파서 동기화를 돌리다 sdsp 시퀀스 다이어그램에서 반복되는 드리프트의 원인을 추적했다. 두 가지였고, 둘 다 parser/de/parse_sqdg.py에서 잡았다.

  • -UC- → -USC- 정규화 — puml 타이틀에 구 식별자 토큰(SDSP-UC-CON-001)이 남아 있으면 파싱할 때마다 정본(SDSP-USC-CON-001)과 어긋난 relatedUc가 기록됐다. 파서가 확정 직후 정규화하도록 해 드리프트 18건 → 0건.
  • 메시지 remarks 보존 — puml에는 없는 remarks 필드를 사람이 sequence.json에 채워 두면, 재파싱이 그것을 날렸다. 병합 시 order+메시지 텍스트가 일치하면 기존 remarks를 보존하도록 했다(참여자 별칭 차이는 무시). 비어있지 않은 remarks 48건 중 46건 보존 — 소실 2건은 puml 쪽 텍스트가 실제로 편집된 메시지로, 옛 remark를 붙이지 않는 것이 의도된 안전 동작이다.

보존 조건을 “order만 일치”가 아니라 “order+텍스트 일치”로 잡은 게 핵심이다. 느슨하게 잡으면 편집된 메시지에 옛 주석이 따라붙는다 — 데이터를 지키려는 장치가 데이터를 오염시키는 역전이 일어난다. 보수적 매칭으로 잃는 2건은 사람이 다시 채우면 되지만, 잘못 붙은 주석은 발견조차 어렵다.

남은 sdsp 드리프트(participants 14·messages 14)는 전부 puml 별칭과 카탈로그 id의 불일치(airflow ↔ airflow_system)다. 이건 파서가 아니라 .puml 쪽을 카탈로그 id로 맞춰야 하는 별도 작업으로 분리했다.

의도치 않은 함정

  • 고정 오프셋 가정이 또 깨졌다 — 물리 데이터 명세서(PDS) 양식에서 속성 표를 메인 카드 표와 분리(사이에 빈 단락 삽입)하자, generator의 “필드 표는 heading+2 위치”라는 고정 가정이 깨져 생성이 실패했다. #12에서 IDS 5블록 표가 같은 이유로 당했던 것과 정확히 같은 결 — 위치 고정 탐색을 표 스캔 방식으로 바꾸고, 사이 단락들을 template_separators로 보존해 카드 생성 시 복제 삽입하도록 했다. 구분 단락 없는 구버전 템플릿과도 호환된다. 양식은 사람이 고치는 문서다. 오프셋을 박는 코드는 양식 수정 한 번에 깨진다.
  • BOM이 env의 첫 키를 먹었다 — env 파일을 UTF-8 BOM으로 저장하면 첫 키 이름 앞에 BOM 문자가 붙어 키가 조용히 사라졌다. 테스트 중 실제로 맞고 나서 common/env.py를 utf-8-sig로 읽도록 보강했다. Windows 편집기들이 BOM을 붙이는 건 막을 수 없으니 읽는 쪽이 받아 줘야 한다.
  • 관리 시트의 행 매칭이 전부 빗나갔다 — 첫 업로드 시도에서 시트 행 매칭이 0건이었다. 탭을 직접 덤프해 보니 코드 열이 AN11-1. 업무 정의서(코드+제목)였고, 탭에 따라 [IAMS] AN22-1. 요구사항 정의서처럼 대괄호 접두가 붙은 변형도 있었다. 행 키를 셀 전체 일치가 아니라 앞머리 산출물 코드 토큰만 추출해 매칭하도록 바꿔 해결했다. 사람이 관리해 온 시트의 표기는 절대 균일하지 않다 — 정규화는 코드 몫이다.

마무리

오늘은 파이프라인의 입구(다이어그램 → participant.json)와 출구(Drive 인도)를 자동화하고, 그 사이의 결정적 검증을 LLM에서 떼어낸 날이다. 관통하는 결은 셋이다.

  • 출처가 온전해야 카탈로그다 — CTXDG/CNTDG/CMPDG 3종 병합과 자기 시스템 제외로, participant.json이 시스템 경계 전체를 담는 초기화 산출물이 됐다.
  • 결정적인 일에 LLM을 쓰지 않는다 — check.py 분리로 검증 로직의 정본이 코드가 되고 스킬은 래퍼가 됐다. LLM의 자리는 사람의 판단을 대행하는 부분뿐이다.
  • 인도까지가 파이프라인이다 — upload.py가 단계 폴더 분류·포맷 변환·관리 시트 갱신을 한 번에 처리하고, 변경 감지로 재실행이 가볍다.

다음에 할 것:

  • sdsp puml 별칭 정리 — 남은 드리프트의 원인인 puml 별칭을 카탈로그 id로 일치시키는 일괄 정리.
  • upload.py 단계 커버리지 확대 — 이번에 올라간 것은 AN/DE/TE 세 단계다. CO.구현·TO.인도 단계 산출물이 생기면 같은 흐름에 태운다.
  • 관리 시트 왕복의 안전장치 — 시트 갱신이 openpyxl 왕복(다운로드→수정→재업로드)이라 다른 탭 서식 손상 위험이 있다. 갱신 전 백업 revision 자동 확보를 붙일지 검토.

Comments

Content