Imported from xmbshwll/ariadne (
AGENTS.md). Install upstream withnpx skills add xmbshwll/ariadne. Copyright stays with the author.
Ariadne Agent Guide
Responsibility
This repository contains the Go package ariadne, a music metadata resolution
library. Agent instructions live in AGENTS.md; keep generated paths, lint
config, and documentation aligned with this file.
Folder Map
*.go: root-package implementation.internal/config/: project-local.ariadne/config.ymlnormalization for runtime credentials and later provider-specific settings.internal/auth/: client credentials and the shared token source - fetch once, cache until near expiry, share one in-flight refresh across callers - plusDiscoveredCredential, the same discipline for credentials discovered from service pages rather than fetched from a token endpoint (SoundCloud's web client id).internal/auth/appleauth/: the Apple Music Media API developer token, which is a signed JWT rather than a fetched bearer token.internal/normalize/: shared canonical text, ISRC, UPC, duration, release date, artist, and title normalization, plus the Metadata Query variants built from them, used by adapters and Target Search.internal/adapters/: the oneAdapterinterface every provider implements, its Capability Set,ErrUnsupported, and the deferred-hydration errors. Providers own only platform code: wire types, endpoints, parsing, transforms, and their Adapter methods. Shared machinery lives elsewhere.internal/adapters/base/:base.Unsupported, the embedded zeroAdaptera provider wraps so it writes only the methods it supports.internal/targetsearch/: the Target Search module - the provider-facing Plan, Layer, Metadata Query fan-out, per-item fetch, and deduplication, plus the Unavailable/timeout error classification. The only Target Search machinery outside the providers themselves.internal/adapters/adaptertest/: the contract harness every provider runs.internal/canonical/: the shared canonical-mapping helpers - FirstNonEmpty, SingleArtistList, DateOnly, the Candidate constructors, and ISO 8601 duration parsing. Providers must not re-declare these.internal/resolve/: Entity Resolution pipeline (Source Input recognition, Runtime Hydration, Target Search, ranking, Candidate Hydration) shared by the root package through type aliases.internal/wiring/: the Provider Catalog. Which Music Service can act as Source Adapter or Target Search adapter, under which Credential Token, in which order, plus the built-in adapter construction. The root package reads it to build the default resolver; the CLI reaches it directly (same import path prefix, sointernal/is visible) instead of re-exporting its queries.internal/model/: canonical entity, candidate, and service-name types shared by every layer, including Candidate SearchKey rules.internal/httpx/: shared HTTP plumbing - client construction, JSON and byte exchanges with status handling, and HTML page fetches.internal/htmlx/: the JSON block a service page assigns to a JavaScript global, which is what the API-less services actually read.internal/urlx/: URL path segments and the region segment between a service host and an entity id.internal/targetsearch/: album and song Target Search plans that assemble service-specific Query Policies and per-query Score Signal weights.internal/score/: deterministic scoring of candidate albums against source album metadata.internal/mocks/: generated Mockery adapter mocks.cmd/go.mod: separate modulegithub.com/xmbshwll/ariadne/cmdwithcmd/ariadne/CLI. Readdocs/service-resolution.mdandCONTEXT.mdbefore changing resolution behavior, service support, adapter interfaces, Target Search, Candidate Hydration, or resolution metadata. They are the contract for those decisions.
Commands
make buildbuilds the CLI intobin/ariadne.make testruns unit tests for this package.make test-coverageruns the full test suite with coverage output.make lintrunsgolangci-lintfor the root package andcmd.make verifyruns formatting, lint, and race tests (the pre-commit gate).make mocksregenerates Mockery adapter mocks.go build ./...builds this package.go test ./...runs Go unit tests.go test ./... -count=1reruns tests without cached results.go test ./... -coverprofile=coverage.outwrites coverage data.cd cmd && go run ./ariadne resolve <url> --target <service> --dry-run.cd cmd && go run ./ariadne config services.cd cmd && go test ./...tests the CLI module.cd cmd && go run ./validate-spotify-auth --helpruns the private Spotify verification command.cd cmd && go run ./validate-apple-music-official --helpruns the private Apple Music MusicKit verification command.cd cmd && go run ./validate-tidal-official --helpruns the private TIDAL official API verification command.make buildbuilds the CLI tobin/ariadne.go run ./cmd/validate-spotify-auth --helpwill fail becausecmdis a separate module.
Style
- Use idiomatic Go: short packages, explicit errors, small interfaces, table tests where useful.
- Use
internal/assertwhen an assertion helper keeps a test table readable. - Use table-driven
tests := []struct{...}loops witht.Runwhen the same check applies across several resolution or config cases. - Use explicit case structs for CLI parsing, config normalization, adapter JSON, and resolver matching/scoring; tables keep related fixtures aligned and readable.
- Use shared test fixtures for repeated service, album, track, candidate, config, and HTTP fixture data instead of repeating similar literals.
- Full fixture payloads - HTML pages, API JSON response bodies - live in
testdata/under the package that consumes them, loaded through a smallmustRead...Fixturehelper. One-line literals built from runtime values (a server URL, a generated id) are test logic, not fixtures, and stay inline. - CLI tests stub the resolver through the narrow
entityResolverbehavior interface and describe theResolutionorSongResolutionthey want rendered; they do not assemble an Entity Resolution pipeline. The resolver's own behavior is proven ininternal/resolveandariadne_test.go. - Use table-driven tests for Target Search and Candidate Hydration across ISRC/UPC/metadata layers, HTTP status codes, API errors, malformed responses, nil/empty payloads, weak candidates, and missing optional metadata such as versions, copyrights, labels, genres, audio profiles, external IDs, URLs, and artist fields.
- Every provider implements the one
internal/adapters.Adapterinterface and declares what it supports throughCapabilities(). Callers select by capability and by Service Identity; they never type-assert an adapter to a narrower interface, and an unimplemented method returnsadapters.ErrUnsupported. Providers embedinternal/adapters/base.Unsupportedand write only the methods their API really offers. - Each provider package ships an
adapter_contract_test.godriven byinternal/adapters/adaptertest: it pinsService(), pinsCapabilities()against a literal expectation, and proves every undeclared method answersadapters.ErrUnsupported. Editing what a provider supports means editing that literal on purpose. - Mockery generates one testify-compatible mock,
MockAdapter, frominternal/adapters.Adapterintointernal/mocksasxxx_mock.go. Runmake mocksafter Adapter interface changes; the one mock satisfies every Source Input and Target Search layer assertion. - The public package exposes no adapter seam:
ariadne.Newplus options is the only construction, andinternal/wiringchooses adapters from the Provider Catalog. Tests that need specific adapters use theexport_test.goseam. - The public package carries only the resolve surface - Config, New, Resolver,
result types, MatchStrength, and error sentinels. Provider Catalog queries
(Describe, EvaluateTarget, TargetServices, service aliases, enablement) are
the CLI's concern and live in
internal/wiring, whichcmdimports directly. - Every exported identifier in the public
package ariadnecarries a doc comment: that package is the library contract. Lint does not enforce this (revive:exportedandrevive:package-commentsare disabled in.golangci.yml), so it stays true by hand. - Under
internal/, document anything that carries domain meaning (services, Entity Shapes, Scoring, Target Search layers); provider wire DTOs that only mirror a provider JSON payload may stay uncommented. - Generated mock files use
_test.goormocks/naming and stay separate from production code. - HTTP clients must close response bodies and use
context.Contextfor network calls. - Keep the public package API small: exported types and interfaces should describe resolution concepts, not internal HTTP or OAuth plumbing.
- The public package root stays
package ariadne. Provider implementations live underinternal/adapters/<provider>/. - Internal
cmdvalidation utilities may return structured issues from parse and validation helpers so CLI commands can render text and JSON output consistently. - Internal
cmdadapters and validation artifact types may expose fields needed by CLI renderers; the publicariadnepackage still exposes only resolution-facing types such asResolution,SongResolution,Candidate, andMatch. - CLI JSON output uses one wrapper shape,
{"error": ...}or{"result": ...}, for JSON-only commands and command errors.
Testing Guidelines
- Test files use the external
<package>_testpackage so the suite exercises the exported interface. White-box tests over unexported wiring are the exception and stay in-package; prefer a narrowexport_test.goseam over widening the publicariadneAPI.cmdbinaries arepackage main, which Go cannot import from an external test package, so those tests stay in-package. - Use the standard Go test toolchain and
make test-coverage. - Root-package tests cover behavior and integration, not implementation details: validation, service normalization, duplicate/empty sets, adapter routing, Target Search selection, Candidate Hydration, weak matches, no matches, and error wrapping.
- Prefer
github.com/stretchr/testify/assertandgithub.com/stretchr/testify/requireover handwrittenif got != wantassertions when both assertions and JSON-equivalent fixtures can cover the behavior. - Table tests must include assertion messages with the case name, and fixtures
must be table-driven with explicit fields for
config.ymlcase, CLI case, Target Search case, scoring case, adapter case, and resolution case where relevant. - Keep tests meaningful: cover Target Search, Candidate Hydration, scoring, aliases, duplicate/empty sets, adapter search paths, and error paths.
- Tests must assert exact public error strings and error codes for failing source inputs, adapters, missing optional data, unsupported services, credential failures, HTTP errors, malformed responses, nil/empty payloads, weak candidates, and missing external IDs, copyrights, labels, genres, audio profiles, versions, and URLs.
- Never test only the happy path for adapters. Adapter tests must prove credential precedence, query fan-out, malformed responses, HTTP status failures, malformed provider payloads, missing optional data, and unsupported service errors.
- Never assert only that Target Search or Candidate Hydration returns no error.
Assert the returned candidates, scores, confidence, hydration state, and
preserved metadata across
Album,Song,Candidate,Match,Resolution, andSongResolution. - Use
httptestfor provider HTTP behavior, and keep provider adapter tests aligned with the public interface rather than internal HTTP structs.
Notes
go test ./...must pass for every change.- Root-package tests run with race detection in CI, so keep fixtures isolated per test.
