From d92a8588bb95ad5761faa26c79100396bf267528 Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Sat, 13 Jun 2026 15:14:24 -0400 Subject: [PATCH] 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) --- AGENTS.md | 10 +- docs/SWIFT_GUIDELINES.md | 239 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 247 insertions(+), 2 deletions(-) create mode 100644 docs/SWIFT_GUIDELINES.md diff --git a/AGENTS.md b/AGENTS.md index 54e8101..a1db0f3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -42,6 +42,8 @@ OpenAppLockUITests/ XCUITest flows (see harness below) docs/RULES_FEATURE_SPEC.md Feature spec derived from the Opal reference recording; §6 maps it to the native presentation. 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 @@ -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), implement, re-run focused tests, then the full suite. Run tests often and fail fast. -- Conventional commits (`feat:`, `fix:`, `refactor:` …), no AI attribution - footers. Commit only when the user asks. +- Conventional commits (`feat:`, `fix:`, `refactor:` …). **Agent attribution is + 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) ` (a + non-Claude agent uses its own name/email). Commit only when the user asks. ## UI-test harness diff --git a/docs/SWIFT_GUIDELINES.md b/docs/SWIFT_GUIDELINES.md new file mode 100644 index 0000000..a28aa22 --- /dev/null +++ b/docs/SWIFT_GUIDELINES.md @@ -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: 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 { + 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.