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:
10
AGENTS.md
10
AGENTS.md
@@ -42,6 +42,8 @@ OpenAppLockUITests/ XCUITest flows (see harness below)
|
|||||||
docs/RULES_FEATURE_SPEC.md Feature spec derived from the Opal reference
|
docs/RULES_FEATURE_SPEC.md Feature spec derived from the Opal reference
|
||||||
recording; §6 maps it to the native presentation.
|
recording; §6 maps it to the native presentation.
|
||||||
Review/update this BEFORE behavior changes.
|
Review/update this BEFORE behavior changes.
|
||||||
|
docs/SWIFT_GUIDELINES.md Swift coding/testing/patterns/security standards
|
||||||
|
agents must follow on this project.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Domain facts worth knowing
|
## Domain facts worth knowing
|
||||||
@@ -81,8 +83,12 @@ docs/RULES_FEATURE_SPEC.md Feature spec derived from the Opal reference
|
|||||||
changes, write the failing test, run it (compile failure counts as red),
|
changes, write the failing test, run it (compile failure counts as red),
|
||||||
implement, re-run focused tests, then the full suite. Run tests often and
|
implement, re-run focused tests, then the full suite. Run tests often and
|
||||||
fail fast.
|
fail fast.
|
||||||
- Conventional commits (`feat:`, `fix:`, `refactor:` …), no AI attribution
|
- Conventional commits (`feat:`, `fix:`, `refactor:` …). **Agent attribution is
|
||||||
footers. Commit only when the user asks.
|
required**: every commit an agent authors or co-authors must end with a
|
||||||
|
`Co-Authored-By:` trailer naming the specific agent/model that did the work,
|
||||||
|
added manually in the commit message — e.g.
|
||||||
|
`Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>` (a
|
||||||
|
non-Claude agent uses its own name/email). Commit only when the user asks.
|
||||||
|
|
||||||
## UI-test harness
|
## UI-test harness
|
||||||
|
|
||||||
|
|||||||
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