wget처럼 Jira를 내려받다 — 티켓이 AI의 컨텍스트가 되는 순간

코드는 "무엇을 만들었는가"를 담는다. 그러나 "왜 만들었는가"는 티켓에 있다. JiraGet은 그 '왜'를 AI의 손이 닿는 곳으로 내려받는다.
이 글은 블룸AI 스튜디오가 오픈한 CLI(Command-Line Interface, 명령줄 인터페이스) 도구 BlumnAI-Studio/jiraget을 소개한다. 대상은 Jira로 요구사항과 이슈를 관리하는 팀에서, 클로드 코드 같은 AI 도구에게 "이 티켓 구현해줘"라고 말하고 싶은 개발자와 테크리드 — 즉 티켓은 브라우저에, 코드는 에디터에 있고, 그 사이를 매번 복사-붙여넣기로 잇고 있는 사람들이다.
앞선 2편 — 그래프로 코드를 읽다 (CodeScan)가 AI에게 코드 컨텍스트를 쥐어 주는 이야기였다면, 이번 편은 그 자매편이다 — 일의 맥락, 티켓 컨텍스트를 쥐어 준다.
1. 왜 티켓 다운로더를 만들었나

1.1 코드는 '무엇'을, 티켓은 '왜'를 담는다
AI로 코드를 생성하는 시대에 코드 자체의 탐색은 어느 정도 풀렸다 — 로컬 저장소를 인덱싱해 별자리로 만드는 CodeScan 같은 장치가 그 일을 한다. 그런데 코드를 아무리 잘 읽어도 답이 나오지 않는 질문이 있다.
- 이 기능은 왜 이렇게 동작해야 하는가?
- 이 예외 처리는 어떤 논의 끝에 추가되었는가?
- 첨부된 화면 설계와 재현 스크린샷은 어디에 있는가?
전부 Jira 티켓 안에 있다. 요구사항 본문, 댓글로 오간 의사결정, 첨부 파일 — 코드의 '왜'에 해당하는 기록들이다. 사람은 브라우저와 에디터를 알트탭으로 넘나들며 이 기록을 참조하지만, AI 코딩 세션은 그 벽을 넘지 못한다. 그래서 우리는 티켓 내용을 손으로 복사해 프롬프트에 붙여 넣는다. 댓글이 스무 개쯤 되면 그 복사도 노동이 된다.
1.2 MCP로 붙이면 되지 않나
Jira를 MCP(Model Context Protocol) 서버로 연결하는 방법도 물론 있다. 그러나 2편에서 다룬 MCP 상주 비용 논지가 여기에도 그대로 적용된다 — 세션이 시작하자마자 도구 스키마가 컨텍스트 윈도우를 차지하고, 티켓 하나를 읽을 때마다 API(Application Programming Interface) 왕복이 발생한다.
티켓이라는 데이터의 성질을 보면 더 단순한 길이 보인다.
- 구현 작업 중의 티켓 참조는 읽기 전용이다 — 코드를 짜면서 티켓을 고칠 일은 없다
- 티켓은 자주 바뀌지 않는다 — 요구사항과 지난 논의는 이미 확정된 기록이다
- 필요한 것은 언제나 티켓 전체다 — 본문만이 아니라 댓글 히스토리와 첨부까지
읽기 전용이고, 잘 안 바뀌고, 통째로 필요하다 — 이것은 실시간 채널이 아니라 다운로드의 요건이다. 인터넷의 문서를 로컬로 가져오던 wget이 정확히 이 요건 위에 서 있었다. JiraGet의 정체성은 Rust 프로젝트 매니페스트(Cargo.toml)의 description 한 줄에 그대로 적혀 있다:
"wget for Atlassian Jira Cloud"
한 번 받아 로컬 마크다운으로 두면, 그다음부터는 그냥 파일이다. AI가 가장 잘 읽는 포맷이고, 컨텍스트 비용은 파일을 여는 순간에만 발생한다.
2. JiraGet 소개 — 티켓 하나가 폴더 하나가 된다

2.1 사용법은 wget처럼 한 줄
전체 명령 표면이 다음이 전부다:
jiraget [-P DIR] ISSUE-KEY
jiraget search --limit N --title-only JQL
jiraget search --limit N --with-content [-P DIR] JQL
jiraget --help
jiraget --version
-P(--directory-prefix)는 wget에서 유일하게 계승한 옵션이다 — 출력 디렉터리 지정. 나머지 wget 옵션은 의도적으로 계승하지 않았다. 티켓 키 하나를 주면 현재 디렉터리에 번들이 떨어지고, 성공 시 표준 출력에는 생성된 절대 경로 한 줄만 찍힌다:
$ jiraget ISS-001
/Users/home/ISS-001
$ ls /Users/home/ISS-001
ISS-001.md attachments
2.2 번들의 구조 — 티켓의 전 생애가 파일 하나에
내려받은 ISS-001.md 한 파일에 티켓의 전 생애가 담긴다:
- YAML(YAML Ain't Markup Language) frontmatter — 문서 머리의 메타데이터 블록. 담당자, 우선순위, 상태, 커스텀 필드까지 Jira가 돌려준 모든 필드가 담기고, 필드 키는 사람이 읽는 표시 이름으로 정리된다
- Description — Jira의 서식 있는 본문이 마크다운으로 변환되어 담긴다
- Comments — 모든 댓글이 작성 순서대로, 작성자·시각 메타데이터와 함께. 페이지네이션을 끝까지 따라가므로 댓글이 수백 개여도 전부 온다
- Attachments — 첨부 파일 목록과 로컬 경로. 실제 파일은
attachments/디렉터리에 함께 내려온다
본문 변환은 Jira가 렌더링한 HTML(HyperText Markup Language)을 Rust 크레이트(crate, 라이브러리 패키지) htmd로 마크다운화하는 방식이다. 렌더링 결과가 없거나 변환이 실패하면 원본 ADF(Atlassian Document Format, Jira의 구조화 본문 포맷)를 JSON(JavaScript Object Notation) 코드 블록으로 보존한다 — 어떤 경우에도 내용이 조용히 사라지지 않는다.
설계 디테일 하나가 이 도구의 사용처를 잘 보여준다. 모든 번들에는 내용이 * 한 줄인 .gitignore가 자동 포함된다. 작업 중인 저장소 안에 티켓을 받아도 커밋이 오염되지 않는다는 뜻이다. 작업 브랜치 옆에 티켓을 두고 AI와 함께 보는 흐름 — jiraget ISS-123 한 줄 뒤에 "이 티켓 구현해줘" — 이 자연스럽게 성립한다.
2.3 JQL 검색 — 목록은 JSON Lines로, 내용은 필요할 때만
티켓 키를 모를 때는 JQL(Jira Query Language)로 검색한다:
$ jiraget search --limit 20 --title-only "project = DEMO AND sprint in openSprints()"
{"key":"DEMO-41","summary":"로그인 세션 만료 처리"}
{"key":"DEMO-42","summary":"Demonstrate output"}
출력은 JSON Lines — 한 줄이 결과 하나인 스트림 포맷이라 jq 같은 도구, 셸 스크립트, 그리고 AI 에이전트가 줄 단위로 바로 파싱한다. --title-only는 목록만 보고 파일을 하나도 만들지 않으며, --with-content로 바꾸면 검색 결과 전체가 번들로 내려온다.
이 두 모드의 조합이 에이전트 워크플로우와 잘 맞는다 — 먼저 --title-only로 훑고, 필요한 티켓만 골라 내려받는다. 스킬(Skill)에 연결하면 트리거 시점에만 로드되는 티켓 컨텍스트 장치가 된다. 2편에서 말한 스킬 + CLI 조합의 두 번째 사례다.
3. 신뢰를 설계하다 — 읽기 전용이라는 약속

회사 Jira에 붙는 도구는 편의보다 먼저 신뢰를 증명해야 한다. JiraGet의 스펙은 이 부분을 가장 강하게 강제한다.
원격 동작은 읽기 4종이 전부다. HTTP(HyperText Transfer Protocol) 클라이언트가 발행할 수 있는 요청이 코드 레벨에서 다음 allowlist(허용 목록)로 제한된다:
| 메서드 | 경로 | 용도 |
|---|---|---|
GET | /rest/api/3/issue/{key} | 이슈 1건 조회 |
GET | /rest/api/3/issue/{key}/comment | 댓글 조회 |
GET | /rest/api/3/attachment/content/{id} | 첨부 다운로드 |
POST | /rest/api/3/search/jql | JQL 검색 (읽기 전용 검색 — 유일한 비 GET) |
생성·수정·삭제·전이는 옵션이 없는 게 아니라 도달 자체가 불가능하다. 그 위로 안전장치가 겹겹이 쌓인다:
- 리다이렉트 추종 금지 — 자격증명이 다른 origin으로 전달되는 경로를 원천 차단한다
- 프록시 무시 — 시스템 프록시와 프록시 환경변수를 읽지 않는다. 트래픽이 설정된 Jira origin 밖으로 새지 않는다
- 토큰 비노출 — API 토큰과 Authorization 헤더는 어떤 진단 메시지에도 찍히지 않는다
- 첨부 파일명 익명화 — 내려받은 첨부는 UUID(Universally Unique Identifier, 범용 고유 식별자) 이름으로 저장되고, 원본 파일명 스템은 파일시스템에도 마크다운에도 보존되지 않는다.
민감정보_고객명단_v2.xlsx같은 파일명이 로컬 디스크에 흔적을 남기는 일이 없다 - 원자적 게시 — 번들은 임시 디렉터리에서 완성된 뒤에만 원자적 rename으로 공개된다. 실패하면 반쪽짜리 번들 대신 아무것도 남지 않고, 기존 경로는 절대 덮어쓰지 않는다(no-clobber)
과속 방지 장치도 보수적이다 — HTTP 429(요청 과다) 응답은 서버가 알려준 대기 시간만큼 기다려 딱 한 번만 재시도하고, 두 번째 429는 즉시 실패한다. 연결 10초·유휴 60초 타임아웃은 설정으로도 바꿀 수 없다.
AI에게 물려줄 도구일수록 도구 자체가 보수적이어야 한다. 에이전트가 실수해도 도구가 할 수 있는 일이 '읽기'뿐이라면, 그 실수의 반경도 '읽기'까지다.
4. 스펙이 곧 하네스다 — Spec-Driven Development

이 저장소에서 코드보다 먼저 읽어야 할 파일은 spec/jiraget.md다. 인터넷 표준 문서의 규범 언어 규격인 BCP 14(Best Current Practice 14 — RFC 2119)의 MUST / MUST NOT 언어로 쓰인 이 스펙은 첫 장에서 이렇게 선언한다:
This document is exhaustive. A behavior, command, option, data source, or output format not described here is unsupported and MUST NOT be implemented without a revision to this specification.
"여기 적히지 않은 동작은 스펙 개정 없이 구현 금지." 인터넷 표준 문서의 규범 언어를 작은 CLI 도구 하나에 적용한 것이다. 그 결과 스펙 문서가 세 가지 역할을 동시에 한다:
- 구현 계약 — 명령 문법부터 에러 메시지 문구, 파일 시스템의 원자성까지 관측 가능한 모든 행동이 규범으로 정의된다
- 테스트 계약 — 스펙 8장(Acceptance requirements)이 통째로 검증 시나리오 목록이다. 전 시나리오가 로컬 목(mock) HTTP 서버 기반이라 라이브 Jira 자격증명 없이 돌아간다
- 울타리 — 스펙 9장(Explicit exclusions)은 "하지 않을 것"의 명시적 목록이다. 이슈 생성도, 댓글 작성도, 프록시 옵션도 금지 항목으로 적혀 있다
세 번째 역할이 AI 시대에 특히 중요하다. AI로 코드를 생성해 본 사람은 안다 — AI는 시키지 않은 기능을 친절하게 추론해서 붙이는 경향이 있다. "첨부 다운로드가 있으니 업로드도 있으면 좋겠지"라는 추론이 읽기 전용 도구에 쓰기 경로를 만든다. 명시적 배제 목록은 그 추론을 막는 울타리다.
여기서 이 시리즈의 관점 하나가 이어진다. 1편에서 하네스를 "AI의 출력을 검증 가능하게 묶는 틀"이라고 했다. 사람이 코드를 전부 읽을 수 없는 시대에 품질의 중심은 코드 리뷰에서 스펙 리뷰로 이동한다 — 사람이 스펙을 소유하고, AI가 구현을 채우고, 스펙에서 파생된 테스트가 그 구현을 검증한다. 그 구조 안에서는 스펙이 곧 그 프로젝트의 하네스다. JiraGet은 그 작업 방식의 실증 사례이기도 하다.
5. 배포 — Rust 단일 바이너리, 설정 파일 하나
블룸AI 개발팀의 환경은 윈도우, WSL 리눅스, 맥이 섞여 있다. JiraGet은 Rust로 작성된 단일 바이너리로 세 플랫폼에서 동일하게 동작한다. TLS(Transport Layer Security)도 시스템 라이브러리 대신 rustls를 내장해, 바이너리 하나면 끝이다. 2편 CodeScan의 단일 바이너리 논지와 같은 계보다.
설치는 한 줄:
cargo install --git https://github.com/BlumnAI-Studio/jiraget --locked
설정도 파일 하나다. 실행 위치 또는 그 상위 디렉터리에 .jiragetrc.toml을 둔다:
base_url = "https://example.atlassian.net"
email = "example@example.com"
api_token = "ATATT..."
홈 디렉터리에 두면 전역 설정이 되고, 프로젝트 디렉터리에 두면 프로젝트별 설정이 된다 — 가장 가까운 파일 하나만 읽는다. 환경변수 폴백도, 명령줄 자격증명도, 대화형 프롬프트도 없다. 이것은 결핍이 아니라 선택이다. 설정 방법이 하나뿐이면 온보딩 문서도 한 문단이면 되고, "내 토큰이 어디서 읽히고 있지?"라는 질문 자체가 사라진다.
6. 마치며 — 두 번째 감각기관

먼저 솔직하게 — 티켓이 몇 장뿐이라면 복사-붙여넣기가 더 빠르다. JiraGet이 빛나는 곳은 댓글 수십 개짜리 티켓의 논의 히스토리를 통째로 참조할 때, 스프린트 단위로 여러 티켓을 함께 내려받을 때, 그리고 사람이 아니라 에이전트가 티켓을 읽는 워크플로우를 만들 때다.
CodeScan이 코드를 읽는 감각기관이라면, JiraGet은 일의 맥락을 읽는 감각기관이다. 하나는 "무엇이 어디에 있는가"에, 다른 하나는 "왜 그렇게 되었는가"에 답한다. 두 장치 모두 하네스에 장착 가능한 작은 CLI이고, 두 장치 모두 같은 원칙 위에 서 있다 — AI의 두뇌는 바꿔 끼워도, 도구는 설명 가능하고 보수적이어야 한다.
이 작은 도구를 BlumnAI-Studio/jiraget에 공개한다. 티켓과 코드 사이의 벽이 답답했던 적이 있다면, 시작은 한 줄이다:
jiraget DEMO-42
참고
- JiraGet:
- BlumnAI-Studio/jiraget (GitHub) — 소스, README(한국어/영어)
- spec/jiraget.md — v1 완전 스펙 (이 글 4장의 원문)
- 기반 기술:
- GNU Wget — 이름과 철학의 원류
- Jira Cloud REST API v3 · 향상된 JQL 검색 API · Basic 인증
- ADF — Atlassian Document Format
- htmd 0.5 — HTML → 마크다운 변환 크레이트
- JSON Lines — 검색 출력 포맷
- BCP 14 / RFC 2119 — 스펙의 규범 언어
- 시리즈:
- 블룸AI 스튜디오:
- blumn.ai — 차세대 AI 에이전트 / 실시간 엣지 AI 인프라 R&D Studio
Verba volant, scripta manent. — 말은 날아가고, 기록은 남는다. 티켓 속을 떠돌던 논의가 로컬의 기록이 될 때, AI는 비로소 일의 '왜'를 읽는다.
하네스의 이야기는 계속됩니다. 감사합니다. 💙