This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Go CLI application that synchronizes anime/manga lists bidirectionally between AniList and MyAnimeList. Uses OAuth2 authentication and supports both YAML config and environment variables.
make install # Install all dev tools (golangci-lint, gofumpt, goimports, gci, govulncheck)
make build # Build binary: go build -o anilist-mal-sync .
make test # Run all tests: go test ./... -v
make generate # Generate mocks using mockgen (run before tests if interfaces change)
make fmt # Format with gofumpt
make lint # Run golangci-lint (new issues only)
make check # Run all checks: generate + format + imports + vet + lint + test
make clean # Remove artifacts and test cachego test -run TestFunctionName ./...
go test -run TestConfig ./... # Run all tests matching "TestConfig"The codebase uses a strategy pattern for matching entries between services:
Source/Targetinterfaces - abstract data from source/target servicesStrategyChain- chains multiple matching strategies (varies by direction and media type):- Forward Anime: Manual → ID → OfflineDB → HatoAPI → ARM → Title → APISearch
- Forward Manga: Manual → ID → HatoAPI → Title → Jikan → APISearch
- Reverse Anime: Manual → ID → OfflineDB → HatoAPI → ARM → Title → MALID → APISearch
- Reverse Manga: Manual → ID → HatoAPI → Title → Jikan → MALID → APISearch
Updater- generic orchestrator with 3-phase pipeline (resolve → deduplicate → process) that uses strategies to match and update entriesFavoritesSync- separate phase for favorites synchronization that runs after the main sync, using already-resolved AniList↔MAL ID mappings
| File | Purpose |
|---|---|
app.go |
App structure & sync orchestration |
cli.go |
CLI interface (urfave/cli/v3) with 6 commands: login, logout, status, sync, watch, unmapped |
config.go |
Config loading (env vars take precedence over YAML) |
oauth.go |
Token management & OAuth2 flow |
anilist.go |
AniList GraphQL client (via verniy library) with ToggleFavourite mutation |
myanimelist.go |
MAL REST API client (via go-myanimelist) |
anime.go / manga.go |
Domain models implementing Source/Target interfaces with IsFavourite field |
strategies.go |
Matching strategy implementations |
arm_api.go |
ARM API client for online ID mapping |
hato_api.go / hato_cache.go |
Hato API client for ID mapping with response caching |
jikan_api.go / jikan_cache.go |
Jikan API client for manga ID mapping with response caching and GetUserFavorites |
offline_database.go |
Offline database using anime-offline-database |
updater.go |
Generic 3-phase update orchestration (resolve, deduplicate, process) |
service.go |
MediaService interface and implementations |
favorites.go |
Favorites synchronization logic (MAL→AniList sync, AniList→MAL report only) |
mappings.go |
Manual AniList↔MAL mappings and ignore rules (YAML) |
unmapped.go |
Unmapped entries state persistence (JSON) |
cmd_sync.go / cmd_watch.go |
Sync and watch command implementations (watch supports both interval and cron schedule modes) |
cmd_login.go / cmd_logout.go / cmd_status.go |
Auth and status commands |
cmd_unmapped.go |
CLI command for managing unmapped entries |
report.go |
Sync report: warnings, unmapped items, duplicate conflicts, favorites mismatches |
statistics.go |
Sync statistics tracking and summary output with favorites results |
logger.go |
Leveled logger with color support, context-based logging |
logging.go |
HTTP round-tripper debug logging |
http_retry.go |
Exponential backoff retry logic |
docs/date-sync.md |
Documentation: date synchronization logic and behavior tables |
The watch command supports two mutually exclusive scheduling modes:
- Interval mode: fixed duration between syncs (
--interval/WATCH_INTERVAL/watch.interval, range 1h–168h) - Cron mode: sync at specific times via cron expression (
--schedule/WATCH_SCHEDULE/watch.schedule, standard 5-field syntax)
Priority: CLI flag > env var > config YAML. Setting both modes is a validation error.
Both modes support --once for an immediate first sync. Cron mode uses time.Local timezone.
- Load config (env vars or YAML)
- Load manual mappings and ignore rules from
mappings.yaml - Get OAuth tokens for both services
- Fetch lists from source and target
- Resolve: match entries using strategy chain (see Key Abstractions for per-direction chains)
- Deduplicate: detect N:1 conflicts (multiple sources → same target), resolve by strategy priority
- Process: update target service with changes
- Favorites sync (if enabled with
--favorites): synchronize favorites between services- MAL→AniList: add missing favorites on AniList (does not remove)
- AniList→MAL: report mismatches only (MAL API does not support favorites write)
- Save unmapped entries state for
unmappedcommand - Print statistics and sync report
- Default: AniList → MyAnimeList
- Reverse (
--reverse-direction): MyAnimeList → AniList
Start/end dates are preserved in both directions. See docs/date-sync.md for details.
Favorites sync is an optional feature enabled via --favorites CLI flag or FAVORITES_SYNC_ENABLED env var. It runs as a separate phase after the main sync.
- MAL → AniList: Adds favorites from MAL to AniList. Does not remove favorites that exist only on AniList (user may have intentionally favorited different items on each service).
- AniList → MAL: Reports mismatches only. MAL API v2 does not support favorites read/write, so MAL favorites are fetched via Jikan API but cannot be modified.
Note: Favorites sync requires Jikan API to be enabled (automatically enabled when --favorites is used).
The .golangci.yml enforces strict limits to prevent overly complex code:
- funlen: 100 lines max, 50 statements max
- gocyclo: 15 complexity max
- cyclop: 25 complexity max
- nestif: 4 depth max
- lll: 140 characters max line length
Test files are exempt from complexity checks.
The codebase uses a Logger struct (logger.go) with 4 levels: Error, Warn, Info, Debug.
Context-based free functions (require context.Context with logger):
LogDebug(ctx, format, args...)— verbose mode onlyLogWarn(ctx, format, args...)— always shownLogInfo(ctx, format, args...)— normal modeLogStage(ctx, format, args...)— section headersLogProgress(ctx, current, total, status, title)— progress barsLogInfoSuccess(ctx, format, args...),LogInfoUpdate(ctx, ...),LogInfoDryRun(ctx, ...)
Never use log.Printf directly or DPrintf (deprecated no-op in updater.go):
// ❌ Bad - raw log or deprecated DPrintf
log.Printf("[DEBUG] message")
DPrintf("[DEBUG] message")
// ✅ Good - use context-based logging
LogDebug(ctx, "Processing item %d", id)github.com/rl404/verniy- AniList GraphQL clientgithub.com/nstratos/go-myanimelist- MAL API client- Jikan API — custom HTTP client in
jikan_api.go(no external library) github.com/urfave/cli/v3- CLI framework- HTTP retry — custom exponential backoff in
http_retry.go(no external library) gopkg.in/yaml.v2- Config file parsing (config.go)gopkg.in/yaml.v3- Mappings file parsing with comments (mappings.go)go.uber.org/mock- Mock generation for testsgithub.com/robfig/cron/v3- Cron expression parsing for watch scheduleanime-offline-database- Offline ID mapping (downloaded from GitHub releases)
Test files follow the pattern *_test.go in the root directory. Main test areas:
- CLI structure and flags (
cli_test.go) - Config loading from env vars (
config_test.go) - OAuth flows (
oauth_test.go) - Domain logic: anime, manga, strategies, score normalization
- Mappings: load/save, manual mapping, ignore rules (
mappings_test.go) - Unmapped state: save/load, JSON round-trip (
unmapped_test.go) - Updater: deduplication, duplicate target detection (
updater_test.go)