@mrciphersmith/keryx 0.3.3 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +996 -366
- package/docs/README.md +2 -0
- package/package.json +1 -1
- package/src/gdskills/bundled/install-manifest.json +520 -48
- package/src/gdskills/bundled/stacks/csharp-dotnet/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/governance/eval.json +1881 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/governance/scout.json +33 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/pack.json +38 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/rules/coding-style.mdc +100 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/rules/patterns.mdc +107 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/rules/security.mdc +86 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/rules/testing.mdc +89 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-build-fix/SKILL.md +143 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-build-fix/evals.json +77 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-code-review/SKILL.md +121 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-code-review/evals.json +77 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-implementation/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-implementation/evals.json +76 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-testing/SKILL.md +130 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-testing/evals.json +77 -0
- package/src/gdskills/bundled/stacks/django/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/django/governance/eval.json +1763 -0
- package/src/gdskills/bundled/stacks/django/governance/scout.json +40 -0
- package/src/gdskills/bundled/stacks/django/pack.json +43 -0
- package/src/gdskills/bundled/stacks/django/rules/coding-style.mdc +80 -0
- package/src/gdskills/bundled/stacks/django/rules/patterns.mdc +92 -0
- package/src/gdskills/bundled/stacks/django/rules/security.mdc +92 -0
- package/src/gdskills/bundled/stacks/django/rules/testing.mdc +89 -0
- package/src/gdskills/bundled/stacks/django/skills/django-build-fix/SKILL.md +149 -0
- package/src/gdskills/bundled/stacks/django/skills/django-build-fix/evals.json +49 -0
- package/src/gdskills/bundled/stacks/django/skills/django-code-review/SKILL.md +137 -0
- package/src/gdskills/bundled/stacks/django/skills/django-code-review/evals.json +48 -0
- package/src/gdskills/bundled/stacks/django/skills/django-implementation/SKILL.md +147 -0
- package/src/gdskills/bundled/stacks/django/skills/django-implementation/evals.json +75 -0
- package/src/gdskills/bundled/stacks/django/skills/django-migrate/SKILL.md +166 -0
- package/src/gdskills/bundled/stacks/django/skills/django-migrate/evals.json +49 -0
- package/src/gdskills/bundled/stacks/django/skills/django-testing/SKILL.md +130 -0
- package/src/gdskills/bundled/stacks/django/skills/django-testing/evals.json +48 -0
- package/src/gdskills/bundled/stacks/fastapi/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/fastapi/governance/eval.json +1777 -0
- package/src/gdskills/bundled/stacks/fastapi/governance/scout.json +34 -0
- package/src/gdskills/bundled/stacks/fastapi/pack.json +43 -0
- package/src/gdskills/bundled/stacks/fastapi/rules/coding-style.mdc +68 -0
- package/src/gdskills/bundled/stacks/fastapi/rules/patterns.mdc +108 -0
- package/src/gdskills/bundled/stacks/fastapi/rules/security.mdc +99 -0
- package/src/gdskills/bundled/stacks/fastapi/rules/testing.mdc +85 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-build-fix/SKILL.md +157 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-build-fix/evals.json +76 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-code-review/SKILL.md +150 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-code-review/evals.json +74 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-implementation/SKILL.md +158 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-implementation/evals.json +75 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-testing/SKILL.md +146 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-testing/evals.json +74 -0
- package/src/gdskills/bundled/stacks/flutter-dart/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/flutter-dart/governance/eval.json +1849 -0
- package/src/gdskills/bundled/stacks/flutter-dart/governance/scout.json +33 -0
- package/src/gdskills/bundled/stacks/flutter-dart/pack.json +41 -0
- package/src/gdskills/bundled/stacks/flutter-dart/rules/coding-style.mdc +98 -0
- package/src/gdskills/bundled/stacks/flutter-dart/rules/patterns.mdc +88 -0
- package/src/gdskills/bundled/stacks/flutter-dart/rules/security.mdc +91 -0
- package/src/gdskills/bundled/stacks/flutter-dart/rules/testing.mdc +101 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-build-fix/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-build-fix/evals.json +79 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-code-review/SKILL.md +124 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-code-review/evals.json +74 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-implementation/SKILL.md +139 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-implementation/evals.json +77 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-testing/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-testing/evals.json +74 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/governance/eval.json +2194 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/governance/scout.json +39 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/pack.json +40 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/coding-style.mdc +67 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/patterns.mdc +65 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/security.mdc +69 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/testing.mdc +80 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-build-fix/SKILL.md +144 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-build-fix/evals.json +74 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-code-review/SKILL.md +129 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-code-review/evals.json +74 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-implementation/SKILL.md +147 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-implementation/evals.json +75 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-migrate/SKILL.md +139 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-migrate/evals.json +74 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-testing/SKILL.md +128 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-testing/evals.json +73 -0
- package/src/gdskills/bundled/stacks/kotlin-android/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/kotlin-android/governance/eval.json +1889 -0
- package/src/gdskills/bundled/stacks/kotlin-android/governance/scout.json +34 -0
- package/src/gdskills/bundled/stacks/kotlin-android/pack.json +38 -0
- package/src/gdskills/bundled/stacks/kotlin-android/rules/coding-style.mdc +89 -0
- package/src/gdskills/bundled/stacks/kotlin-android/rules/patterns.mdc +96 -0
- package/src/gdskills/bundled/stacks/kotlin-android/rules/security.mdc +90 -0
- package/src/gdskills/bundled/stacks/kotlin-android/rules/testing.mdc +89 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/compose-implementation/SKILL.md +150 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/compose-implementation/evals.json +77 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-build-fix/SKILL.md +151 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-build-fix/evals.json +76 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-code-review/SKILL.md +139 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-code-review/evals.json +78 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-testing/SKILL.md +131 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-testing/evals.json +77 -0
- package/src/gdskills/bundled/stacks/python/agent-refs.json +2 -1
- package/src/gdskills/bundled/stacks/python/pack.json +1 -1
- package/src/gdskills/bundled/stacks/rust/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/rust/governance/eval.json +1823 -0
- package/src/gdskills/bundled/stacks/rust/governance/scout.json +32 -0
- package/src/gdskills/bundled/stacks/rust/pack.json +42 -0
- package/src/gdskills/bundled/stacks/rust/rules/coding-style.mdc +93 -0
- package/src/gdskills/bundled/stacks/rust/rules/patterns.mdc +85 -0
- package/src/gdskills/bundled/stacks/rust/rules/security.mdc +85 -0
- package/src/gdskills/bundled/stacks/rust/rules/testing.mdc +82 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-build-fix/SKILL.md +141 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-build-fix/evals.json +78 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-code-review/SKILL.md +127 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-code-review/evals.json +72 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-implementation/SKILL.md +133 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-implementation/evals.json +79 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-testing/SKILL.md +130 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-testing/evals.json +75 -0
- package/src/gdskills/bundled/stacks/swift-ios/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/swift-ios/governance/eval.json +1803 -0
- package/src/gdskills/bundled/stacks/swift-ios/governance/scout.json +32 -0
- package/src/gdskills/bundled/stacks/swift-ios/pack.json +38 -0
- package/src/gdskills/bundled/stacks/swift-ios/rules/coding-style.mdc +92 -0
- package/src/gdskills/bundled/stacks/swift-ios/rules/patterns.mdc +112 -0
- package/src/gdskills/bundled/stacks/swift-ios/rules/security.mdc +78 -0
- package/src/gdskills/bundled/stacks/swift-ios/rules/testing.mdc +90 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-build-fix/SKILL.md +144 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-build-fix/evals.json +75 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-code-review/SKILL.md +122 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-code-review/evals.json +75 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-testing/SKILL.md +131 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-testing/evals.json +75 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swiftui-implementation/SKILL.md +149 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swiftui-implementation/evals.json +76 -0
- package/src/gdskills/bundled/agents/python-build-fixer.md +0 -52
- package/src/gdskills/bundled/agents/python-code-auditor.md +0 -49
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"query": "Use when implementing or extending a feature in a SwiftUI/iOS app -- state ownership (@State/@Binding/@Environment/@Observable), Swift concurrency (async/await, actors, @MainActor, TaskGroup, Sendable), and view composition.",
|
|
4
|
+
"decision": "create",
|
|
5
|
+
"topMatch": "flutter-dart/flutter-implementation",
|
|
6
|
+
"recordedAt": "2026-09-25T15:32:07.026Z",
|
|
7
|
+
"skillName": "swiftui-implementation"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"query": "Use when a Swift/iOS test suite needs writing, extending, or fixing -- Swift Testing's @Test/#expect/#require, parameterized tests, legacy XCTest, and mocking network/persistence dependencies at a protocol boundary rather than the type under test's internals.",
|
|
11
|
+
"decision": "fork",
|
|
12
|
+
"topMatch": "csharp-dotnet/dotnet-testing",
|
|
13
|
+
"recordedAt": "2026-09-25T15:35:21.070Z",
|
|
14
|
+
"skillName": "swift-testing",
|
|
15
|
+
"justification": "Nearest match csharp-dotnet/dotnet-testing tests C# with xUnit/NUnit/Moq, a different language and tooling entirely; no existing test skill covers Swift Testing's @Test/#expect, XCTest, or Swift protocol-boundary mocking."
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"query": "Use when reviewing a Swift/iOS change for concurrency, memory, and safety risks -- force-unwraps, retain cycles from closures capturing self, missing @MainActor isolation, @unchecked Sendable used to silence checks, and secrets stored outside the Keychain. Read-only, no edits.",
|
|
19
|
+
"decision": "create",
|
|
20
|
+
"topMatch": "csharp-dotnet/dotnet-code-review",
|
|
21
|
+
"recordedAt": "2026-09-25T15:32:07.629Z",
|
|
22
|
+
"skillName": "swift-code-review"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"query": "Use when xcodebuild/swift build fails, or a Swift 6 strict-concurrency/Sendable error, SwiftLint failure, or failing test blocks the build -- resolves the root cause instead of force-unwrapping, adding @unchecked Sendable, or disabling a lint rule to silence the check.",
|
|
26
|
+
"decision": "fork",
|
|
27
|
+
"topMatch": "ts-js-node/nodejs-build-fix",
|
|
28
|
+
"recordedAt": "2026-09-25T15:35:21.350Z",
|
|
29
|
+
"skillName": "swift-build-fix",
|
|
30
|
+
"justification": "Nearest match ts-js-node/nodejs-build-fix fixes tsc/ESM/eslint errors for Node/TS, not xcodebuild/Swift 6 concurrency/Sendable/SwiftLint failures; each build-fix skill is scoped to its own toolchain's specific failure surface."
|
|
31
|
+
}
|
|
32
|
+
]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "swift-ios",
|
|
3
|
+
"family": "framework",
|
|
4
|
+
"modules": ["swift-ios-rules", "swift-ios-skills"],
|
|
5
|
+
"detectionMarkers": ["ios"],
|
|
6
|
+
"provenance": { "origin": "authored", "sourceRef": "flow 336, Wave 4 batch 4" },
|
|
7
|
+
"stability": "experimental",
|
|
8
|
+
"skills": {
|
|
9
|
+
"implement": ["swiftui-implementation"],
|
|
10
|
+
"test": ["swift-testing"],
|
|
11
|
+
"review": ["swift-code-review"],
|
|
12
|
+
"build-fix": ["swift-build-fix"],
|
|
13
|
+
"migrate": []
|
|
14
|
+
},
|
|
15
|
+
"agentProfile": {
|
|
16
|
+
"displayName": "Swift/iOS",
|
|
17
|
+
"auditFocus": [
|
|
18
|
+
"force-unwrapping (`!`) or force-try (`try!`) on a value that can genuinely be nil or throw, especially on network/decoded data",
|
|
19
|
+
"an `@Observable` model or any UI-touching type not isolated to `@MainActor`, risking a cross-actor data race",
|
|
20
|
+
"a closure captured strongly by `self` in a long-lived callback (completion handler, Combine sink, stored closure) causing a retain cycle",
|
|
21
|
+
"a secret, API key, or auth token stored in `UserDefaults` or hardcoded in source instead of the Keychain",
|
|
22
|
+
"state ownership mismatches: `@State` for a value that is actually shared/mutated by a parent, or a legacy `@StateObject` recreated inline instead of owned once",
|
|
23
|
+
"`@unchecked Sendable` or a blanket concurrency-checking suppression applied to silence the compiler instead of fixing the actual data-race shape"
|
|
24
|
+
],
|
|
25
|
+
"buildCommands": [
|
|
26
|
+
"xcodebuild build -scheme <Scheme> -destination 'platform=iOS Simulator,name=<Simulator>' (or swift build for a SwiftPM package)",
|
|
27
|
+
"xcodebuild test -scheme <Scheme> -destination 'platform=iOS Simulator,name=<Simulator>' (or swift test)",
|
|
28
|
+
"swiftlint",
|
|
29
|
+
"swift-format lint --recursive . (or swiftformat --lint . depending on the project's configured formatter)"
|
|
30
|
+
],
|
|
31
|
+
"fixGuardrails": [
|
|
32
|
+
"never mark a type `@unchecked Sendable` to silence a strict-concurrency error without actually proving and documenting why its mutable state is safe",
|
|
33
|
+
"never force-unwrap (`!`) or force-try (`try!`) just to clear a compiler warning about an optional or a throwing call",
|
|
34
|
+
"never disable a SwiftLint/swift-format rule repo-wide (or with a blanket `// swiftlint:disable`) to make one finding disappear",
|
|
35
|
+
"never delete or skip a failing test, or loosen its assertion, to reach a green build"
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: ["**/*.swift"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Swift/iOS coding style
|
|
9
|
+
|
|
10
|
+
Narrows `core-common-rules`' stack-agnostic style rules to modern Swift
|
|
11
|
+
(6.x) and SwiftUI idiom. Applies only to `*.swift` files — everything not
|
|
12
|
+
Swift-specific still comes from the common rules this file `extends`.
|
|
13
|
+
|
|
14
|
+
## Naming and structure
|
|
15
|
+
|
|
16
|
+
- Types (`struct`, `class`, `enum`, `protocol`) are `UpperCamelCase`;
|
|
17
|
+
properties, methods, and cases are `lowerCamelCase` — never
|
|
18
|
+
`snake_case` for either.
|
|
19
|
+
- Protocol names read as a capability (`Fetchable`, `Cacheable`) or a
|
|
20
|
+
role noun (`DataSource`, `Delegate`); do not suffix a protocol with
|
|
21
|
+
`Protocol`.
|
|
22
|
+
- An identifier's name shrinks with its scope: a tight closure parameter
|
|
23
|
+
can be `$0`/a one-letter name, a public API member gets a full
|
|
24
|
+
descriptive name.
|
|
25
|
+
- File name matches the primary type it declares (`OrderService.swift`
|
|
26
|
+
declares `OrderService`); one primary type per file for anything
|
|
27
|
+
public.
|
|
28
|
+
|
|
29
|
+
## Value types vs reference types
|
|
30
|
+
|
|
31
|
+
- Default to `struct` (and `enum` for closed sets of cases) for model and
|
|
32
|
+
state types — value semantics mean a copy cannot be mutated out from
|
|
33
|
+
under another owner, which is what SwiftUI's data flow assumes.
|
|
34
|
+
- Reach for `class` only when you need reference semantics on purpose:
|
|
35
|
+
shared mutable state across owners, identity that must be preserved
|
|
36
|
+
(`===`), or Objective-C interop. Justify a `class` model type in review
|
|
37
|
+
rather than defaulting to it out of habit from other languages.
|
|
38
|
+
- A `class` used as SwiftUI-observed state is marked `@Observable` (see
|
|
39
|
+
`patterns.mdc`) and, when its mutable state is touched from view code,
|
|
40
|
+
isolated to `@MainActor`.
|
|
41
|
+
|
|
42
|
+
## Guard for early exit
|
|
43
|
+
|
|
44
|
+
- Use `guard let`/`guard` with an early `return`/`continue`/`throw` to
|
|
45
|
+
unwrap a precondition and keep the "happy path" unindented, rather than
|
|
46
|
+
nesting the whole function body inside an `if let`.
|
|
47
|
+
- Prefer a single `guard` with multiple comma-separated conditions over a
|
|
48
|
+
pyramid of nested `if let`s when several optionals must all be present
|
|
49
|
+
before the function can proceed.
|
|
50
|
+
|
|
51
|
+
## Optionals
|
|
52
|
+
|
|
53
|
+
- Treat `!` (force-unwrap) and `try!` (force-try) as a last resort, not a
|
|
54
|
+
convenience — reserve them for a value whose absence is a genuine
|
|
55
|
+
programmer error that should crash in development (an `IBOutlet` wired
|
|
56
|
+
in a storyboard, a compile-time-guaranteed non-nil literal), never for
|
|
57
|
+
network responses, decoded JSON, user input, or anything else that can
|
|
58
|
+
legitimately be nil or throw in production.
|
|
59
|
+
- Prefer `guard let`/`if let`, nil-coalescing (`??`) with a real default,
|
|
60
|
+
or `try`/`try?` with explicit handling. Use `#require(...)` (Swift
|
|
61
|
+
Testing) or `XCTUnwrap` in tests instead of `!` when a test needs to
|
|
62
|
+
unwrap and fail with a clear message if the value is missing.
|
|
63
|
+
- Chain optional access with `?.`/optional chaining rather than
|
|
64
|
+
force-unwrapping an intermediate step in a chain.
|
|
65
|
+
|
|
66
|
+
## @Observable vs legacy ObservableObject
|
|
67
|
+
|
|
68
|
+
- For code targeting iOS 17+, model types that drive SwiftUI view
|
|
69
|
+
updates use the `@Observable` macro, not the legacy
|
|
70
|
+
`ObservableObject`/`@Published` combination — `@Observable` tracks
|
|
71
|
+
property-level access automatically, so a view only re-renders for the
|
|
72
|
+
properties it actually reads, where `@Published` re-renders on any
|
|
73
|
+
change to the whole object.
|
|
74
|
+
- Mark an `@Observable` class `@MainActor` unless the project has set a
|
|
75
|
+
module-wide default actor isolation to `MainActor` — this keeps its
|
|
76
|
+
mutable state confined to the main actor by construction (see
|
|
77
|
+
`patterns.mdc` for the concurrency implications).
|
|
78
|
+
- Only reach for `ObservableObject`/`@Published`/`@StateObject`/
|
|
79
|
+
`@ObservedObject` when the project's minimum deployment target
|
|
80
|
+
predates iOS 17, or an existing codebase has not yet migrated; do not
|
|
81
|
+
introduce the legacy pattern in new code on a project that already
|
|
82
|
+
targets iOS 17+.
|
|
83
|
+
|
|
84
|
+
## Formatting and linting
|
|
85
|
+
|
|
86
|
+
- Format with the project's configured formatter (`swift-format` or
|
|
87
|
+
`swiftformat`) before finishing a change; do not hand-format around a
|
|
88
|
+
formatter that is already configured in the project.
|
|
89
|
+
- Fix a SwiftLint finding at its root cause; do not silence it with a
|
|
90
|
+
blanket `// swiftlint:disable` covering more than the one line it
|
|
91
|
+
actually applies to, and never disable a rule repository-wide to clear
|
|
92
|
+
one finding.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: ["**/*.swift"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Swift/iOS patterns
|
|
9
|
+
|
|
10
|
+
Narrows `core-common-rules`' stack-agnostic design guidance to idiomatic
|
|
11
|
+
Swift concurrency and SwiftUI state ownership. Applies only to `*.swift`
|
|
12
|
+
files.
|
|
13
|
+
|
|
14
|
+
## Swift concurrency: async/await and structured concurrency
|
|
15
|
+
|
|
16
|
+
- Prefer `async`/`await` over completion-handler callbacks for new
|
|
17
|
+
asynchronous code — it reads linearly and composes with structured
|
|
18
|
+
concurrency instead of nesting closures.
|
|
19
|
+
- Start concurrent child work with `async let` (a small, fixed number of
|
|
20
|
+
parallel tasks) or `TaskGroup`/`withThrowingTaskGroup` (a dynamic or
|
|
21
|
+
unbounded set of child tasks); both guarantee every child task is
|
|
22
|
+
awaited or cancelled before the parent scope exits, unlike a bare
|
|
23
|
+
unstructured `Task {}` with no owner to join it. (`Task {}` is
|
|
24
|
+
"unstructured" — it inherits the creating context's actor and priority
|
|
25
|
+
but nothing tracks its lifetime; `Task.detached {}` is a stronger,
|
|
26
|
+
rarely-needed form that inherits neither — do not use the two names
|
|
27
|
+
interchangeably.)
|
|
28
|
+
- SwiftUI's `.task { }` modifier closure is already async — it is the
|
|
29
|
+
boundary, not a place to wrap another `Task {}` inside it. Reach for a
|
|
30
|
+
bare `Task {}` only at a genuine synchronous-to-async boundary with no
|
|
31
|
+
async closure already available (a button action's `Void`-returning
|
|
32
|
+
handler, a delegate callback); give it an explicit reason it does not
|
|
33
|
+
need to be joined. Prefer `.task { }` directly on a SwiftUI view over a
|
|
34
|
+
manually created `Task` when the work should be tied to the view's
|
|
35
|
+
lifetime (it is cancelled automatically when the view disappears).
|
|
36
|
+
- Respect cancellation in long-running async work: check
|
|
37
|
+
`Task.isCancelled`/call `try Task.checkCancellation()` in a loop, so a
|
|
38
|
+
cancelled parent actually stops the child's work instead of running to
|
|
39
|
+
completion regardless.
|
|
40
|
+
|
|
41
|
+
## Actors and @MainActor
|
|
42
|
+
|
|
43
|
+
- Use `actor` to protect mutable state that multiple tasks touch
|
|
44
|
+
concurrently — its methods are implicitly isolated, so the compiler
|
|
45
|
+
(not a convention) prevents unsynchronized access, the same guarantee
|
|
46
|
+
a manual lock only provides by discipline.
|
|
47
|
+
- Since the iOS 18 SDK, `View` conformance is itself `@MainActor`-isolated
|
|
48
|
+
— a view's own `body` and any `@State`/`@Binding` kept directly on the
|
|
49
|
+
view struct are already main-actor-isolated automatically, with no
|
|
50
|
+
explicit annotation needed. That isolation does NOT extend to a
|
|
51
|
+
SEPARATE type: mark any standalone type whose state SwiftUI reads or
|
|
52
|
+
mutates `@MainActor` explicitly — view models, `@Observable` stores, or
|
|
53
|
+
any other type outside the `View` struct itself that a `body` touches.
|
|
54
|
+
Isolate at the type level (`@MainActor final class ...`) rather than
|
|
55
|
+
sprinkling `@MainActor` on individual methods, unless only some of the
|
|
56
|
+
type's methods genuinely need main-actor isolation.
|
|
57
|
+
- Cross an actor boundary explicitly with `await`; do not reach for
|
|
58
|
+
`@unchecked Sendable` or `nonisolated(unsafe)` to make a boundary
|
|
59
|
+
error disappear — either the type's state genuinely needs no
|
|
60
|
+
isolation (make that case explicitly, with a comment), or it needs a
|
|
61
|
+
real actor/lock, not a suppression.
|
|
62
|
+
|
|
63
|
+
## Sendable conformance
|
|
64
|
+
|
|
65
|
+
- A value crossing a concurrency boundary (passed into a `Task`, an
|
|
66
|
+
actor method, or a `TaskGroup` child) must be `Sendable`: a `struct`
|
|
67
|
+
or `enum` with only `Sendable` members is `Sendable` for free; a
|
|
68
|
+
`final class` with only immutable (`let`) stored properties can
|
|
69
|
+
declare conformance explicitly.
|
|
70
|
+
- Do not slap `@unchecked Sendable` on a class to silence a
|
|
71
|
+
strict-concurrency diagnostic without actually auditing and
|
|
72
|
+
documenting why its mutable state cannot race — that trades a
|
|
73
|
+
compile-time guarantee for an unchecked promise, which is exactly the
|
|
74
|
+
class of bug Swift 6's data-race safety exists to catch.
|
|
75
|
+
- Under the Swift 6 language mode, treat a "Sendable" or "actor
|
|
76
|
+
isolation" compiler error as a real potential data race to fix at the
|
|
77
|
+
root (add an actor, make the type immutable, or copy the value before
|
|
78
|
+
crossing), not friction to suppress.
|
|
79
|
+
|
|
80
|
+
## SwiftUI state ownership
|
|
81
|
+
|
|
82
|
+
- `@State` owns a value the view itself creates and is the sole owner
|
|
83
|
+
of — a simple value type, or (iOS 17+) an `@Observable` reference type
|
|
84
|
+
the view instantiates directly (`@State private var model = Model()`,
|
|
85
|
+
not `@StateObject` for `@Observable` types).
|
|
86
|
+
- `@Binding` is for a child view that needs to both read and mutate a
|
|
87
|
+
value it does not own — the parent passes `$value`, and the child
|
|
88
|
+
writes through the binding instead of receiving a copy it cannot
|
|
89
|
+
propagate changes from.
|
|
90
|
+
- `@Environment` is for values that are ambient to a whole subtree
|
|
91
|
+
(theme, a shared service, a feature flag) rather than threaded
|
|
92
|
+
explicitly through every initializer; do not reach for it as a
|
|
93
|
+
shortcut to avoid passing an explicit dependency that only one or two
|
|
94
|
+
views actually need.
|
|
95
|
+
- A view that receives an `@Observable` model from a parent and needs to
|
|
96
|
+
pass a binding to one of its properties down further uses `@Bindable`,
|
|
97
|
+
not a manually reconstructed `Binding(get:set:)`.
|
|
98
|
+
|
|
99
|
+
## Anti-patterns to flag
|
|
100
|
+
|
|
101
|
+
- A retain cycle from a closure (completion handler, Combine `sink`,
|
|
102
|
+
stored callback) that captures `self` strongly when the closure
|
|
103
|
+
outlives the call that created it — use `[weak self]` and unwrap, or
|
|
104
|
+
`[unowned self]` only when `self`'s lifetime is provably guaranteed to
|
|
105
|
+
exceed the closure's.
|
|
106
|
+
- Lifting a value into `@State`/`@StateObject` at the view that merely
|
|
107
|
+
displays it, when a parent further up actually owns and mutates it —
|
|
108
|
+
that duplicates state instead of using `@Binding`/`@Observable`
|
|
109
|
+
references, and the two copies drift.
|
|
110
|
+
- A massive view or view-model type that owns navigation, networking,
|
|
111
|
+
and persistence together — split by responsibility the same way a
|
|
112
|
+
"god package"/"god object" is flagged in any other stack.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: ["**/*.swift"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Swift/iOS security
|
|
9
|
+
|
|
10
|
+
Narrows `core-common-rules`' stack-agnostic security rules to
|
|
11
|
+
iOS/Apple-platform-specific risks and the safe framework API to use
|
|
12
|
+
instead. Applies only to `*.swift` files.
|
|
13
|
+
|
|
14
|
+
## Secrets and credential storage
|
|
15
|
+
|
|
16
|
+
- Store credentials, tokens, and refresh tokens in the Keychain
|
|
17
|
+
(`Security` framework, or a thin wrapper around it) — never in
|
|
18
|
+
`UserDefaults`, which is unencrypted plist storage readable by
|
|
19
|
+
anything with filesystem access to the app's container, and never in a
|
|
20
|
+
plain file the app writes itself.
|
|
21
|
+
- Never hardcode an API key, client secret, or signing credential as a
|
|
22
|
+
string literal in source. Load it from a build-time configuration
|
|
23
|
+
(an `.xcconfig` value injected into `Info.plist`, a secrets manager
|
|
24
|
+
fetched at runtime) that is excluded from source control, and treat a
|
|
25
|
+
literal key found in a diff as a finding, not a style nit.
|
|
26
|
+
- Set an appropriate Keychain accessibility level
|
|
27
|
+
(`kSecAttrAccessibleWhenUnlockedThisDeviceOnly` or stricter) for
|
|
28
|
+
anything sensitive — the default accessibility can survive a device
|
|
29
|
+
backup/restore onto different hardware, which is usually not the
|
|
30
|
+
intended threat model for a session token.
|
|
31
|
+
|
|
32
|
+
## Network security (ATS and transport)
|
|
33
|
+
|
|
34
|
+
- Leave App Transport Security (ATS) at its default (HTTPS-only,
|
|
35
|
+
TLS 1.2+); do not add `NSAllowsArbitraryLoads` or a blanket
|
|
36
|
+
`NSExceptionDomains` bypass to `Info.plist` to work around a backend
|
|
37
|
+
that is not yet on HTTPS — fix the backend, or scope an ATS exception
|
|
38
|
+
to the exact domain and the exact minimum relaxation needed, with a
|
|
39
|
+
comment saying why.
|
|
40
|
+
- Validate TLS the platform's normal way (`URLSession`'s default trust
|
|
41
|
+
evaluation); do not implement a `URLSessionDelegate` that accepts any
|
|
42
|
+
server certificate (`.performDefaultHandling` bypassed with
|
|
43
|
+
unconditional trust) outside of a short-lived local/test
|
|
44
|
+
configuration, and never ship that in production.
|
|
45
|
+
|
|
46
|
+
## Biometric authentication
|
|
47
|
+
|
|
48
|
+
- Treat a `LAContext` biometric (Face ID/Touch ID) result as a *local
|
|
49
|
+
presence* gate, not proof of identity to a remote server on its own —
|
|
50
|
+
a successful `evaluatePolicy` unlocks a locally-held secret (a
|
|
51
|
+
Keychain item, a locally cached credential); it does not substitute
|
|
52
|
+
for a real authentication token when talking to a backend.
|
|
53
|
+
- Check the specific `LAError` case on failure (`.userCancel`,
|
|
54
|
+
`.biometryNotAvailable`, `.biometryLockout`, ...) and present a
|
|
55
|
+
fallback (passcode, credential re-entry) rather than treating every
|
|
56
|
+
failure identically or silently retrying.
|
|
57
|
+
- Do not cache "biometric succeeded" as a long-lived boolean flag read
|
|
58
|
+
later by unrelated code paths — re-evaluate at the point access is
|
|
59
|
+
actually needed, scoped to what that specific action requires.
|
|
60
|
+
|
|
61
|
+
## Input handling and injection-adjacent risks
|
|
62
|
+
|
|
63
|
+
- Parameterize any `NSPredicate`/Core Data query built from
|
|
64
|
+
user-influenced input rather than interpolating the value directly
|
|
65
|
+
into the predicate format string — the same injection-shaped risk as
|
|
66
|
+
building SQL by string concatenation.
|
|
67
|
+
- Validate/sanitize any URL or deep link the app opens
|
|
68
|
+
(`UIApplication.open`, a Universal Link handler) before acting on it —
|
|
69
|
+
a deep link is untrusted input from outside the process, not a value
|
|
70
|
+
to trust and route on directly.
|
|
71
|
+
|
|
72
|
+
## Logging
|
|
73
|
+
|
|
74
|
+
- Never log a token, password, or full PII payload at a level that
|
|
75
|
+
reaches persistent device logs (`print`, unredacted `os_log`/`Logger`
|
|
76
|
+
interpolation) — use `Logger`'s privacy-redaction (`\(value,
|
|
77
|
+
privacy: .private)`) for anything sensitive, or omit it from the log
|
|
78
|
+
entirely.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: ["**/*.swift"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Swift/iOS testing
|
|
9
|
+
|
|
10
|
+
Narrows `core-common-rules`' stack-agnostic testing rules to Swift
|
|
11
|
+
Testing (the current default for new test code) and legacy XCTest, where
|
|
12
|
+
the project has not yet migrated. Applies only to `*.swift` files.
|
|
13
|
+
|
|
14
|
+
## Swift Testing vs XCTest
|
|
15
|
+
|
|
16
|
+
- For new test code on a toolchain that supports it, prefer the Swift
|
|
17
|
+
Testing framework (`import Testing`, `@Test` functions, `#expect`/
|
|
18
|
+
`#require` macros) over XCTest — it gives clearer failure messages
|
|
19
|
+
(`#expect` captures the evaluated sub-expressions), native
|
|
20
|
+
parameterized tests, and tests run in parallel by default.
|
|
21
|
+
- Swift Testing does not yet replace XCTest everywhere: UI automation
|
|
22
|
+
(`XCUITest`) and performance tests (`XCTMetric`/`measure`) still run
|
|
23
|
+
on XCTest, and an existing XCTest suite does not need a wholesale
|
|
24
|
+
rewrite just to adopt Swift Testing — the two frameworks coexist in
|
|
25
|
+
the same target. Match whichever the project's existing test target
|
|
26
|
+
already uses for a given kind of test; introduce Swift Testing for new
|
|
27
|
+
unit-test coverage rather than mixing conventions within one file.
|
|
28
|
+
- A Swift Testing function is `@Test func name() async throws { ... }`
|
|
29
|
+
(not `test`-prefixed, no `XCTestCase` subclass needed); assert with
|
|
30
|
+
`#expect(condition)` for a check that should record a failure and
|
|
31
|
+
continue, or `try #require(optional)` for one that must stop the test
|
|
32
|
+
immediately if it fails (the Swift Testing equivalent of
|
|
33
|
+
`XCTUnwrap`/`XCTAssert...` followed by an early return).
|
|
34
|
+
|
|
35
|
+
## Structure and naming
|
|
36
|
+
|
|
37
|
+
- One `@Test` (or `XCTest` method) per behavior being verified; a
|
|
38
|
+
descriptive test name (`@Test("returns nil for an empty cart")` or a
|
|
39
|
+
method named `test_returnsNil_whenCartIsEmpty`) over `test1`/`test2`.
|
|
40
|
+
- Use Swift Testing's `arguments:` parameterization for the same
|
|
41
|
+
assertion repeated over several inputs, instead of a hand-rolled loop
|
|
42
|
+
inside one test function or several copy-pasted test functions that
|
|
43
|
+
differ only by input.
|
|
44
|
+
- Group related tests with a `struct`/`enum` namespace and Swift
|
|
45
|
+
Testing's `@Suite`, or an `XCTestCase` subclass for legacy code,
|
|
46
|
+
matching whatever the target already does.
|
|
47
|
+
|
|
48
|
+
## Mocking at the protocol boundary
|
|
49
|
+
|
|
50
|
+
- Mock external dependencies — network clients, persistence
|
|
51
|
+
(`UserDefaults`, Core Data, a database layer), platform services
|
|
52
|
+
(location, notifications) — at a protocol boundary the production code
|
|
53
|
+
already depends on, never by mocking the type under test's own
|
|
54
|
+
internal collaborators or reaching into its private state.
|
|
55
|
+
- Define (or reuse) a narrow protocol for the dependency (`protocol
|
|
56
|
+
OrderClient { func fetchOrder(id: String) async throws -> Order }`),
|
|
57
|
+
inject a real conforming type in production and a test double
|
|
58
|
+
conforming to the same protocol in tests — the type under test never
|
|
59
|
+
needs to know it is under test.
|
|
60
|
+
- Do not mock one layer too deep (stubbing a private helper method, or
|
|
61
|
+
a concrete `URLSession` call inside a class instead of the protocol
|
|
62
|
+
that wraps it) — that couples the test to an implementation detail and
|
|
63
|
+
breaks on a harmless refactor instead of only on an actual behavior
|
|
64
|
+
change.
|
|
65
|
+
|
|
66
|
+
## Async and concurrency in tests
|
|
67
|
+
|
|
68
|
+
- Mark a test `async` and `await` the async call under test directly —
|
|
69
|
+
Swift Testing and modern `XCTestCase` both support `async` test
|
|
70
|
+
methods natively; do not reach for an `XCTestExpectation`/semaphore to
|
|
71
|
+
bridge an `async` API into a synchronous-looking test unless the
|
|
72
|
+
project's minimum toolchain predates async test support.
|
|
73
|
+
- Never synchronize with a fixed delay (`Thread.sleep`/`Task.sleep` used
|
|
74
|
+
as a guess at "enough time") to wait for concurrent work to finish —
|
|
75
|
+
`await` the async call, join a `TaskGroup`, or await an
|
|
76
|
+
`XCTestExpectation` with a real fulfillment signal instead of a timed
|
|
77
|
+
guess.
|
|
78
|
+
- Annotate a test `@MainActor` when it exercises `@MainActor`-isolated
|
|
79
|
+
view-model/state code, so the test itself runs on the same actor its
|
|
80
|
+
subject requires.
|
|
81
|
+
|
|
82
|
+
## Determinism
|
|
83
|
+
|
|
84
|
+
- A test must not depend on real network access, the real Keychain, or
|
|
85
|
+
wall-clock time; inject a fake/in-memory conforming type at the
|
|
86
|
+
protocol boundary and, for time-dependent logic, inject a clock/date
|
|
87
|
+
provider instead of calling `Date()` directly inside the code under
|
|
88
|
+
test.
|
|
89
|
+
- A bug fix gets a regression test that fails before the fix and passes
|
|
90
|
+
after; new behavior gets new test coverage in the same change.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: swift-build-fix
|
|
3
|
+
description: "Use when xcodebuild/swift build fails, or a Swift 6 strict-concurrency/Sendable error, SwiftLint failure, or failing test blocks the build -- resolves the root cause instead of force-unwrapping, adding @unchecked Sendable, or disabling a lint rule to silence the check."
|
|
4
|
+
triggers:
|
|
5
|
+
- "xcodebuild is failing"
|
|
6
|
+
- "swift build error"
|
|
7
|
+
- "fix this Swift 6 concurrency error"
|
|
8
|
+
- "SwiftLint is failing"
|
|
9
|
+
- "resolve this Sendable conformance error"
|
|
10
|
+
- "this Swift test is failing, fix the build"
|
|
11
|
+
metadata:
|
|
12
|
+
origin: authored
|
|
13
|
+
category: build-fix
|
|
14
|
+
version: "1.0.0"
|
|
15
|
+
compatible_harnesses: "claude,codex,cursor,zed,opencode"
|
|
16
|
+
license: "MIT"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Swift/iOS build fix
|
|
20
|
+
|
|
21
|
+
Resolve an `xcodebuild`/`swift build` failure, a Swift 6 strict-
|
|
22
|
+
concurrency or `Sendable` conformance error, a SwiftLint/swift-format
|
|
23
|
+
failure, or a failing test blocking the build — with the smallest change
|
|
24
|
+
that fixes the actual root cause. `rules/coding-style.mdc` and
|
|
25
|
+
`rules/patterns.mdc` govern what a "correct" fix looks like; this skill
|
|
26
|
+
never reaches for a suppression instead of a fix.
|
|
27
|
+
|
|
28
|
+
## Workflow
|
|
29
|
+
|
|
30
|
+
### Step 1: Reproduce and classify
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
xcodebuild build -scheme <Scheme> -destination 'platform=iOS Simulator,name=<Simulator>'
|
|
34
|
+
# or, for a Swift package:
|
|
35
|
+
swift build
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Run the project's configured linter/formatter if present (`swiftlint`,
|
|
39
|
+
`swift-format lint`/`swiftformat --lint`). Read the exact error text and
|
|
40
|
+
classify it:
|
|
41
|
+
|
|
42
|
+
- **Compile error** (undefined symbol, type mismatch, wrong argument
|
|
43
|
+
count/label).
|
|
44
|
+
- **Concurrency/Sendable** (`Sendable` conformance error, an actor-
|
|
45
|
+
isolation error, a Swift 6 strict-concurrency diagnostic).
|
|
46
|
+
- **Optional-binding warning** (a `guard`/`if let` the compiler flags as
|
|
47
|
+
always-succeeding or a value it infers as never-nil).
|
|
48
|
+
- **Dependency/toolchain** (Swift Package Manager resolution failure, a
|
|
49
|
+
CocoaPods/Carthage mismatch, an Xcode/Swift toolchain version the
|
|
50
|
+
project's minimum predates).
|
|
51
|
+
- **Lint/format finding** (SwiftLint rule, swift-format/swiftformat
|
|
52
|
+
diagnostic).
|
|
53
|
+
- **Failing test** blocking a scheme that runs tests as part of build.
|
|
54
|
+
|
|
55
|
+
### Step 2: Fix by category
|
|
56
|
+
|
|
57
|
+
**Concurrency/Sendable:** read exactly what the compiler says is
|
|
58
|
+
crossing the boundary unsafely. If the type's mutable state genuinely
|
|
59
|
+
needs protecting, isolate it with an `actor` or `@MainActor`; if it is
|
|
60
|
+
genuinely immutable, conform it to `Sendable` properly (all stored
|
|
61
|
+
properties `Sendable`, or an explicit, justified conformance). Only use
|
|
62
|
+
`@unchecked Sendable` when you can state in the report exactly why the
|
|
63
|
+
type's access pattern is safe despite the compiler being unable to prove
|
|
64
|
+
it — never as a default move to clear the error.
|
|
65
|
+
|
|
66
|
+
**Optional-binding warning:** fix the actual type/control-flow issue the
|
|
67
|
+
compiler is pointing at (a value that genuinely cannot be nil should not
|
|
68
|
+
be declared `Optional`; a value that can be nil needs the `guard`/`if
|
|
69
|
+
let` the compiler is questioning). Never force-unwrap (`!`) to silence
|
|
70
|
+
the warning instead of addressing why the compiler flagged it.
|
|
71
|
+
|
|
72
|
+
**Dependency/toolchain:** for a Swift Package Manager resolution
|
|
73
|
+
failure, check `Package.resolved` against `Package.swift`'s declared
|
|
74
|
+
requirements before bumping a version by hand; for a toolchain mismatch,
|
|
75
|
+
confirm the project's actual minimum Swift/Xcode version before changing
|
|
76
|
+
`swift-tools-version` or the deployment target just to make an error
|
|
77
|
+
disappear.
|
|
78
|
+
|
|
79
|
+
**Lint/format finding:** fix the underlying issue the rule names (a real
|
|
80
|
+
force-unwrap, a genuinely too-long function, an unused variable). Never
|
|
81
|
+
add a blanket `// swiftlint:disable` covering more than the one flagged
|
|
82
|
+
line, and never disable a rule repository-wide in `.swiftlint.yml` to
|
|
83
|
+
clear one finding without discussing why the rule itself is wrong for
|
|
84
|
+
this codebase.
|
|
85
|
+
|
|
86
|
+
**Failing test:** read the failure and fix the actual regression it
|
|
87
|
+
caught; do not delete, skip (`.disabled()`/`XCTSkip` used to dodge
|
|
88
|
+
rather than genuinely skip an environment-specific case), or loosen the
|
|
89
|
+
test's assertion just to reach a green build.
|
|
90
|
+
|
|
91
|
+
### Step 3: Verify
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
xcodebuild build -scheme <Scheme> -destination 'platform=iOS Simulator,name=<Simulator>'
|
|
95
|
+
xcodebuild test -scheme <Scheme> -destination 'platform=iOS Simulator,name=<Simulator>'
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Re-run the project's linter/formatter if it was part of the original
|
|
99
|
+
failure. All must exit 0 before reporting done.
|
|
100
|
+
|
|
101
|
+
### Step 4: Report
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
Fixed: Sendable conformance error in OrderCache
|
|
105
|
+
- Root cause: OrderCache held mutable state accessed from two tasks
|
|
106
|
+
with no isolation; converted it to an actor
|
|
107
|
+
- xcodebuild build/test both pass, swiftlint clean
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
State the root cause in one sentence, not just "fixed the error."
|
|
111
|
+
|
|
112
|
+
## Rules
|
|
113
|
+
|
|
114
|
+
- Find and fix the smallest change that addresses the actual root cause
|
|
115
|
+
— never widen a fix beyond what the failure requires.
|
|
116
|
+
- NEVER mark a type `@unchecked Sendable` to silence a strict-
|
|
117
|
+
concurrency error without stating, in the report, exactly why its
|
|
118
|
+
mutable state is safe.
|
|
119
|
+
- NEVER force-unwrap (`!`) or force-try (`try!`) just to clear a
|
|
120
|
+
compiler warning about an optional or a throwing call.
|
|
121
|
+
- NEVER add a blanket `// swiftlint:disable` (or disable a rule
|
|
122
|
+
repository-wide) to make one finding disappear.
|
|
123
|
+
- NEVER delete or skip a failing test to reach a green build.
|
|
124
|
+
|
|
125
|
+
## Red Flags
|
|
126
|
+
|
|
127
|
+
| Rationalization | Why it is wrong |
|
|
128
|
+
|---|---|
|
|
129
|
+
| "I'll mark this `@unchecked Sendable`, it's the fastest way to clear the error" | Trades a compile-time data-race guarantee for an unchecked promise; audit the actual access pattern or add real isolation instead |
|
|
130
|
+
| "I'll force-unwrap this to silence the compiler's optional-binding warning" | The warning exists because the compiler cannot prove the value is non-nil; force-unwrapping does not fix that, it just moves the failure to a runtime crash |
|
|
131
|
+
| "I'll add `// swiftlint:disable force_unwrapping` for this whole file" | Silences every future violation in the file, not just the one the fix addressed — scope any disable as narrowly as the actual justified exception |
|
|
132
|
+
| "This test is flaky, I'll mark it `.disabled()` for now" | Hides a real regression or a genuine flake worth fixing (non-deterministic async wait, shared state) instead of fixing the underlying cause |
|
|
133
|
+
|
|
134
|
+
## Verification
|
|
135
|
+
|
|
136
|
+
Do not report the fix done until all of the following hold:
|
|
137
|
+
|
|
138
|
+
- `xcodebuild build`/`swift build` and `xcodebuild test`/`swift test`
|
|
139
|
+
both exit 0.
|
|
140
|
+
- The project's linter/formatter (if configured) exits 0.
|
|
141
|
+
- The change is the smallest one that addresses the stated root cause —
|
|
142
|
+
no unrelated files touched.
|
|
143
|
+
- The report states the root cause in one sentence, not just "build now
|
|
144
|
+
passes."
|