Skip to content
Back to skills

Swift Audit

ASecurity

Audit planning docs, story files, and Swift source code against the Swift/SwiftUI guidance in docs/setup/swift/. Produces a remediation story file with one AC per finding. Use when the user says "swift audit", "audit swift", "audit codebase", or "check swift patterns".

  • 2 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 19, 2026
ai-agentsgoswiftbashtestingdatabasesecurity

Security analysis

A100/100

Scanned September 23, 2026

npx -y skills add rterakedis/leanwheel-skills --skill swift-audit --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Swift Audit?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Swift Audit
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/rterakedis-swift-audit/badge)](https://www.skillsdirectory.com/skills/rterakedis-swift-audit)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: swift-audit
description: Audit planning docs, story files, and Swift source code against the Swift/SwiftUI guidance in docs/setup/swift/. Produces a remediation story file with one AC per finding. Use when the user says "swift audit", "audit swift", "audit codebase", or "check swift patterns".
---

# Swift Audit Skill

**Goal:** Scan the entire project — planning docs, story files, and Swift source code — against the guidance in `docs/setup/swift/`. Collate all findings into a single remediation story file that `/dev-story` can implement directly.

**Requires:** `docs/setup/swift/` must exist. If absent, tell the user to run `/setup` (for a new project) or `/refresh-swift` (to populate an existing project's swift docs).

---

## Step 1 — Load Guidance

Read all files present in `docs/setup/swift/`:

```bash
ls docs/setup/swift/
```

Read each file completely. These are the standards everything will be measured against. Pay particular attention to `anti-patterns.md` — it contains the hard rejection list used for code scanning.

---

## Step 2 — Inventory the Project

Run these commands to understand what exists:

```bash
# Planning docs
ls docs/prd.md docs/architecture.md docs/epics.md 2>/dev/null

# Story files
find docs/epics -name "*.md" | sort

# Swift source files (exclude build artifacts and dependencies)
find . -name "*.swift" \
  ! -path "*/DerivedData/*" \
  ! -path "*/.build/*" \
  ! -path "*/Pods/*" \
  ! -path "*/Packages/*" \
  | sort

# Existing audit files (to avoid re-auditing completed work)
find docs/epics -name "swift-audit-*.md" | sort
```

Note what is present. If no Swift source files exist yet, skip Step 4 and note "no source to audit" in the report.

---

## Step 3 — Audit Planning Docs

Read each planning doc that exists and reason against the guidance. For each doc, look for:

### `docs/architecture.md`

| What to check | Rejection signal |
|---|---|
| Data layer / state management design | References MVVM, ViewModel classes, `ObservableObject`, `@Published` as the prescribed pattern |
| Dependency injection / service access | Prescribes init-parameter injection of services instead of `@Environment` |
| Concurrency / threading design | Prescribes `DispatchQueue`, `OperationQueue`, or Combine pipelines for new code |
| Project structure | Type-based structure (`/Models`, `/Views`, `/Services`) rather than feature-based |
| Navigation design | References `NavigationView` (deprecated) |
| Testing strategy | Prescribes XCTest classes rather than Swift Testing |
| iPadOS navigation (if applicable) | Prescribes `NavigationStack` alone as iPad root instead of `NavigationSplitView` |
| macOS settings (if applicable) | Prescribes a sheet or modal for app settings instead of `Settings` scene |

### `docs/prd.md`

| What to check | Rejection signal |
|---|---|
| Technical requirements or constraints | Mandates a specific architecture that conflicts with current guidance |
| Non-functional requirements | References threading or concurrency approaches that are now anti-patterns |

### `docs/epics.md`

| What to check | Rejection signal |
|---|---|
| Epic or story descriptions | Reference banned patterns in task or acceptance criteria language |
| Technical approach notes | Prescribe ViewModel files, Combine usage, or legacy observation in task descriptions |

### Story files (`docs/epics/*.md`, excluding `swift-audit-*.md`)

For each story file, check:
- Tasks that instruct creating `*ViewModel.swift` files
- Dev Notes that reference `ObservableObject`, `@Published`, `@StateObject`, `@ObservedObject`, `@EnvironmentObject`
- Acceptance Criteria that imply a banned pattern ("the ViewModel must expose…")
- Dev Notes that prescribe `DispatchQueue` or `import Combine`

---

## Step 4 — Audit Swift Source Code

Run grep commands to find hard violations in `.swift` files. For each match, record the file path and line number.

```bash
# Banned observation patterns
grep -rn "ObservableObject\|@Published\|@StateObject\|@ObservedObject\|@EnvironmentObject" \
  --include="*.swift" \
  --exclude-dir="{DerivedData,.build,Pods,Packages}" .

# DispatchQueue in non-legacy files
grep -rn "DispatchQueue" \
  --include="*.swift" \
  --exclude-dir="{DerivedData,.build,Pods,Packages}" .

# Combine imports in new files
grep -rn "^import Combine" \
  --include="*.swift" \
  --exclude-dir="{DerivedData,.build,Pods,Packages}" .

# onAppear + Task leak pattern
grep -rn "onAppear" \
  --include="*.swift" \
  --exclude-dir="{DerivedData,.build,Pods,Packages}" . \
  | grep -v "// ✅"

# Deprecated NavigationView
grep -rn "NavigationView" \
  --include="*.swift" \
  --exclude-dir="{DerivedData,.build,Pods,Packages}" .

# ViewModel files
find . -name "*ViewModel.swift" \
  ! -path "*/DerivedData/*" \
  ! -path "*/.build/*" \
  ! -path "*/Pods/*" \
  ! -path "*/Packages/*"

# Array conversion of FetchedResults
grep -rn "Array(.*[Ff]etch" \
  --include="*.swift" \
  --exclude-dir="{DerivedData,.build,Pods,Packages}" .

# UIDevice idiom check (use size classes instead)
grep -rn "userInterfaceIdiom" \
  --include="*.swift" \
  --exclude-dir="{DerivedData,.build,Pods,Packages}" .

# Screen-dimension layout math (breaks on iPhone Duo / Split View — use container bounds)
grep -rn "UIScreen.main\|UIScreen.screens" \
  --include="*.swift" \
  --exclude-dir="{DerivedData,.build,Pods,Packages}" .

# Fixed-width frames (size relative to the container, not to device dimensions)
grep -rn "\.frame(width: [0-9]" \
  --include="*.swift" \
  --exclude-dir="{DerivedData,.build,Pods,Packages}" .

# Hand-rolled bars — opt the app out of vertical bar presentation on iPhone Duo
grep -rn "UIToolbar(\|UINavigationBar(\|UITabBar(" \
  --include="*.swift" \
  --exclude-dir="{DerivedData,.build,Pods,Packages}" .

# Index-based ForEach on mutable data
grep -rn "ForEach(0\.\.<\|ForEach(.*\.indices" \
  --include="*.swift" \
  --exclude-dir="{DerivedData,.build,Pods,Packages}" .

# Old TabView pattern (tabItem without Tab wrapper)
grep -rn "\.tabItem" \
  --include="*.swift" \
  --exclude-dir="{DerivedData,.build,Pods,Packages}" .

# XCTest in new test files
grep -rn "^import XCTest\|XCTestCase" \
  --include="*.swift" \
  --exclude-dir="{DerivedData,.build,Pods,Packages}" .
```

**Design token compliance (if `docs/ux/DESIGN.md` exists):** read its frontmatter token block, then grep for hardcoded colors that bypass it:

```bash
# Hardcoded colors in views (should come from the design token layer / asset catalog)
grep -rn "Color(red:\|Color(hex:\|Color(#colorLiteral\|UIColor(red:" \
  --include="*.swift" \
  --exclude-dir="{DerivedData,.build,Pods,Packages}" .
```

Flag each hit where a DESIGN.md token covers the value (MEDIUM). Values with no corresponding token are a DESIGN.md gap finding rather than a code finding.

For each `onAppear` hit, read surrounding lines (±5) to determine if it contains a `Task {` — only flag if it does. For `DispatchQueue` hits, read surrounding lines to determine if the file is a legacy wrapper (acceptable) or new code (flagged). Use judgment — do not flag things that are clearly intentional compatibility shims with a comment explaining why.

**Automation contract (only if `docs/setup/swift/testability.md` exists):**

```bash
# Interactive elements — compare against identifier count to size the gap
grep -rnE "Button\(|TextField\(|Toggle\(|Picker\(|NavigationLink\(|\.onTapGesture" \
  --include="*.swift" --exclude-dir="{DerivedData,.build,Pods,Packages}" . | wc -l
grep -rn "accessibilityIdentifier" \
  --include="*.swift" --exclude-dir="{DerivedData,.build,Pods,Packages}" . | wc -l

# Identifiers that break the {feature}-{element}-{role} kebab-case convention
grep -rhoE 'accessibilityIdentifier\("[^"]+"\)' \
  --include="*.swift" --exclude-dir="{DerivedData,.build,Pods,Packages}" . \
  | grep -vE 'accessibilityIdentifier\("[a-z0-9]+(-[a-z0-9\\()._]+)+"\)'
```

Flag non-kebab or label-shaped identifiers (`"Save Button"`, `"btn1"`) as MEDIUM `[CODE]` findings — they defeat the locator contract as surely as having none.

---

## Step 4b — Testability Foundation Check (brownfield retrofit)

Three greps decide whether this fires at all — zero cost when the foundation is present:

```bash
grep -rl "enum SeedScenario" --include="*.swift" .              # seed registry
grep -rl '"--seed"\|"--uitest"' --include="*.swift" .           # launch-arg contract
grep -rl '"--route"' --include="*.swift" .                      # unattended route delivery
xcodebuild -list -json 2>/dev/null | grep -i "UITests"          # UI test target
grep -rl "onOpenURL" --include="*.swift" .                      # route table
```

If **all five** are present, record "testability foundation present" and skip to Step 5.

`--route` is checked separately from `--seed`/`--uitest` because a project can have a
complete `.onOpenURL` route table and still be undrivable: on iOS 26 an externally
opened custom-scheme URL raises a system confirmation alert, so unattended runs need
the route delivered as a launch argument. `onOpenURL` present + `"--route"` absent is a
real Stage 0 gap, not a formality — see `testability.md` (*Deep-Link Routes*).

If any are missing, the project cannot be driven deterministically — but do **not** emit one big-bang retrofit AC. Nobody runs a story that says "add identifiers to 60 views." Emit a **staged** remediation instead, written into the Step 6 story as separate task groups:

- **Stage 0 — Foundation (blocking).** Whatever of the five is missing: `SeedScenario` registry with `.empty`/`.typical`/`.edge`, the `--seed`/`--uitest`/`--reset` launch-argument contract with in-memory store isolation, a URL scheme + `.onOpenURL` route table **plus `--route` dispatching into that same table**, one UI test target with the `launch(seed:route:)` / `step(_:_:)` helpers, and 2–4 flows. This is the only stage that blocks; everything else builds on it. On a Core Data + CloudKit project, explicitly verify CloudKit is disabled on seeded/`--uitest` runs (see `testability.md`) — seeding into a CloudKit-backed store writes fixture rows to the developer's real private database.
- **Stages 1..N — Identifiers + routes, highest-traffic screens first.** One stage per screen or small screen group, each independently shippable. Rank screens by inbound-reference count — a deterministic zero-token traffic proxy:

```bash
# For each view file, how many places navigate to it
for f in $(find . -name "*View.swift" ! -path "*/DerivedData/*" ! -path "*/.build/*"); do
  n=$(basename "$f" .swift)
  printf "%s %s\n" "$(grep -rlE "NavigationLink|\.sheet|\.fullScreenCover|TabView" --include="*.swift" . \
    | xargs grep -l "\b$n\b" 2>/dev/null | wc -l | tr -d ' ')" "$n"
done | sort -rn | head -20
```

Report the ranking in Dev Notes so the ordering is auditable rather than asserted. Each stage's AC: every interactive element and dynamic row on those screens gets a Design-Contract-style identifier, plus the screen's deep-link route. State the honest total in the report (`{k} of {total} screens covered by this remediation`) — never imply full coverage from a partial retrofit.

**Over-engineering / deletion pass:** scan touched source for complexity that shouldn't exist, one tagged line per finding — `delete:` (dead code / speculative feature), `stdlib:` (hand-rolled thing the standard library ships), `native:` (a dependency or code doing what the SDK already does — e.g. a custom formatter over `Foundation`), `yagni:` (an abstraction with one implementation, a protocol with one conformer, a config nobody sets), `shrink:` (same logic, fewer lines). Never flag a single smoke test / assert, validation, security, or accessibility for removal. These are `[CODE]` findings, usually LOW/MEDIUM.

---

## Step 5 — Triage Findings

Assign each finding a scope tag and severity:

**Scope tags:**
- `[DOC-ARCH]` — finding in `architecture.md`
- `[DOC-PRD]` — finding in `prd.md`
- `[DOC-EPICS]` — finding in `epics.md`
- `[STORY]` — finding in a story file
- `[CODE]` — finding in Swift source
- `[TESTABILITY]` — missing foundation / identifiers / routes (Step 4b). Stage 0 is HIGH; per-screen stages are MEDIUM.

**Severity:**
- `HIGH` — active anti-pattern that will produce wrong code when implemented (e.g., architecture prescribes `ObservableObject`; source file uses banned pattern in production path)
- `MEDIUM` — pattern that diverges from guidance but may not cause immediate breakage (e.g., `NavigationView` still compiles; XCTest still works)
- `LOW` — style or convention gap (e.g., type-based project structure that could be refactored; index-based `ForEach` on a list that currently never mutates)

Deduplicate: if the same file has five `@Published` properties, that is one finding with a note that multiple instances exist.

If zero findings across all scopes: skip Step 6, report "Clean — no violations found against current guidance." and stop.

---

## Step 6 — Write Remediation Story

Write to `docs/maintainer/swift-audit-{YYYY-MM-DD}.md`. Use today's date. Create `docs/maintainer/` if it does not exist.

### Story File Format

```markdown
---
Status: ready-for-dev
Type: remediation
Generated: {today's date}
---

# Swift Audit Remediation — {today's date}

**Source:** `/swift-audit` run against guidance in `docs/setup/swift/`
**Findings:** {N} total — {H} HIGH / {M} MEDIUM / {L} LOW

---

## Acceptance Criteria

<!-- DOC findings first, then CODE findings. HIGH severity first within each group. -->

### Planning Docs
- [ ] [HIGH][DOC-ARCH] {one-line description} — `docs/architecture.md`
- [ ] [MEDIUM][DOC-ARCH] {one-line description} — `docs/architecture.md`
- [ ] [MEDIUM][DOC-PRD] {one-line description} — `docs/prd.md`

### Story Files
- [ ] [MEDIUM][STORY] {one-line description} — `docs/epics/{filename}.md`

### Swift Source
- [ ] [HIGH][CODE] {one-line description} — `{File.swift}:{line}`
- [ ] [MEDIUM][CODE] {one-line description} — `{File.swift}:{line}`

---

## Tasks

### 1. Update Planning Docs
- [ ] Revise `architecture.md` to replace banned patterns with current guidance
- [ ] Revise `prd.md` where technical constraints conflict with current guidance
- [ ] Revise `epics.md` story/task language that references anti-patterns

### 2. Update Story Files
- [ ] Update story Dev Notes and task descriptions that prescribe banned patterns

### 3. Fix Swift Source
- [ ] Resolve all HIGH code findings before any MEDIUM/LOW
- [ ] Run the project's test suite after each batch of fixes to confirm no regressions

---

## Dev Notes

### Reference
Read all files in `docs/setup/swift/` before implementing any fix. The guidance files
are the source of truth for what each banned pattern should be replaced with.

### Finding Details

#### DOC-ARCH findings
{For each: what was found verbatim, file location, what it should say instead}

#### DOC-PRD findings
{same}

#### DOC-EPICS findings
{same}

#### STORY findings
{same — include story file name and the specific AC/task/Dev Note line}

#### CODE findings
{For each: file:line, the offending code snippet (1–3 lines), the replacement pattern from guidance}

### Notes
- Do not modify story files listed here if their Status is `in-progress` or `done` —
  log a separate note in Completion Notes instead.
- For CODE findings in files not yet implemented (stubs or placeholders), update the
  stub rather than writing the fix inline.
```

---

## Step 7 — Report to User

After writing the story file:

1. State the story file path.
2. Print the finding summary table:

| Scope | HIGH | MEDIUM | LOW | Total |
|---|---|---|---|---|
| DOC-ARCH | | | | |
| DOC-PRD | | | | |
| DOC-EPICS | | | | |
| STORY | | | | |
| CODE | | | | |
| TESTABILITY | | | | |

3. Say: "Run `/dev-story docs/maintainer/swift-audit-{date}.md` to implement all fixes."

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.