This file contains repository-specific guidance. Also review organization-level guidance: - AGENTS.organization.md - AGENTS.style.coding.md
Package Overview
artcurator provides:
- Unified AI Interface - Provider-agnostic artwork analysis (artgemini/artopenai abstraction)
- Curator Context - Hot-cached artist/artwork data for AI agents
- Benchmark Data - Percentile metrics for artistic analysis
See README.md for user-facing overview.
Testing Strategy
httptest2 Fixture System
All tests use httptest2 fixtures - no live API calls, no environment-based skips.
Pattern:
# tests/testthat/test-artwork-ai.R
httptest2::with_mock_dir(simplify = FALSE, arthelpers::path_mocks("unified/1"), {
result <- art_about_ai(img, provider = "gemini", ml = "gemini-2.5-flash")
expect_type(result, "character")
})Key files: - tests/testthat/setup.R - httptest2 configuration (block_requests, redactors) - inst/httptest2/redact.R - Automatic redaction (API keys, base64 images) - tests/testthat/fixtures/_mocks/ - Recorded HTTP responses - tests/testthat/fixtures/_extra/ - Shared test assets (images, data)
Recording Fixtures
First time only:
# Set real API keys
Sys.setenv(ART_GEMINI_KEY = "your-key", ART_OPENAI_KEY = "sk-your-key")
# Record fixtures
httptest2::capture_requests({ devtools::test(filter = "artwork-ai") })
# Commit fixtures
git add tests/testthat/fixtures/_mocks/
git commit -m "Record httptest2 test fixtures"Subsequent runs: Tests use fixtures automatically (zero API calls).
Refreshing fixtures: Delete tests/testthat/fixtures/_mocks/unified/ and re-record.
Helper Functions
Use arthelpers (in Suggests) for test utilities: - arthelpers::path_mocks("subdir") - Mock fixture directory - arthelpers::path_extra("file") - Extra fixtures directory - arthelpers::dev_artist() - Test artist UUID - arthelpers::dev_artwork() - Test artwork UUID
Never use these in R/* files - tests/vignettes only.
Vignette Building
httptest2 + Quarto Freeze
Vignettes use combined httptest2 mocking and Quarto freeze caching for zero-cost CI builds.
Every vignette has:
start_vignette("vignette-name") # Top
# ... executable code ...
end_vignette() # BottomPlus YAML: eval: true, freeze: auto
Recording vignette fixtures (first time only):
export ART_GEMINI_KEY="your-key"
export ART_OPENAI_KEY="sk-your-key"
rm -rf vignettes/*/ _freeze/ # Clean slate
quarto render vignettes/ # Records fixtures + cache
git add vignettes/*/ _freeze/
git commit -m "Record vignette fixtures and freeze cache"CI builds: Uses _freeze/ cache (no API calls, no compute) = $0
Important files: - _quarto.yml - Project-level freeze config - inst/httptest2/redact.R - Redactor (auto-used) - vignettes/{name}/ - httptest2 fixtures (commit to git) - _freeze/ - Quarto cache (commit to git)
Architecture
Unified Interface Pattern
artcurator::art_about_ai(img, provider = "gemini")
↓
.dispatch_artwork_ai("art_about_ai", "gemini", img_path = img)
↓
do.call(artgemini::art_about_ai, list(img_path = img))
Provider resolution: parameter → env var → auto-detect → error
Dependencies
Both artgemini and artopenai are in Imports (not Suggests) to guarantee functionality:
-
Out-of-box experience: All
art_*_ai()functions work immediately - No provider confusion: Users don’t need to research which provider to install
- Complete feature access: Users can compare providers, use both in workflows
-
Simplified installation: Single
pak::pkg_install("artalytics/artcurator")gets both
Trade-off: Larger dependency footprint, but ensures unified interface works as advertised.
Both use conditional loading (requireNamespace()) internally for clean error handling.
Common Tasks
Add New Unified Function
- Wrapper in
R/artwork-ai.Rusing.dispatch_artwork_ai() - Roxygen docs with
@export - Test with httptest2 fixtures
- Update NEWS.md
