docs: require per-agent commit attribution; add Swift guidelines
AGENTS.md: replace the 'no AI attribution footers' rule with a requirement that every agent-authored/co-authored commit end with a Co-Authored-By trailer naming the specific agent/model. Add docs/SWIFT_GUIDELINES.md to the repo layout. docs/SWIFT_GUIDELINES.md: consolidate the team Swift standards (coding style, testing, patterns, security, tooling hooks) into a self-contained project doc, with project notes for Xcode MCP, MainActor/Sendable, and SwiftData gotchas. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
239
docs/SWIFT_GUIDELINES.md
Normal file
239
docs/SWIFT_GUIDELINES.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# Swift Guidelines
|
||||
|
||||
Swift coding, testing, patterns, and security standards for OpenAppLock.
|
||||
Agents working on this project must follow these. They are the project-local
|
||||
copy of the team's Swift standards, consolidated here so the repo is
|
||||
self-contained (no dependency on any individual contributor's global config).
|
||||
|
||||
Where a rule meets a project specific, a **Project note** calls it out.
|
||||
General/cross-language principles (immutability, small files, comprehensive
|
||||
error handling, input validation) still apply on top of the Swift specifics
|
||||
below.
|
||||
|
||||
---
|
||||
|
||||
## 1. Coding style
|
||||
|
||||
### Formatting
|
||||
|
||||
- **SwiftFormat** for auto-formatting, **SwiftLint** for style enforcement.
|
||||
- `swift-format` is bundled with Xcode 16+ as an alternative.
|
||||
|
||||
### Immutability
|
||||
|
||||
- Prefer `let` over `var` — define everything as `let` and only change to `var`
|
||||
if the compiler requires it.
|
||||
- Use `struct` with value semantics by default; use `class` only when identity
|
||||
or reference semantics are needed.
|
||||
- **Project note:** SwiftData `@Model` types are necessarily reference types
|
||||
(`BlockingRule`, `AppList`); everything in `Shared/` and `Logic/` that can
|
||||
be a value type (`RuleDraft`, `RuleSchedule`, `UsageLedger`, snapshots,
|
||||
enums like `RuleKind`/`Weekday`) is.
|
||||
|
||||
### Naming
|
||||
|
||||
Follow the [Apple API Design Guidelines](https://www.swift.org/documentation/api-design-guidelines/):
|
||||
|
||||
- Clarity at the point of use — omit needless words.
|
||||
- Name methods and properties for their roles, not their types.
|
||||
- Use `static let` for constants over global constants.
|
||||
|
||||
### Error handling
|
||||
|
||||
Use typed throws (Swift 6+) and pattern matching:
|
||||
|
||||
```swift
|
||||
func load(id: String) throws(LoadError) -> Item {
|
||||
guard let data = try? read(from: path) else {
|
||||
throw .fileNotFound(id)
|
||||
}
|
||||
return try decode(data)
|
||||
}
|
||||
```
|
||||
|
||||
### Concurrency
|
||||
|
||||
Enable Swift 6 strict concurrency checking. Prefer:
|
||||
|
||||
- `Sendable` value types for data crossing isolation boundaries.
|
||||
- Actors for shared mutable state.
|
||||
- Structured concurrency (`async let`, `TaskGroup`) over unstructured `Task {}`.
|
||||
|
||||
> **Project note:** the app target defaults to `@MainActor` isolation, and the
|
||||
> test suites are `@MainActor`. Data shared with the DeviceActivity / shield
|
||||
> extensions through the app group (`RuleSnapshot`, `UsageLedger`, the
|
||||
> `MonitoringPlan` naming) must remain `Sendable`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Testing
|
||||
|
||||
### Framework
|
||||
|
||||
Use **Swift Testing** (`import Testing`) for new tests — `@Test` and `#expect`:
|
||||
|
||||
```swift
|
||||
@Test("User creation validates email")
|
||||
func userCreationValidatesEmail() throws {
|
||||
#expect(throws: ValidationError.invalidEmail) {
|
||||
try User(email: "not-an-email")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Test isolation
|
||||
|
||||
Each test gets a fresh instance — set up in `init`, tear down in `deinit`. No
|
||||
shared mutable state between tests.
|
||||
|
||||
> **Project note:** SwiftData is the exception. Repeatedly creating
|
||||
> `ModelContainer`s for this schema traps intermittently, so unit tests share
|
||||
> **one** container per process and get a fresh context + data wipe per test via
|
||||
> `makeInMemoryContext()` (TestSupport.swift). See AGENTS.md → "Gotchas learned
|
||||
> the hard way."
|
||||
|
||||
### Parameterized tests
|
||||
|
||||
```swift
|
||||
@Test("Validates formats", arguments: ["json", "xml", "csv"])
|
||||
func validatesFormat(format: String) throws {
|
||||
let parser = try Parser(format: format)
|
||||
#expect(parser.isValid)
|
||||
}
|
||||
```
|
||||
|
||||
### Coverage
|
||||
|
||||
Target **80%+** coverage (unit + integration + critical-flow E2E).
|
||||
|
||||
```bash
|
||||
swift test --enable-code-coverage
|
||||
```
|
||||
|
||||
> **Project note:** this is an **Xcode project, not a SwiftPM package** — build
|
||||
> and run tests through the **Xcode MCP** tools (`BuildProject`, `RunAllTests`,
|
||||
> `RunSomeTests`), not `swift test` or raw `xcodebuild`. The scheme destination
|
||||
> must be an iOS **simulator** or runs hang. UI flows are XCUITest
|
||||
> (`OpenAppLockUITests`) driven by the launch-argument harness in AGENTS.md.
|
||||
|
||||
### Workflow
|
||||
|
||||
Red-green TDD: update `docs/RULES_FEATURE_SPEC.md` first for behavior changes,
|
||||
write the failing test, run it (a compile failure counts as red), implement,
|
||||
re-run focused tests, then the full suite. Run tests often and fail fast.
|
||||
|
||||
---
|
||||
|
||||
## 3. Patterns
|
||||
|
||||
### Protocol-oriented design
|
||||
|
||||
Define small, focused protocols. Use protocol extensions for shared defaults:
|
||||
|
||||
```swift
|
||||
protocol Repository: Sendable {
|
||||
associatedtype Item: Identifiable & Sendable
|
||||
func find(by id: Item.ID) async throws -> Item?
|
||||
func save(_ item: Item) async throws
|
||||
}
|
||||
```
|
||||
|
||||
> **Project note:** this is how `ScreenTimeAuthorization` is structured — a
|
||||
> protocol with a real FamilyControls implementation and a mock, so `Logic/`
|
||||
> stays pure and unit-testable.
|
||||
|
||||
### Value types
|
||||
|
||||
- Use structs for data transfer objects and models.
|
||||
- Use enums with associated values to model distinct states:
|
||||
|
||||
```swift
|
||||
enum LoadState<T: Sendable>: Sendable {
|
||||
case idle
|
||||
case loading
|
||||
case loaded(T)
|
||||
case failed(Error)
|
||||
}
|
||||
```
|
||||
|
||||
> **Project note:** `RuleStatus` (`disabled / dormant / active(until:) /
|
||||
> paused(until:) / upcoming(startsAt:)`) is exactly this pattern — status is
|
||||
> always *derived*, never stored.
|
||||
|
||||
### Actor pattern
|
||||
|
||||
Use actors for shared mutable state instead of locks or dispatch queues:
|
||||
|
||||
```swift
|
||||
actor Cache<Key: Hashable & Sendable, Value: Sendable> {
|
||||
private var storage: [Key: Value] = [:]
|
||||
|
||||
func get(_ key: Key) -> Value? { storage[key] }
|
||||
func set(_ key: Key, value: Value) { storage[key] = value }
|
||||
}
|
||||
```
|
||||
|
||||
### Dependency injection
|
||||
|
||||
Inject protocols with default parameters — production uses defaults, tests
|
||||
inject mocks:
|
||||
|
||||
```swift
|
||||
struct UserService {
|
||||
private let repository: any UserRepository
|
||||
|
||||
init(repository: any UserRepository = DefaultUserRepository()) {
|
||||
self.repository = repository
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Security
|
||||
|
||||
### Secret management
|
||||
|
||||
- Use **Keychain Services** for sensitive data (tokens, passwords, keys) — never
|
||||
`UserDefaults`.
|
||||
- Use environment variables or `.xcconfig` files for build-time secrets.
|
||||
- Never hardcode secrets in source — decompilation tools extract them trivially.
|
||||
|
||||
```swift
|
||||
let apiKey = ProcessInfo.processInfo.environment["API_KEY"]
|
||||
guard let apiKey, !apiKey.isEmpty else {
|
||||
fatalError("API_KEY not configured")
|
||||
}
|
||||
```
|
||||
|
||||
> **Project note:** OpenAppLock has no network backend or API keys today.
|
||||
> `UserDefaults` (and the app-group container) is used only for non-sensitive
|
||||
> rule mirroring / stray-shield cleanup — keep it that way; do not put secrets
|
||||
> there.
|
||||
|
||||
### Transport security
|
||||
|
||||
- App Transport Security (ATS) is enforced by default — do not disable it.
|
||||
- Use certificate pinning for critical endpoints.
|
||||
- Validate all server certificates.
|
||||
|
||||
### Input validation
|
||||
|
||||
- Sanitize all user input before display to prevent injection.
|
||||
- Use `URL(string:)` with validation rather than force-unwrapping.
|
||||
- Validate data from external sources (APIs, deep links, pasteboard) before
|
||||
processing.
|
||||
|
||||
---
|
||||
|
||||
## 5. Tooling hooks (optional, local)
|
||||
|
||||
Contributors may configure PostToolUse hooks in their own
|
||||
`~/.claude/settings.json` to run after editing `.swift` files:
|
||||
|
||||
- **SwiftFormat** — auto-format.
|
||||
- **SwiftLint** — lint checks.
|
||||
- **swift build / type-check** — catch errors early.
|
||||
|
||||
Flag `print()` statements — use `os.Logger` or structured logging instead for
|
||||
production code.
|
||||
Reference in New Issue
Block a user