Canonical instructions for AI coding agents working in this repository.
CLAUDE.md and GEMINI.md import this file — edit this file, not those.
Human-facing contribution docs live in CONTRIBUTING.md.
KarrotCodableKit is a public Swift package that extends Swift's Codable protocol:
- CustomCodable: Macro-based custom encoding/decoding with configurable coding key styles
- PolymorphicCodable: Polymorphic types with automatic type resolution based on identifiers
- AnyCodable: Type-erased Codable values for handling various types
- BetterCodable: Property wrappers for dates, data values, defaults, and lossy conversions
Package facts:
- Library product
KarrotCodableKit; targetsKarrotCodableKit(runtime) andKarrotCodableKitMacros(SwiftSyntax macro plugin) swift-tools-version: 5.9, dependencyswift-syntax509.0.0..<604.0.0- Platforms: macOS 11, iOS 13, tvOS 13, watchOS 6, macCatalyst 13
- Building the test suite requires a Swift 6.2+ toolchain (raw-identifier test names, SE-0451); CI uses macOS 15 + Xcode 26.3
swift build # Build all targets
swift test # Run all tests (debug)
swift test -c release # Release configuration tests
swift test --filter TestClassName # Specific test class
swift test --filter TestClassName.testMethodName # Specific test method
swift test --filter UnnestedPolymorphic # Tests matching pattern
swiftformat . # Format (config: .swiftformat in repo root)
swiftformat --lint . # Verify formatting (must report 0 files requiring formatting)
swift package resolve|update|clean|reset # Package managementDone criteria for any code change: swift test passes in both -c debug and -c release with zero failures. The debug count is higher than release (DEBUG-only APIs such as outcome/projected values have DEBUG-only tests) — that difference is expected. swiftformat . must produce no diff before committing.
- KarrotCodableKit: Main library target containing runtime functionality
- KarrotCodableKitMacros: Swift macro implementations using SwiftSyntax
- CustomCodable/: Macro system for automated Codable implementations with CodingKey generation
- PolymorphicCodable/: Runtime polymorphic type resolution system with strategy-based decoding
- Value Wrappers:
PolymorphicValue,OptionalPolymorphicValue,LossyOptionalPolymorphicValue - Array Wrappers:
PolymorphicArrayValue,OptionalPolymorphicArrayValue,DefaultEmptyPolymorphicArrayValue,PolymorphicLossyArrayValue,OptionalPolymorphicLossyArrayValue
- Value Wrappers:
- AnyCodable/: Type erasure wrappers (AnyCodable, AnyEncodable, AnyDecodable)
- BetterCodable/: Property wrappers for common Codable patterns
- DateValue/OptionalDateValue: Date formatting strategies (ISO8601, RFC3339, Timestamp, etc.)
- LosslessValue: Lossless type conversion (preserves original type, restores on encoding)
- LossyArray/LossyDictionary/LossyOptional: Lossy decoding (filters out failed elements)
- Defaults: Default value handling (DefaultCodable, DefaultEmptyArray, etc.)
- Resilient/: DEBUG mode decoding error tracking and reporting system
ResilientDecodingOutcome: Decoding result states (decodedSuccessfully, keyNotFound, valueWasNil, recoveredFrom)ResilientDecodingErrorReporter: Error collection and hierarchical storage by coding path- Accessible via
outcomeproperty on all BetterCodable and PolymorphicCodable property wrappers
Wrapper policy: Polymorphic wrappers follow the same recovery policy as their BetterCodable counterparts. Optional* variants treat only keyNotFound/valueWasNil as nil; Lossy* variants recover from all decoding errors. Optional* variants differ from their base wrapper only in optionality.
- Macros are implemented in the
KarrotCodableKitMacrostarget - Factory classes in
Supports/Factory/generate syntax nodes PropertyAnalyzerandSyntaxHelperprovide macro development utilities
- PolymorphicEnumCodableMacro/Decodable/Encodable: Auto-generates Codable conformance for enums
- PolymorphicEnumCodableFactory: Generates CodingKey and init/encode methods
- Each case must have exactly one associated value (conforming to
PolymorphicIdentifiable)
Template Method pattern with shared components:
- BaseUnnestedPolymorphicMacro: Protocol extension providing common functionality
- UnnestedPolymorphicValidation: Centralized validation logic with dynamic error messages
- PolymorphicMacroArgumentValidator: Argument extraction and validation
- UnnestedPolymorphicCodeGenerator / StructGenerator / MethodGenerator: Code generation layers
Each macro type (UnnestedPolymorphicCodableMacro, UnnestedPolymorphicDecodableMacro) implements UnnestedPolymorphicMacroType with specific protocol and macro type configurations.
Adding a new UnnestedPolymorphic macro variant:
- Implement the
UnnestedPolymorphicMacroTypeprotocol - Define
protocolType,macroType, andmacroNameproperties - Use template methods from the protocol extension for common functionality
- Register the macro in
KarrotCodableKitPlugin.swift
- Feature docs:
Docs/AnyCodable/README.md,Docs/BetterCodable/README.md; the README "Key Features" section holds macro-expansion examples. Update these when the public API changes. - API reference (DocC) is built and hosted by Swift Package Index via
.spi.yml— there is no local DocC catalog. - AnyCodable, BetterCodable, and Resilient are ports of Flight-School/AnyCodable, marksands/BetterCodable, and airbnb/ResilientDecoding. Keep behavior parity with BetterCodable when touching wrapper policies, and add attribution under
ThirdPartyLicenses/when vendoring upstream code.
.swiftformat(repo root) is the source of truth: rule whitelist, 2-space indent, 120-column limit. Runswiftformat .before committing — it must produce no diff.- Claude Code auto-formats edited Swift files via the PostToolUse hook in
.claude/settings.json— an unexpected post-edit diff is usually just the formatter. - Multiline string literals (JSON fixtures, expected macro expansions) indent their content and closing
"""two spaces past the opening line;--indent-strings truepreserves this — do not "fix" it. #if DEBUGblocks add no extra indentation (--ifdef no-indent).- propertyTypes rule gotchas:
- Write
CodingUserInfoKey.resilientDecodingErrorReporteras: CodingUserInfoKey = .init(...)!— the rule miscompiles the multiline= CodingUserInfoKey(...)!form into a tuple. - Keep the
// swiftformat:disable propertyTypesregions around@DefaultCodabletest doubles — explicit type annotations break the wrapper's generic parameter inference.
- Write
- Code, comments, and documentation are written in English.
- Follow the Swift API Design Guidelines; prefer dedicated structs/enums over tuples in public API.
Test targets:
- Tests/KarrotCodableKitTests/: Runtime functionality tests, organized by feature. Uses Swift Testing (
import Testing,structsuites,@Test,#expect/#require). - Tests/KarrotCodableMacrosTests/: Macro expansion tests. Uses XCTest with
assertMacroExpansion(SwiftSyntaxMacrosTestSupportis XCTest-based — do not migrate these to Swift Testing).
Conventions (see existing tests for reference):
- TDD: write a failing test first (red), then make it pass. Bug fixes start with a regression test reproducing the bug against the old behavior.
- Test method names are backtick raw identifiers in natural language:
func `decodes valid JSON`(). - Structure test bodies with
// given/// when/// thencomments. - Use realistic, real-world JSON payloads as fixtures; name test doubles
*Dummy/TestDouble*. - Cover edge cases: missing key, null value, type mismatch, empty collections, and encode→decode round-trips.
- Assert concrete error cases (e.g. specific
DecodingError), not just "throws". - DEBUG-only features (Resilient
outcome, projected values) get#if DEBUG-gated test files (*ResilientTests.swift).
- Commit subjects: lowercase Conventional Commits —
feat:,fix:,docs:,test:,style:,refactor:,perf:,chore:; optional scope, e.g.fix(polymorphic):. Imperative mood. - Branch names:
<type>/<slug>, e.g.fix/polymorphic-lossy-array-null-outcome,docs/update-readme. - PRs are landed with merge commits (not squash). Structure work as one logical commit per unit of change — each commit should build and pass tests.
- Fill in the PR template. Report exact test counts for both configurations in Testing Methods, e.g. "
swift testpasses in debug (303) and release (295), 0 failures". - Keep PRs focused: defer unrelated changes (renames, drive-by cleanups) to follow-up PRs.
- CodeRabbit reviews every PR automatically and applies one of the labels Bug / Feature / Improvement / Update / Docs / Breaking Changes / CI. These labels determine the release-note category (release-drafter) — verify the label matches the change.
- Reply to review comments in their thread, not as top-level PR comments.
- CI runs only when
Package.swift,Package.resolved,Sources/**, orTests/**change.
- A type-inference error in the macros module can cascade into unrelated
'@const' value should be initialized with a compile-time valueerrors on@Testfunctions. Fix the first real error before trusting the rest of the diagnostics. - Release builds strip DEBUG-only API (
outcomereporting, projected values), soswift test -c releaseruns fewer tests than debug. Always run both. - Releases are SemVer git tags without a
vprefix (e.g.2.1.0); pushing a tag drafts release notes from PR labels. Do not create tags or releases unless explicitly asked.