From dda4cb95f95a4b8b6f75544be74e13bbc0ec9fa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elon=20Park=20=28=E1=84=8B=E1=85=A6=E1=86=AF=E1=84=85?= =?UTF-8?q?=E1=85=A9=E1=86=AB=29?= Date: Fri, 24 Jul 2026 15:01:21 +0900 Subject: [PATCH 1/6] chore: commit the swiftformat and editorconfig configs Commit the house-style SwiftFormat configuration (rule whitelist, 2-space indent, 120-column limit) that PR #30 was formatted with, so formatting is reproducible for contributors and coding agents. Three rules are deliberately left out of the whitelist (isEmpty, wrapIfStatementBodies, elseOnSameLine) to keep `swiftformat .` idempotent on the existing tree: `swiftformat --lint .` reports 0/185 files requiring formatting at this commit. The .editorconfig mirrors the same basics (UTF-8, LF, 2-space indent, final newline) for non-Swift files and editors without SwiftFormat. Claude-Session: https://claude.ai/code/session_017SJfRUv3HzmLXAAUCKNVah --- .editorconfig | 12 +++++ .swiftformat | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 .editorconfig create mode 100644 .swiftformat diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f15441a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/.swiftformat b/.swiftformat new file mode 100644 index 0000000..9074f49 --- /dev/null +++ b/.swiftformat @@ -0,0 +1,137 @@ +# SwiftFormat configuration for KarrotCodableKit. +# +# Based on the Karrot house style applied across this codebase in PR #30 +# (SwiftFormat 0.62.x). Uses an explicit rule whitelist: only the rules listed +# under "rules" below run; everything else is disabled. +# +# `swiftformat .` must stay idempotent on a clean tree. Verify with: +# swiftformat --lint . +# +# Deliberate deviations from the house baseline, so the existing tree stays +# untouched: isEmpty, wrapIfStatementBodies, and elseOnSameLine are not +# enabled (tests intentionally assert `count == 0` for better failure +# messages, and a few single-line if/else bodies are kept as written). + +# options +--min-version 0.62.0 +--swift-version 6.2 +--language-mode 5 +--self remove # redundantSelf +--import-grouping testable-bottom # sortedImports +--trailing-commas multi-element-lists # trailingCommas +--trim-whitespace always # trailingSpace +--indent 2 #indent +--ifdef no-indent #indent +--indent-strings true #indent +--wrap-arguments before-first # wrapArguments +--wrap-parameters before-first # wrapArguments +--wrap-collections before-first # wrapArguments +--wrap-conditions before-first # wrapArguments +--wrap-return-type never #wrapArguments +--wrap-effects never #wrapArguments +--closing-paren balanced # wrapArguments +--call-site-paren balanced # wrapArguments +--wrap-type-aliases before-first # wrapArguments +--func-attributes prev-line # wrapAttributes +--computed-var-attributes prev-line # wrapAttributes +--stored-var-attributes preserve # wrapAttributes +--complex-attributes prev-line # wrapAttributes +--type-attributes prev-line # wrapAttributes +--wrap-ternary before-operators # wrap +--extension-acl on-declarations # extensionAccessControl +--pattern-let inline # hoistPatternLet +--property-types infer-locals-only # redundantType, propertyTypes +--strip-unused-args closure-only # unusedArguments +--type-blank-lines preserve # blankLinesAtStartOfScope, blankLinesAtEndOfScope +--ranges preserve # spaceAroundOperators +--some-any disabled # opaqueGenericParameters +--single-line-for-each convert # preferForLoop +--short-optionals always # typeSugar +--semicolons never # semicolons +--doc-comments preserve # docComments +--max-width 120 # wrap +--modifier-order private,fileprivate,internal,package,public,open,private(set),fileprivate(set),internal(set),package(set),public(set),open(set),override,final,dynamic,optional,required,convenience,indirect,isolated,nonisolated,nonisolated(unsafe),lazy,weak,unowned,unowned(safe),unowned(unsafe),static,class,borrowing,consuming,mutating,nonmutating,prefix,infix,postfix,async # modifierOrder + +# rules +--rules anyObjectProtocol +--rules blankLinesBetweenScopes +--rules consecutiveSpaces +--rules consecutiveBlankLines +--rules duplicateImports +--rules extensionAccessControl +--rules environmentEntry +--rules hoistPatternLet +--rules indent +--rules redundantParens +--rules redundantReturn +--rules redundantSelf +--rules redundantType +--rules redundantPattern +--rules redundantGet +--rules redundantFileprivate +--rules redundantRawValues +--rules redundantEquatable +--rules sortImports +--rules sortDeclarations +--rules strongifiedSelf +--rules trailingCommas +--rules trailingSpace +--rules linebreakAtEndOfFile +--rules typeSugar +--rules wrap +--rules wrapMultilineStatementBraces +--rules wrapArguments +--rules wrapAttributes +--rules wrapLoopBodies +--rules braces +--rules redundantClosure +--rules redundantInit +--rules redundantVoidReturnType +--rules redundantOptionalBinding +--rules redundantInternal +--rules redundantVariable +--rules unusedArguments +--rules spaceInsideBrackets +--rules spaceInsideBraces +--rules spaceAroundBraces +--rules spaceInsideParens +--rules spaceAroundParens +--rules spaceAroundOperators +--rules enumNamespaces +--rules blockComments +--rules docComments +--rules docCommentsBeforeModifiers +--rules spaceAroundComments +--rules spaceInsideComments +--rules blankLinesAtStartOfScope +--rules blankLinesAtEndOfScope +--rules emptyBraces +--rules andOperator +--rules opaqueGenericParameters +--rules genericExtensions +--rules trailingClosures +--rules sortTypealiases +--rules preferForLoop +--rules conditionalAssignment +--rules wrapMultilineConditionalAssignment +--rules void +--rules blankLineAfterSwitchCase +--rules consistentSwitchCaseSpacing +--rules semicolons +--rules propertyTypes +--rules blankLinesBetweenChainedFunctions +--rules emptyExtensions +--rules preferCountWhere +--rules preferFirstWhere +--rules preferContains +--rules swiftTestingTestCaseNames +--rules modifiersOnSameLine +--rules noForceTryInTests +--rules noForceUnwrapInTests +--rules redundantThrows +--rules redundantAsync +--rules noGuardInTests +--rules redundantMemberwiseInit +--rules redundantTypedThrows +--rules redundantBreak +--rules preferFinalClasses From 1fded93ee9aba513daf8e4bd60eb99f49b2c381e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elon=20Park=20=28=E1=84=8B=E1=85=A6=E1=86=AF=E1=84=85?= =?UTF-8?q?=E1=85=A9=E1=86=AB=29?= Date: Fri, 24 Jul 2026 15:02:51 +0900 Subject: [PATCH 2/6] docs: add AGENTS.md as the canonical agent instructions Move the tool-neutral content of CLAUDE.md into a new AGENTS.md and extend it with the conventions observed across merged PRs: commit and branch naming, merge strategy, test reporting expectations, label-driven release notes, code style, and testing conventions. CLAUDE.md and GEMINI.md become thin pointers that import AGENTS.md via the `@` syntax supported by Claude Code and Gemini CLI, with a fallback note for tools that do not expand imports. Codex and other agents read AGENTS.md natively, so all agents share one source of truth. Claude-Session: https://claude.ai/code/session_017SJfRUv3HzmLXAAUCKNVah --- AGENTS.md | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 121 +++------------------------------------------------ GEMINI.md | 9 ++++ 3 files changed, 142 insertions(+), 116 deletions(-) create mode 100644 AGENTS.md create mode 100644 GEMINI.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..4e28d94 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,128 @@ +# AGENTS.md + +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](CONTRIBUTING.md). + +## Project Overview + +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`; targets `KarrotCodableKit` (runtime) and `KarrotCodableKitMacros` (SwiftSyntax macro plugin) +- `swift-tools-version: 5.9`, dependency `swift-syntax` `509.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 + +## Commands + +```bash +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) +swift package resolve|update|clean|reset # Package management +``` + +Done 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. + +## Architecture + +### Core Modules +- **KarrotCodableKit**: Main library target containing runtime functionality +- **KarrotCodableKitMacros**: Swift macro implementations using SwiftSyntax + +### Key Components +- **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` +- **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 `outcome` property 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. + +### Macro System +- Macros are implemented in the `KarrotCodableKitMacros` target +- Factory classes in `Supports/Factory/` generate syntax nodes +- `PropertyAnalyzer` and `SyntaxHelper` provide macro development utilities + +### PolymorphicEnumCodable Macro +- **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`) + +### UnnestedPolymorphic Macros +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: +1. Implement the `UnnestedPolymorphicMacroType` protocol +2. Define `protocolType`, `macroType`, and `macroName` properties +3. Use template methods from the protocol extension for common functionality +4. Register the macro in `KarrotCodableKitPlugin.swift` + +## Code Style + +- `.swiftformat` (repo root) is the source of truth: rule whitelist, 2-space indent, 120-column limit. Run `swiftformat .` before committing — it must produce no diff. +- Multiline string literals (JSON fixtures, expected macro expansions) indent their content and closing `"""` two spaces past the opening line; `--indent-strings true` preserves this — do not "fix" it. +- `#if DEBUG` blocks add no extra indentation (`--ifdef no-indent`). +- propertyTypes rule gotchas: + - Write `CodingUserInfoKey.resilientDecodingErrorReporter` as `: CodingUserInfoKey = .init(...)!` — the rule miscompiles the multiline `= CodingUserInfoKey(...)!` form into a tuple. + - Keep the `// swiftformat:disable propertyTypes` regions around `@DefaultCodable` test doubles — explicit type annotations break the wrapper's generic parameter inference. +- Code, comments, and documentation are written in English. +- Follow the Swift API Design Guidelines; prefer dedicated structs/enums over tuples in public API. + +## Testing + +Test targets: +- **Tests/KarrotCodableKitTests/**: Runtime functionality tests, organized by feature. Uses **Swift Testing** (`import Testing`, `struct` suites, `@Test`, `#expect`/`#require`). +- **Tests/KarrotCodableMacrosTests/**: Macro expansion tests. Uses **XCTest** with `assertMacroExpansion` (`SwiftSyntaxMacrosTestSupport` is 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` / `// then` comments. +- 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`). + +## Git & PR Conventions + +- Commit subjects: lowercase Conventional Commits — `feat:`, `fix:`, `docs:`, `test:`, `style:`, `refactor:`, `perf:`, `chore:`; optional scope, e.g. `fix(polymorphic):`. Imperative mood. +- Branch names: `/`, 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 test` passes 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/**`, or `Tests/**` change. + +## Gotchas + +- A type-inference error in the macros module can cascade into unrelated `'@const' value should be initialized with a compile-time value` errors on `@Test` functions. Fix the first real error before trusting the rest of the diagnostics. +- Release builds strip DEBUG-only API (`outcome` reporting, projected values), so `swift test -c release` runs fewer tests than debug. Always run both. +- Releases are SemVer git tags without a `v` prefix (e.g. `2.1.0`); pushing a tag drafts release notes from PR labels. Do not create tags or releases unless explicitly asked. diff --git a/CLAUDE.md b/CLAUDE.md index f593a86..7ca71d2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,120 +1,9 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +@AGENTS.md -## Project Overview +AGENTS.md is the canonical source of project instructions for coding agents. +If your tool does not expand `@` imports, read [AGENTS.md](AGENTS.md) directly. -KarrotCodableKit is a Swift package that extends Swift's Codable protocol with enhanced functionality: - -- **CustomCodable**: Macro-based custom encoding/decoding with configurable coding key styles -- **PolymorphicCodable**: Support for 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 - -## Architecture - -### Core Modules -- **KarrotCodableKit**: Main library target containing runtime functionality -- **KarrotCodableKitMacros**: Swift macro implementations using SwiftSyntax - -### Key Components -- **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` - - Optional handles only keyNotFound/valueWasNil as nil, Lossy recovers from all errors -- **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 array/dictionary 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 `outcome` property on all BetterCodable and PolymorphicCodable property wrappers - -### Macro System - -The project heavily uses Swift macros for code generation: -- Macros are implemented in `KarrotCodableKitMacros` target -- Factory classes in `Supports/Factory/` generate syntax nodes -- `PropertyAnalyzer` and `SyntaxHelper` provide macro development utilities - -### PolymorphicEnumCodable Macro Architecture - -Polymorphic Codable support for enum types: -- **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`) - -### UnnestedPolymorphic Macro Architecture -The UnnestedPolymorphic macros use a 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**: Top-level code generation -- **UnnestedPolymorphicStructGenerator**: Nested struct generation -- **UnnestedPolymorphicMethodGenerator**: Init/encode method generation - -Each macro type (`UnnestedPolymorphicCodableMacro`, `UnnestedPolymorphicDecodableMacro`) implements `UnnestedPolymorphicMacroType` with specific protocol and macro type configurations. - -## Common Development Commands - -### Building -```bash -swift build # Build all targets -swift build -c release # Release build -``` - -### Testing -```bash -swift test # Run all tests -swift test -c debug # Debug configuration tests -swift test -c release # Release configuration tests -``` - -### Running Specific Tests -```bash -swift test --filter TestClassName # Run specific test class -swift test --filter TestClassName.testMethodName # Run specific test method -swift test --filter UnnestedPolymorphic # Run tests matching pattern -swift test --filter "UnnestedPolymorphicCodableTests" # Run macro expansion tests -``` - -### Package Management -```bash -swift package resolve # Resolve dependencies -swift package update # Update dependencies -swift package clean # Clean build artifacts -swift package reset # Reset cache and build directory -``` - -## Testing Structure - -- **KarrotCodableKitTests/**: Runtime functionality tests organized by feature -- **KarrotCodableMacrosTests/**: Macro expansion and generation tests -- Uses SwiftSyntaxMacrosTestSupport for macro testing - -## Development Notes - -### Macro Development -- Macros use SwiftSyntax for AST manipulation -- Test macro expansions using `SwiftSyntaxMacrosTestSupport` -- Factory pattern used for generating complex syntax structures - -### Adding New UnnestedPolymorphic Macro Types -When adding new UnnestedPolymorphic macro variants: -1. Implement `UnnestedPolymorphicMacroType` protocol -2. Define `protocolType`, `macroType`, and `macroName` properties -3. Use template methods from protocol extension for common functionality -4. Register macro in `KarrotCodableKitPlugin.swift` - -### Polymorphic System -- Uses identifier-based type resolution during decoding -- Strategy pattern for different polymorphic decoding approaches -- Supports both protocol-based and enum-based polymorphic types - -### Property Wrappers -- BetterCodable provides specialized property wrappers for common Codable scenarios -- Includes date formatting strategies, default value handling, and lossy conversions \ No newline at end of file +Add Claude-specific instructions below this line; keep everything +project-general in AGENTS.md. diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..46f6c18 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,9 @@ +# GEMINI.md + +@AGENTS.md + +AGENTS.md is the canonical source of project instructions for coding agents. +If your tool does not expand `@` imports, read [AGENTS.md](AGENTS.md) directly. + +Add Gemini-specific instructions below this line; keep everything +project-general in AGENTS.md. From e0190f10681decacb393dc48e37212713cc3a0e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elon=20Park=20=28=E1=84=8B=E1=85=A6=E1=86=AF=E1=84=85?= =?UTF-8?q?=E1=85=A9=E1=86=AB=29?= Date: Fri, 24 Jul 2026 15:03:21 +0900 Subject: [PATCH 3/6] docs: add CONTRIBUTING.md Document the human contributor workflow derived from the conventions observed in merged PRs: prerequisites, build and test expectations, TDD workflow, code style, branch/commit/PR conventions, the CodeRabbit review flow, and how labels drive release notes. Operational details are cross-referenced to AGENTS.md to avoid duplication. Claude-Session: https://claude.ai/code/session_017SJfRUv3HzmLXAAUCKNVah --- CONTRIBUTING.md | 107 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..fd6a330 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,107 @@ +# Contributing to KarrotCodableKit + +Thanks for your interest in contributing! Issues and pull requests are welcome. + +This guide covers the human workflow. The operational facts (commands, +architecture, conventions) that both humans and AI coding agents rely on live +in [AGENTS.md](AGENTS.md) — please skim it before your first change. + +English is the default language for code, comments, commits, and docs. +Issues and PR descriptions in Korean are also welcome. + +## Prerequisites + +- A Swift 6.2+ toolchain is required to build the test suite (the tests use + raw-identifier test names). CI runs macOS 15 with Xcode 26.3. +- No additional tooling is required; [SwiftFormat](https://github.com/nicklockwood/SwiftFormat) + (0.62+) is recommended for formatting. + +Getting started: + +```bash +git clone https://github.com/daangn/KarrotCodableKit.git +cd KarrotCodableKit +swift build +open Package.swift # or open the folder in Xcode +``` + +## Building and Testing + +```bash +swift test # debug configuration +swift test -c release # release configuration +swift test --filter SomeTestClass +``` + +A change is done when **both** debug and release test runs pass with zero +failures. The debug run has more tests than release because DEBUG-only APIs +(the Resilient `outcome` reporting) have DEBUG-gated tests — that difference +is expected. + +CI runs on pull requests that touch `Package.swift`, `Package.resolved`, +`Sources/**`, or `Tests/**`: the test matrix (debug/release) plus a macro +compatibility check across swift-syntax versions. + +## Development Workflow + +This project follows TDD: + +1. Write a failing test that captures the bug or the new behavior. +2. Make it pass. +3. Refactor while keeping tests green. + +Bug fixes should start with a regression test written against the old +behavior. Test conventions (Swift Testing vs XCTest, naming, fixtures) are +described in [AGENTS.md](AGENTS.md#testing). + +## Code Style + +Formatting is defined by `.swiftformat` in the repo root — run `swiftformat .` +before committing; it must produce no diff on a clean tree. The basics: +2-space indent, 120-column limit. There is no lint step in CI; style is +checked in review. + +## Branches, Commits, and Pull Requests + +- Branch names: `/`, e.g. `fix/lossy-array-null-outcome`, + `feature/optional-polymorphic-value`, `docs/update-readme`. +- Commit subjects: lowercase [Conventional Commits](https://www.conventionalcommits.org/): + + | Type | Use for | Example | + |---|---|---| + | `feat` | New functionality | `feat: omit nil optional fields on encode` | + | `fix` | Bug fixes | `fix(polymorphic): align lossy array recovery with BetterCodable policy` | + | `perf` | Performance | `perf(polymorphic): drop the intermediate Result array` | + | `refactor` | No behavior change | `refactor: extract KeyedDecodingContainer extension` | + | `test` | Tests only | `test: cover RFC 3339 legacy offset decoding` | + | `docs` | Documentation | `docs: correct DefaultEmptyPolymorphicArrayValue docs` | + | `style` | Formatting only | `style: apply swiftformat across the codebase` | + | `chore` | Tooling, CI, meta | `chore: add issue templates` | + +- PRs are merged with a merge commit (not squashed), so structure your branch + as one logical commit per unit of change — each commit should build and pass + tests on its own. +- Fill in the PR template, and report exact test counts for both + configurations, e.g. "`swift test` passes in debug (303) and release (295), + 0 failures". +- Keep PRs focused; put unrelated improvements in a follow-up PR. + +## Review Process + +- [CodeRabbit](https://coderabbit.ai) reviews every PR automatically and + applies a category label (Bug / Feature / Improvement / Update / Docs / + Breaking Changes / CI). A maintainer then reviews and merges. +- Please reply to review comments in their thread (not as top-level PR + comments), and feel free to push back with reasoning — review is a + conversation. + +## Releases + +Releases are SemVer git tags without a `v` prefix (e.g. `2.1.0`), created by +the maintainers. Pushing a tag drafts GitHub release notes grouped by PR +label, so the label on your PR determines where it appears in the notes. + +## Reporting Issues + +Please use the issue templates. For bugs, a minimal reproducible code sample +plus the JSON payload involved makes fixes dramatically faster. From 1c4cc5de98bf76023283d6001b5cff2875b5629d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elon=20Park=20=28=E1=84=8B=E1=85=A6=E1=86=AF=E1=84=85?= =?UTF-8?q?=E1=85=A9=E1=86=AB=29?= Date: Fri, 24 Jul 2026 15:03:59 +0900 Subject: [PATCH 4/6] chore: add issue templates and a PR checklist Add form-based bug report and feature request templates. The templates auto-apply the Bug / Feature labels that release-drafter uses for release-note categories. The bug form asks for a minimal reproducible code sample and the JSON payload, which most decoding bug reports need. Extend the PR template with a checklist covering the observed review expectations: both test configurations passing with counts reported, test coverage, a clean swiftformat run, and docs updates for public API changes. Claude-Session: https://claude.ai/code/session_017SJfRUv3HzmLXAAUCKNVah --- .github/ISSUE_TEMPLATE/bug_report.yml | 61 ++++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 1 + .github/ISSUE_TEMPLATE/feature_request.yml | 32 ++++++++++++ .github/pull_request_template.md | 7 +++ 4 files changed, 101 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..02ae836 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,61 @@ +name: Bug Report +description: Report incorrect behavior in KarrotCodableKit +labels: ["Bug"] +body: + - type: input + id: version + attributes: + label: KarrotCodableKit version + placeholder: "2.1.0" + validations: + required: true + - type: input + id: toolchain + attributes: + label: Swift / Xcode version + placeholder: "Swift 6.2 / Xcode 26.3" + validations: + required: true + - type: input + id: platform + attributes: + label: Platform and OS version + placeholder: "iOS 18.5 simulator" + validations: + required: false + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Describe the actual behavior. Include the full error output (e.g. the `DecodingError`) if any. + validations: + required: true + - type: textarea + id: reproduction + attributes: + label: Minimal reproducible code + description: The smallest type + decoding/encoding code that reproduces the issue. + render: swift + validations: + required: true + - type: textarea + id: json + attributes: + label: JSON payload + description: The JSON involved, if the bug is about decoding or encoding. + render: json + validations: + required: false + - type: textarea + id: expected + attributes: + label: Expected behavior + validations: + required: true + - type: checkboxes + id: checks + attributes: + label: Checklist + options: + - label: I searched existing issues and found no duplicate. + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..0086358 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..acb31ee --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,32 @@ +name: Feature Request +description: Suggest new functionality for KarrotCodableKit +labels: ["Feature"] +body: + - type: textarea + id: motivation + attributes: + label: Problem / motivation + description: What problem would this solve? What are you trying to do that is currently hard or impossible? + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposed API or solution + description: Sketch the API you have in mind, if any. + render: swift + validations: + required: false + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + validations: + required: false + - type: checkboxes + id: contribution + attributes: + label: Contribution + options: + - label: I am willing to submit a PR for this. + required: false diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index deea3f4..72c8fbb 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -13,3 +13,10 @@ ## Review Notes - + +## Checklist + +- [ ] `swift test -c debug` and `swift test -c release` pass (test counts reported above) +- [ ] Changes are covered by tests (for bug fixes: a regression test written first) +- [ ] `swiftformat .` produces no diff +- [ ] Docs updated if the public API changed From ca83bf617e242629389d71800fd14b90f59f272c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elon=20Park=20=28=E1=84=8B=E1=85=A6=E1=86=AF=E1=84=85?= =?UTF-8?q?=E1=85=A9=E1=86=AB=29?= Date: Fri, 24 Jul 2026 15:50:04 +0900 Subject: [PATCH 5/6] chore: add Claude Code project hooks for swiftformat and swiftlint Auto-format Swift files with swiftformat after every Edit/Write, so agents and contributors using Claude Code get the house style applied without a separate step. The swiftlint hook is gated on a committed .swiftlint.yml: the repo intentionally ships no SwiftLint config, and running the default rules with --strict would block edits on pre-existing violations. It activates automatically if a config is added later. Both hooks no-op when the tool is not installed. Claude-Session: https://claude.ai/code/session_017SJfRUv3HzmLXAAUCKNVah --- .claude/settings.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .claude/settings.json diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..6896f57 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-settings.json", + "hooks": { + "PostToolUse": [ + { + "matcher": "Edit|MultiEdit|Write", + "hooks": [ + { + "type": "command", + "command": "jq -r '.tool_input.file_path // .tool_response.filePath // empty' | { read -r f; case \"$f\" in *.swift) command -v swiftformat >/dev/null && swiftformat \"$f\";; esac; }" + }, + { + "type": "command", + "command": "jq -r '.tool_input.file_path // .tool_response.filePath // empty' | { read -r f; case \"$f\" in *.swift) if [ -f \"${CLAUDE_PROJECT_DIR:-.}/.swiftlint.yml\" ]; then command -v swiftlint >/dev/null && swiftlint lint --strict --quiet \"$f\"; fi;; esac; }" + } + ] + } + ] + } +} From d58ae825a2970450089959a2f171397edacb04cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elon=20Park=20=28=E1=84=8B=E1=85=A6=E1=86=AF=E1=84=85?= =?UTF-8?q?=E1=85=A9=E1=86=AB=29?= Date: Fri, 24 Jul 2026 20:33:33 +0900 Subject: [PATCH 6/6] docs: expand AGENTS.md with lint command, hook note, and provenance Add the swiftformat --lint verification command, note that the committed Claude Code hook auto-formats edited Swift files, and add a Documentation & Provenance section pointing at the feature docs to update on API changes, the SPI-hosted DocC setup, and the upstream projects the AnyCodable / BetterCodable / Resilient modules are ported from. Claude-Session: https://claude.ai/code/session_017SJfRUv3HzmLXAAUCKNVah --- AGENTS.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 4e28d94..d949cb3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -30,6 +30,7 @@ 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 management ``` @@ -84,9 +85,15 @@ Adding a new UnnestedPolymorphic macro variant: 3. Use template methods from the protocol extension for common functionality 4. Register the macro in `KarrotCodableKitPlugin.swift` +### Documentation & Provenance +- 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. + ## Code Style - `.swiftformat` (repo root) is the source of truth: rule whitelist, 2-space indent, 120-column limit. Run `swiftformat .` 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 true` preserves this — do not "fix" it. - `#if DEBUG` blocks add no extra indentation (`--ifdef no-indent`). - propertyTypes rule gotchas: