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 서버). - 고정 기준:
main의92b7bc167c8d08ded25abc01cc54175a40e4e6c8(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:7777Atlassian 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 aslocal). - 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 API | gadak | |
|---|---|---|---|
| Simple filter, 100 issues | 706 ms | 17 ms | 42× |
| 이슈 1건 + 전체 이력 | 1,055 ms | 54 ms | 20× |
에픽별 미해결 이슈 (GROUP BY) | 3,924 ms · 7 API pages | 24 ms · one query | 162× |
| 이력 전량 조회 | ≈ 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/store는internal/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(단일/--batch)·attach·edit·comment·transition·assign.
프로젝트 상태 (STATE_OF_PLAY.md 기준)
- v0.14.0 wave: MCP 표면이
query중심으로 재구축(에러 echo), 세 pipe 계약 문서화(issues_full+RECIPES,gadak sqlstdout,views open --keys -),sql --no-header, brew cask 분리(gadak앱 /gadak-cliCLI), 첫 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}단일 패턴 + 내부 분기.
관련 자료
- raw source:
github-midagedev-gadak— raw 포인터 (commit-pinned, 파일별 SHA-256) - moc-dev-tools · moc-ai-agents · moc-mcp