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:
2026-06-13 15:14:24 -04:00
parent f9e6bda0ab
commit d92a8588bb
2 changed files with 247 additions and 2 deletions

View File

@@ -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
View 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.