gadak은 Atlassian Jira·Confluence를 로컬 SQLite 파일(~/.gadak/gadak.db) 하나로 미러링하는 Go 싱글 바이너리다. “어느 에픽이 막혀 있지?”라는 질문을 JQL로는 못 묻던 문제를 SQL 한 줄로 바꾼다 — 앱·웹 UI·CLI·SQL·MCP 다섯 표면이 같은 저장소를 공유하고, 에이전트는 REST API 대신 문서화된 스키마에 직접 SQL을 쓴다.

한눈에 보기

  • 프로젝트 성격: 로컬퍼스트·오프라인 이슈 트래커 미러 (macOS 앱 + gadak serve 웹 UI + CLI + SQL + MCP 서버).
  • 고정 기준: main92b7bc167c8d08ded25abc01cc54175a40e4e6c8 (2026-08-15 캡처, 최신 태그 v0.14.0).
  • 핵심 아이디어: “데이터가 API 뒤에만 있으면 검색은 네트워크 왕복이고 에이전트는 추론을 못 한다. 파일이면 둘 다 해결된다.” 미러는 언제든 버려도 되는 캐시 — Jira가 진실의 원천.
  • 두 표면, 하나의 저장소: 앱+웹 UI(하루 종일 트리아지, j/k/x/s/a/l/c 키보드) / CLI+SQL(에이전트·스크립트).
  • 라이선스: Apache-2.0 (GitHub API license: Apache-2.0 — README·LICENSE·NOTICE 일치).
  • 검증 상태: README·AGENTS.md·ARCHITECTURE·MCP·STATE_OF_PLAY·CONCEPT·BENCHMARKS·data-model 문서를 commit-pinned로 캡처했다. 실제 설치·실행·live-site 동기화·벤치마크 재측정은 하지 않았다. README 벤치마크 표(17ms/42× 등)는 문서 주장이다.

설치와 사용 (README 기준, 실행 안 함)

brew install midagedev/tap/gadak        # 앱 — 번들 CLI도 PATH에
# or, CLI만 (macOS + Linux):
brew install midagedev/tap/gadak-cli
gadak init && gadak sync    # Jira (and Confluence) -> ~/.gadak/gadak.db
gadak serve                # http://gadak.localhost:7777

Atlassian Cloud 전용. API 토큰 하나로 Jira와 Confluence를 같은 사이트에서 커버한다. 데모는 계정 없이 bundled snapshot으로 돌아간다: gadak demo (534 issues). 실시간 데모: https://midagedev.github.io/gadak/

에이전트 인터페이스 — SQL이 API다

“The database is the agent API.” 문서화된 SQLite 스키마가 있으면 에이전트는 툴 정의도 엔드포인트 목록도 필요 없다 — 질의를 쓰면 된다. 계층:

계층용도필요 조건
SQL관계형·집계·이력 질문gadak sql 또는 파일 자체
CLI한 이슈·한 검색·한 쓰기gadak 바이너리
REST셸이 없는 호스트gadak serve 실행 중
MCP셸 없는 클라이언트(Claude Desktop 등)gadak mcp — 5개 툴, 쓰기 없음
  • 핵심 스키마: items(source-neutral spine) → issues(Jira 투영, item_id 조인) → issues_full(에이전트 편의 뷰), comments·attachments·changelog·links, items_fts(FTS5), sync_state. labels·components·fix_versions는 JSON 배열 — json_each로 접근. 개인 이력은 별도 local.db (ATTACH as local).
  • MCP 서버(gadak mcp): stdio JSON-RPC 2.0, 프로토콜 2025-03-26, MCP SDK 의존성 없음(stdlib). 툴 5종: gadak_query(SELECT/WITH만, limit 기본 200·하드 1000·바이트 캡), gadak_search(FTS), gadak_issue, gadak_status, gadak_show(ui-focus 파일만 작성 — SQL이 답하면 창이 제시). 미러·Jira에 쓰지 않는다.
  • 설치: gadak mcp install claude|cursor|codex|json — 프로필과 바이너리 절대 경로를 등록에 구워 넣는다 (MCP 호스트는 셸 환경을 상속하지 않으므로).
  • 스킬: gadak skill install — 스키마+질의 패턴을 Claude Code 스킬로, 별도 프로세스 없음.
  • 주의 (hard-won knowledge): Jira는 계정 언어별로 status.name·issuetype.name을 번역하고 Accept-Language를 무시한다 → display name이 아닌 status_category(new|inprogress|done)·id로 필터링해야 한다. WHERE status = 'In Progress'는 한국어 계정에서 빈 결과를 낸다.

성능 (README 문서 주장 — live Cloud 2,853 issues 기준)

질문REST APIgadak
Simple filter, 100 issues706 ms17 ms42×
이슈 1건 + 전체 이력1,055 ms54 ms20×
에픽별 미해결 이슈 (GROUP BY)3,924 ms · 7 API pages24 ms · one query162×
이력 전량 조회≈ 20 min (changelog 크롤)one query

반대편 비용: 첫 전체 동기화는 수 분, watch tick마다 ~6.6 s, 미러는 Jira보다 한 sync 간격 뒤처진다. GROUP BY가 없는 JQL로는 못 묻던 “어느 에픽이 실제로 막혔나”가 이 파일에서는 SQL 한 줄이다.

아키텍처 (ARCHITECTURE.md 기준)

  • 모양: 하나의 바이너리, 하나의 DB, 하나의 origin. UI와 API가 같은 localhost origin에서 서빙되어 CORS 헤더가 아예 없다. cross-origin 쓰기·DNS-rebinding 읽기는 internal/server/browser_guard.go가 mux 이전에 거부.
  • 모듈 경계 (spine): internal/jira·internal/confluence(HTTP → neutral records, SQL 안 씀) → internal/store(SQLite 스키마·마이그레이션·FTS·파생 필드, jira 미의존) → internal/sync(full/incremental/reconcile) → internal/server(HTTP 계약). internal/storeinternal/jira를 import하지 않고, internal/jira는 SQL을 쓰지 않는다 — 이 경계 덕에 두 번째 소스(Confluence)가 재작성이 아닌 새 패키지가 됐다.
  • 의도된 3계층 캐시: ① SQLite(영속 미러, 에이전트 질의 대상) ② 브라우저 IndexedDB(마지막 IssueLite — 콜드 스타트 즉시 페인트) ③ Svelte stores의 인메모리 풀(필터·그룹·정렬·서브스트링 검색이 메모리 안에서만). 규칙: 키 입력은 네트워크 요청을 유발하지 않는다 — 서버 검색은 클라이언트가 안 가진 텍스트(댓글 본문 등)에만, Enter 시에만.
  • 쓰기: UI → gadak → 사용자 자격으로 Jira 호출 → 성공 시 해당 이슈를 Jira에서 다시 읽어 upsert + version++ → 갱신된 IssueLite 응답. 큐·낙관적 로컬 커밋·reconcile 로직 없음 — 실패하면 Jira의 에러가 그대로 돌아오고 UI가 낙관적 상태를 롤백.
  • 파생 필드 (동기화 시 changelog에서 계산, 질의마다 재계산하지 않음): reopen_count·reopen_reason·status_changed_at·resolved_at·cloned_from·정직한 epic_key(가장 가까운 epic 조상). 모든 규칙은 로컬라이즈된 이름이 아닌 statusCategory와 id에 키잉.
  • 의도적 결여: ORM 없음(문서화된 스키마에 손 SQL), 상시 데몬 불필요(gadak install-service는 선택), in-process 소스 플러그인 로더 없음(새 소스 = 새 패키지 + rebuild; enrichments는 out-of-process).
  • 플러그인 경계: 오픈소스 코어에 GitHub/CD/테스트관리 코드는 0줄. 외부 프로세스가 enrichments 테이블에 upsert + sync_state.version 증가 → 서버가 목록·상세에 병합(deploy_status, linked_prs 등). 작동 예제: examples/plugins/github-prs·csv-import. enrichments는 미러 필드를 덮을 수 없다.

범위 밖 (good fit / bad fit)

일상 검색 지연, 트래커·위키를 함께 보는 에이전트, 오프라인 읽기 — 적합. 보드·관리 기능·위키 작성·1분 stale — Jira에 남는다. 미러는 위키 미러링은 read-only다. 쓰기는 create(단일/--batchattach·edit·comment·transition·assign.

프로젝트 상태 (STATE_OF_PLAY.md 기준)

  • v0.14.0 wave: MCP 표면이 query 중심으로 재구축(에러 echo), 세 pipe 계약 문서화(issues_full+RECIPES, gadak sql stdout, views open --keys -), sql --no-header, brew cask 분리(gadak 앱 / gadak-cli CLI), 첫 live 벤치마크. 태그 이후 main에서: CLI 쓰기 create·attach·edit·batch 생성, MCP 툴 호출이 전체 미러 스캔 중단, hosted demo에서 service worker 제거(인앱 브라우저 호환).
  • 검증됨 (문서 주장): full sync 534 issues ~5s, incremental idempotent, derived fields 정답 대조(224/144/166 + 95 reopen), live write-through, Playwright E2E, JQL 부분집합, MCP stdio round-trip, goreleaser snapshot 산출물, go test ./... green.
  • 남은 것: live-site assignee 표시명(T6.8), zero-install hosted demo(v0.3, 배포됨), web push VAPID(v0.2, 지연 — in-tab Notification만), 10k 규모 bootstrap 페이로드 ≈61 ms/op (50 ms 목표 초과 — IndexedDB 캐시로 1회 부트 비용).
  • 만든 사람: 현재 1인. 미러는 버려도 되는 캐시, 0.x 계약은 data-model.md의 세 promise, 라이선스 Apache-2.0, 파일은 평범한 SQLite.
  • Known-issue 지식: 조직 API 키(ATCTT)는 제품 API에서 401 → 사용자 토큰(ATATT) + Basic auth 필요. Team-managed 프로젝트는 priority/components/fixVersions가 없음. reopen은 done 카테고리 → non-done 전이(이름 매칭 금지). changelog 이력은 백필 불가. Go ServeMux는 교차 패턴 등록 시 panic → {key}/{action} 단일 패턴 + 내부 분기.

관련 자료