Skip to content

Commit 01756fd

Browse files
committed
Add swift-format workflow and agent guidance
1 parent e884383 commit 01756fd

34 files changed

Lines changed: 1091 additions & 1163 deletions

.githooks/pre-commit

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/bin/sh
2+
set -eu
3+
4+
REPO_ROOT=$(git rev-parse --show-toplevel)
5+
cd "$REPO_ROOT"
6+
7+
if ! command -v swift-format >/dev/null 2>&1; then
8+
echo "swift-format is not installed or not available in PATH." >&2
9+
exit 1
10+
fi
11+
12+
CONFIG_FILE="$REPO_ROOT/.swift-format"
13+
14+
STAGED_SWIFT_FILES=$(git diff --cached --name-only --diff-filter=ACMR | grep '\.swift$' || true)
15+
16+
if [ -z "$STAGED_SWIFT_FILES" ]; then
17+
exit 0
18+
fi
19+
20+
echo "Running swift-format on staged Swift files..." >&2
21+
22+
printf '%s\n' "$STAGED_SWIFT_FILES" | while IFS= read -r file; do
23+
[ -f "$file" ] || continue
24+
swift-format format --in-place --configuration "$CONFIG_FILE" "$file"
25+
git add -- "$file"
26+
done

.swift-format

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"version": 1,
3+
"indentation": {
4+
"spaces": 4
5+
},
6+
"lineLength": 180,
7+
"maximumBlankLines": 1,
8+
"respectsExistingLineBreaks": true,
9+
"indentConditionalCompilationBlocks": true,
10+
"indentSwitchCaseLabels": false,
11+
"spacesBeforeEndOfLineComments": 2,
12+
"tabWidth": 8,
13+
"lineBreakBeforeEachArgument": false,
14+
"lineBreakBeforeEachGenericRequirement": false,
15+
"lineBreakBetweenDeclarationAttributes": false,
16+
"lineBreakAroundMultilineExpressionChainComponents": false,
17+
"prioritizeKeepingFunctionOutputTogether": true,
18+
"rules": {
19+
"GroupNumericLiterals": false
20+
}
21+
}

AGENTS.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# AGENTS.md
2+
3+
## Project Context
4+
5+
ObservableDefaults is a Swift 6 package that integrates SwiftUI Observation with two storage backends:
6+
7+
- `@ObservableDefaults` for `UserDefaults`
8+
- `@ObservableCloud` for `NSUbiquitousKeyValueStore`
9+
10+
The package has two core targets:
11+
12+
- `Sources/ObservableDefaults`: public macros, runtime wrappers, test-mode cloud mock
13+
- `Sources/ObservableDefaultsMacros`: SwiftSyntax macro implementations and diagnostics
14+
15+
Most changes are correctness-sensitive because public behavior is generated by macros and then executed through runtime wrappers. When changing behavior, inspect both the macro expansion logic and the runtime wrapper behavior before editing.
16+
17+
## Key Files
18+
19+
- `Package.swift`: package targets and SwiftSyntax dependency range
20+
- `Sources/ObservableDefaults/Macros.swift`: public macro declarations and user-facing parameter docs
21+
- `Sources/ObservableDefaults/UserDefaults/UserDefaultsWrapper.swift`: `UserDefaults` read/write resolution rules
22+
- `Sources/ObservableDefaults/NSUbiquitousKeyValueStore/NSUbiquitousKeyValueStoreWrapper.swift`: cloud read/write resolution rules and DEBUG test-mode isolation
23+
- `Sources/ObservableDefaultsMacros/Macros/ObservableDefaultsMacro.swift`: generated `UserDefaults` observation and external change handling
24+
- `Sources/ObservableDefaultsMacros/Macros/ObservableCloudMacro.swift`: generated cloud observation logic
25+
- `Sources/ObservableDefaultsMacros/Macros/DefaultsBackedMacro.swift`: generated `UserDefaults` accessor behavior and declaration-time default capture
26+
- `Sources/ObservableDefaultsMacros/Macros/CloudBackedMacro.swift`: generated cloud accessor behavior
27+
- `Tests/ObservableDefaultsTests`: authoritative behavior coverage
28+
- `README.md` and `README_zh.md`: public semantics and usage docs
29+
30+
## Development Rules
31+
32+
### Macro and Runtime Semantics
33+
34+
- Do not mix `@ObservableDefaults` and `@ObservableCloud` on the same class. Keep storage concerns split across separate types.
35+
- Both top-level macros are class-only. Do not introduce examples or tests that suggest struct support.
36+
- `@DefaultsBacked` and `@CloudBacked` do not support `willSet` / `didSet`. `@ObservableOnly` does.
37+
- In `observeFirst: true` mode, only explicitly backed properties are persistent. Observable-only properties must not appear in external storage notification handling.
38+
- Prefix values are trimmed and must not contain `"."`. Preserve this invariant.
39+
- Static properties must remain ignored by persistence/observation generation.
40+
41+
### Default and Fallback Behavior
42+
43+
- Declaration-time defaults are immutable model defaults. They are not the same as current runtime values.
44+
- `@ObservableDefaults` fallback order is:
45+
1. persisted value in the selected `UserDefaults` domain
46+
2. value from `UserDefaults.register(defaults:)`
47+
3. declaration-time model default captured by the macro
48+
- `@ObservableCloud` fallback order is:
49+
1. persisted cloud value
50+
2. declaration-time model default
51+
- When fixing key-removal or missing-key behavior, verify both non-optional and optional properties.
52+
- Never use the current cached property value as the fallback when recomputing external `UserDefaults` changes.
53+
54+
### Storage Resolution Rules
55+
56+
- RawRepresentable-based storage takes priority over Codable JSON storage when multiple conformances match.
57+
- For hybrid `RawRepresentable & PropertyListValue` types, maintain raw-value write behavior and compatibility fallback on reads.
58+
- If you change overload ordering or wrapper dispatch, run the ambiguity and compatibility suites before considering the change complete.
59+
60+
## Testing Expectations
61+
62+
- Run `swift test` before finishing any behavioral change.
63+
- If a change touches external notifications, fallback logic, or equality checks, at minimum run:
64+
- `swift test --filter ExternalChangeEqualityTests`
65+
- `swift test --filter ObservableDefaultsTests`
66+
- If a change touches cloud test-mode behavior, mock storage, or notification handling, also run:
67+
- `swift test --filter ObservableCloudTests`
68+
- `swift test --filter Issue26OverloadAmbiguityTests`
69+
- If a change touches prefixes, suite parsing, or macro string parameters, run:
70+
- `swift test --filter PrefixTests`
71+
- `swift test --filter WhitespaceTests`
72+
- `swift test --filter StringParameterTests`
73+
- If a change touches storage format or overload resolution, run:
74+
- `swift test --filter RawRepresentableCodableTests`
75+
- `swift test --filter Issue26OverloadAmbiguityTests`
76+
77+
### Test Isolation Rules
78+
79+
- `UserDefaults` tests should use isolated suite names. Prefer `UserDefaults.getTestInstance(suiteName:)`, and use a unique suite per test when cross-test interference is possible.
80+
- Cloud `.testMode` tests use per-test mock suite isolation through `NSUbiquitousKeyValueStoreWrapper.testSuiteName`. Preserve that mechanism.
81+
- If a suite relies on shared notifications or shared storage state, make it `.serialized`.
82+
- Do not “fix” flaky tests by weakening expectations if the real issue is shared storage or notification interference.
83+
84+
## Documentation Rules
85+
86+
- If you change public behavior, update `README.md` and `README_zh.md` in the same change.
87+
- If you change public macro parameters, update:
88+
- `Sources/ObservableDefaults/Macros.swift`
89+
- `README.md`
90+
- `README_zh.md`
91+
- If you change fallback semantics, `observeFirst`, App Group behavior, or development/test mode behavior, update the README examples and explanatory sections, not just inline comments.
92+
93+
## Review Priorities
94+
95+
When reviewing changes, prioritize:
96+
97+
1. behavioral regressions in generated code
98+
2. missing tests for external changes and fallback paths
99+
3. `MainActor` and `defaultIsolationIsMainActor` coverage
100+
4. `observeFirst` correctness
101+
5. storage format compatibility for RawRepresentable/Codable hybrids
102+
6. test isolation and notification scope
103+
104+
## Release Workflow
105+
106+
- Bug fixes should normally increment the patch version.
107+
- Current release style uses plain semantic version tags such as `1.8.3`, not `v1.8.3`.
108+
- Before tagging a release:
109+
- merge the intended commits into `main`
110+
- run full `swift test`
111+
- confirm `README.md` and `README_zh.md` reflect any public semantic changes
112+
- After tagging:
113+
- push `main`
114+
- push the tag
115+
- create a GitHub release with notes
116+
- Release notes should summarize:
117+
- the user-visible fix
118+
- any fallback or compatibility semantics that changed or were clarified
119+
- test coverage added
120+
- documentation updates
121+
- If a release includes an external PR, explicitly thank the original contributor in the release notes.
122+
123+
## Commands
124+
125+
Common commands used in this repository:
126+
127+
```bash
128+
swift test
129+
swift test --filter ExternalChangeEqualityTests
130+
swift test --filter ObservableDefaultsTests
131+
swift test --filter ObservableCloudTests
132+
swift test --filter Issue26OverloadAmbiguityTests
133+
git switch main
134+
git merge --ff-only <branch>
135+
git tag -a 1.8.3 -m "1.8.3"
136+
git push origin main
137+
git push origin 1.8.3
138+
gh release create 1.8.3 --generate-notes --title "1.8.3"
139+
```

0 commit comments

Comments
 (0)