|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Commands |
| 6 | + |
| 7 | +```bash |
| 8 | +make build # build binary (injects version/commit/date via ldflags) |
| 9 | +make install # install to $GOPATH/bin |
| 10 | +make test # go test ./... |
| 11 | +make lint # golangci-lint run ./... |
| 12 | +make clean # remove binary |
| 13 | + |
| 14 | +go build -o dartcli . # quick dev build (no version injection) |
| 15 | +go test ./internal/render/... # run tests for a specific package |
| 16 | +``` |
| 17 | + |
| 18 | +The binary requires a DART API key at runtime. Set `DART_API_KEY` env var or use `~/.dartcli/config.yaml`. |
| 19 | + |
| 20 | +## Architecture |
| 21 | + |
| 22 | +``` |
| 23 | +main.go → cmd.Execute() |
| 24 | +cmd/ CLI layer (cobra commands, global state) |
| 25 | +internal/ Core logic (no direct CLI dependencies) |
| 26 | + api/ DART OpenAPI HTTP client |
| 27 | + cache/ Corp code cache (~12k companies, persisted at ~/.dartcli/cache/corpcode.json) |
| 28 | + config/ Viper-based config (~/.dartcli/config.yaml + env vars) |
| 29 | + render/ API response → markdown, DART XML → markdown |
| 30 | + httpclient/ HTTP client with relaxed TLS (DART servers use legacy TLS 1.0+) |
| 31 | +pkg/dartcli/ Version metadata (injected by ldflags at build time) |
| 32 | +``` |
| 33 | + |
| 34 | +### cmd/root.go — shared state |
| 35 | + |
| 36 | +`root.go` owns three globals used across all commands: |
| 37 | +- `cfg` — loaded once via `cobra.OnInitialize` |
| 38 | +- `renderer` — glamour-based terminal renderer |
| 39 | +- `corpStore` — lazy-loaded on first corp lookup (`loadCorpStore()`) |
| 40 | + |
| 41 | +`resolveCorpCode(query)` searches the corp store (exact stock code → exact corp code → exact name → substring), and if multiple matches are found presents an interactive `huh.Select` prompt. |
| 42 | + |
| 43 | +### internal/api — DART OpenAPI client |
| 44 | + |
| 45 | +All endpoints are under `https://opendart.fss.or.kr`. Status `"000"` = success; `"013"` = no data (treated as empty result, not an error). The client injects the API key as a query parameter on every request. |
| 46 | + |
| 47 | +Financial period codes: `11011` annual, `11013` Q1, `11012` half-year, `11014` Q3. |
| 48 | + |
| 49 | +### internal/cache — corp code store |
| 50 | + |
| 51 | +Downloads `GET /api/corpCode.xml` (returns a ZIP containing CORPCODE.xml), parses the XML, and saves as JSON. Auto-refreshes when older than 7 days; falls back to stale data with a warning if the refresh fails. The in-memory `Store` has three indices: by corp code, by lowercase name, by stock code. |
| 52 | + |
| 53 | +### internal/render/document.go — DART XML → markdown |
| 54 | + |
| 55 | +This is the most complex file. DART discloses documents as ZIP archives containing proprietary `dart4` XML. The parser uses a streaming `xml.Decoder` with an element-name stack to produce markdown: |
| 56 | + |
| 57 | +- **`sanitizeXML()`** must run before decoding — DART XML routinely embeds Korean text in bare angle brackets (e.g. `<이사ㆍ감사 보수현황>`, `< TV 시장점유율 >`) that are visually used as emphasis markers but cause Go's xml decoder to fatal-error. The sanitizer replaces any `<` not followed by a valid XML name start character (`[a-zA-Z_:/?!>]`) with `<`. |
| 58 | +- **Do not set `dec.AutoClose = xml.HTMLAutoClose`** — DART XML uses explicit `<IMG>...</IMG>` pairs; auto-closing IMG causes a "unexpected end element `</IMG>`" fatal error. |
| 59 | +- `LIBRARY` is a transparent container (like `SECTION-*`), not metadata to skip. It wraps entire document sections in complex reports. |
| 60 | +- `SECTION-N` depth determines heading level: `SECTION-1` → `##`, `SECTION-2` → `###`, etc. The heading level is derived from the stack at the time a `<TITLE>` element is encountered. |
| 61 | +- Single-cell single-row tables are rendered as paragraphs (avoids glamour bullet rendering). |
| 62 | +- `DocumentFromZIP` reads up to 3 largest XML/HTML files from the ZIP, sorted by: rcptNo-matching filename first, then by size descending. |
| 63 | + |
| 64 | +### Rendering pipeline |
| 65 | + |
| 66 | +`renderer.Print(md)` uses glamour with TTY detection (`go-isatty`) and auto terminal width (capped at 120). `renderer.PrintWide(md)` sets `WithWordWrap(0)` — used for document view to prevent wide financial table truncation. |
| 67 | + |
| 68 | +Financial amounts are formatted in 억원 (÷100,000,000) when ≥ 1억, otherwise 만원. |
| 69 | + |
| 70 | +## Key DART API quirks |
| 71 | + |
| 72 | +- The API key goes in the query string as `crtfc_key`. |
| 73 | +- The corp code (`corp_code`) is an 8-digit zero-padded string, distinct from the 6-digit stock ticker (`stock_code`). |
| 74 | +- `GET /api/document.xml?rcpNo=<접수번호>` returns a ZIP (despite the `.xml` endpoint name). |
| 75 | +- ZIPs for 사업보고서 can contain multiple XML files (e.g. main + two supplementary audit files). All up to 3 files are rendered in sequence. |
0 commit comments