Feature-marker ios-workflow

iOS-specific workflow activated automatically when platform-context.json reports primary_platform=ios. Handles toolchain verification, Swift Testing patterns, XcodeBuildMCP integration, and SwiftLint.

install
source · Clone the upstream repo
git clone https://github.com/Viniciuscarvalho/Feature-marker
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Viniciuscarvalho/Feature-marker "$T" && mkdir -p ~/.claude/skills && cp -r "$T/feature-marker-dist/feature-marker/resources/spec-workflow/skills/ios-workflow" ~/.claude/skills/viniciuscarvalho-feature-marker-ios-workflow && rm -rf "$T"
manifest: feature-marker-dist/feature-marker/resources/spec-workflow/skills/ios-workflow/SKILL.md
source content

iOS Workflow

Activated automatically when the Platform Detection Engine identifies an iOS/Swift project. Never activate this manually in non-iOS projects.

Phase 1: Toolchain verification

When iOS is detected, check and report tool availability:

🍎 iOS project detected. Checking toolchain...

✅ swift        — found (Swift 6.0.3)
✅ xcodebuild   — found (Xcode 16.2)
✅ swiftlint    — found (0.57.0)
✅ XcodeBuildMCP — found (~/.claude/skills/xcodebuildmcp/SKILL.md)
⚠️ xcbeautify  — not found (optional: brew install xcbeautify)

iOS toolchain ready.

Checks to perform:

  • swift --version
    → report Swift version
  • xcodebuild -version
    → report Xcode version
  • command -v swiftlint && swiftlint version
    → report version or "not found"
  • ls ~/.claude/skills/xcodebuildmcp/SKILL.md 2>/dev/null
    → XcodeBuildMCP presence
  • command -v xcbeautify
    → optional, just report

None of these checks are blockers — missing tools generate warnings, not errors.

Phase 2: Test Framework Detection

Determine whether the project uses Swift Testing or XCTest:

# Count test files using each framework
swift_testing_count=$(grep -r "import Testing" . --include="*.swift" -l 2>/dev/null | wc -l)
xctest_count=$(grep -r "import XCTest" . --include="*.swift" -l 2>/dev/null | wc -l)
  • swift_testing_count > 0
    AND
    xctest_count == 0
    → use Swift Testing
  • xctest_count > 0
    AND
    swift_testing_count == 0
    → use XCTest (legacy)
  • Both present → use Swift Testing for new tests, keep existing XCTest tests
  • None found → default to Swift Testing (modern standard)

Report to user:

🧪 Test framework: Swift Testing (@Test, @Suite, #expect)

or:

🧪 Test framework: XCTest (existing project uses XCTest — maintaining consistency)

Phase 3: iOS Test Pipeline

Execute in this exact order:

Step 1 — Unit tests (swift test or xcodebuild)

# Option A: Swift Package Manager project
swift test --parallel

# Option B: Xcode project/workspace (if .xcodeproj/.xcworkspace found)
xcodebuild test \
  -scheme {detected_scheme} \
  -destination 'platform=iOS Simulator,name=iPhone 16' \
  -resultBundlePath .claude/feature-state/{slug}/test-results.xcresult \
  | xcbeautify 2>/dev/null || cat

Detect which to use: if

Package.swift
exists without
.xcodeproj
→ use
swift test
.

Step 2 — SwiftLint (files modified by this feature only)

Run SwiftLint only on modified files — not the whole project:

# Get files modified since the feature branch started
modified_files=$(git diff --name-only HEAD~1 -- "*.swift" 2>/dev/null)

# Run swiftlint on those files only (if swiftlint is available)
if command -v swiftlint > /dev/null 2>&1 && [ -n "$modified_files" ]; then
  swiftlint lint $modified_files --reporter json > .claude/feature-state/{slug}/swiftlint.json
  swiftlint lint $modified_files  # human-readable output
fi

If swiftlint not found: skip with

⚠️ swiftlint not found — skipping lint
.

Step 3 — XcodeBuildMCP build + simulator (optional, non-blocking)

Only if

capabilities.xcodebuildmcp_available == true
in platform-context.json:

1. /xcodebuildmcp discover_projs
   → find .xcodeproj or .xcworkspace

2. /xcodebuildmcp session_set_defaults
   → configure scheme, destination, configuration

3. /xcodebuildmcp build_run_sim
   → build + launch on iOS Simulator
   → capture build output and simulator status

Result reporting:

✅ App running on iPhone 16 Simulator (iOS 18.3)

or:

⚠️ Simulator build failed: [error summary] — tests passed, continuing to commit

XcodeBuildMCP failure is non-blocking. Unit tests passing is sufficient for the workflow.

Swift Testing patterns for iOS

When generating new tests (Test Only mode or per-task test generation), always use Swift Testing — never XCTest — unless the project exclusively uses XCTest.

Required imports and structure

import Testing
@testable import {ModuleName}

@Suite("ViewModel or UseCase Name")
struct PersonalTrainerViewModelTests {

    @Test("describe the behavior, not the method name")
    func loadsTrainerOnAppear() async throws {
        // Arrange
        let viewModel = PersonalTrainerViewModel(
            discoverTrainersUseCase: MockDiscoverTrainersUseCase(),
            featureFlagChecker: MockFeatureFlagChecker(enabled: true)
        )

        // Act
        await viewModel.onAppear()

        // Assert
        #expect(viewModel.isFeatureEnabled == true)
        #expect(viewModel.currentTrainer != nil)
    }

    @Test("does not load trainer when feature flag is disabled")
    func doesNotLoadWhenDisabled() async {
        let viewModel = PersonalTrainerViewModel(
            featureFlagChecker: MockFeatureFlagChecker(enabled: false)
        )
        await viewModel.onAppear()
        #expect(viewModel.currentTrainer == nil)
    }

    // Parameterized test
    @Test("requestConnection returns true for valid trainer IDs",
          arguments: ["trainer-alpha", "trainer-beta", "trainer-gamma"])
    func requestConnectionSucceeds(trainerId: String) async {
        let viewModel = PersonalTrainerViewModel(
            connectTrainerUseCase: MockConnectTrainerUseCase(shouldSucceed: true)
        )
        let result = await viewModel.requestConnection(trainerId: trainerId)
        #expect(result == true)
    }
}

Rules for iOS tests

  • Use
    @Suite
    to group related tests (one suite per ViewModel/UseCase/Repository)
  • Use
    @Test("behavior description")
    — describe what it does, not the method
  • Use
    #expect(condition)
    for assertions — never
    XCTAssert
  • Use
    #require(value)
    to unwrap optionals that must exist
  • Use
    throws
    for tests that can throw,
    async throws
    for async tests
  • Use
    arguments:
    for data-driven tests (replaces XCTest
    parametrize
    )
  • Use
    .tags()
    trait for categorization:
    @Test("...", .tags(.unit, .viewModel))
  • Never use
    XCTest
    ,
    XCTAssert*
    , or
    class FooTests: XCTestCase

File structure for iOS tests

Follow the existing project convention. If no convention exists, use:

{ProjectName}/
└── {ProjectName}Tests/
    ├── Domain/
    │   └── UseCases/
    │       └── {UseCase}Tests.swift     ← one file per use case
    ├── Data/
    │   ├── Repositories/
    │   │   └── {Repository}Tests.swift
    │   └── Services/
    │       └── {Service}Tests.swift
    └── Presentation/
        └── Features/
            └── {Feature}/
                └── {Feature}ViewModelTests.swift

Test file naming

  • One test file per production file:
    PersonalTrainerViewModel.swift
    PersonalTrainerViewModelTests.swift
  • Place in mirror directory under
    {ProjectName}Tests/

Test Only mode — iOS

When

--mode test-only
is invoked on an iOS project:

  1. Detect test framework (Swift Testing vs XCTest)
  2. Find Swift files without corresponding test files:
    find . -name "*.swift" \
      ! -name "*Tests.swift" \
      ! -name "*Mock*" \
      ! -path "*/Tests/*" \
      -type f 2>/dev/null
    
  3. For each file without tests, check if it's testable (ViewModels, UseCases, Repositories, Services)
  4. Present to user:
    Found 3 files without test coverage:
    - Presentation/Features/PersonalTrainer/PersonalTrainerViewModel.swift
    - Domain/UseCases/ConnectTrainerUseCase.swift
    - Data/Repositories/TrainerRepository.swift
    
    Generate Swift Testing tests for these files? [yes/no/select]
    
  5. Generate tests following Swift Testing patterns above
  6. Run
    swift test --parallel
  7. If XcodeBuildMCP available: optionally run build on simulator
  8. Report coverage delta

Configuration in platform-context.json

The iOS entry in

platform-context.json
includes:

{
  "type": "ios",
  "test_framework": "swift-testing",
  "capabilities": {
    "swiftlint_available": true,
    "xcodebuildmcp_available": true,
    "swift_testing_available": true
  }
}

This configuration is ignored completely on non-iOS projects.