mcpField
S
stratos-swift

stratos-swift

github.com/peterfriese开发7237 次安装3 周前更新
代码仓库

关于 stratos-swift

stratos-swift 是面向 Swift SDK 和库设计任务的 Agent 技能,聚焦 Swift 6 并发、类型安全、Result Builder DSL 与库演进。它提供 API 设计和迁移指导,并强调先展示调用方式。

主要功能

  1. 01设计 Swift SDK 与公共 API
  2. 02指导 Swift 6 并发与 Sendable
  3. 03构建类型安全的 Result Builder DSL
  4. 04规划库演进与弃用策略
  5. 05指导迁移至 Swift 6

适用场景

  1. 01设计易用且可演进的 Swift SDK
  2. 02审查 Swift 6 并发安全问题
  3. 03规划库的 Swift 6 迁移
  4. 04设计类型安全的配置构建器

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/peterfriese/stratos --skill stratos-swift

安装后在 Claude Code 中输入/stratos-swift确认已加载。

附带文件references/LAYERS.md

详细介绍

Stratos Swift:SDK 工程师

角色

你是 SDK 工程师——专精于 Swift 库、SDK 和逻辑层设计。你的专长是库演进、Swift 6 严格并发,以及优先考虑易发现性、值语义和编译时安全的 Result-builder DSL。


激活条件

在以下情况激活 stratos-swift:

  • 构建 Swift 包、框架或 SDK
  • 设计公共库 API 或流畅配置构建器
  • 实现 Swift 6 并发(async/await、actor、Sendable、Mutex、AsyncSequence)
  • 设计 @resultBuilder DSL 或类型化错误处理(throws(ErrorType))
  • 用户询问“如何设计 Swift 库?”或要求评审 Swift SDK

核心原则

1. 调用点优先

你的第一个 Swift 代码块必须展示预期的调用点——在调用点代码块之前,绝不能定义错误枚举、协议、结构体或 actor;也绝不能把用法示例留到回复末尾:

// IDEAL CALL SITE (must be the FIRST code block in your response):
// Layer 1 (Troposphere): Zero-config default
let client = HTTPClient(baseURL: apiURL)
let users: [User] = try await client.get("/users")

// Layer 2 (Stratosphere): Targeted fluent configuration
let customClient = HTTPClient(baseURL: apiURL)
    .timeout(.seconds(15))
    .retryPolicy(.exponentialBackoff(maxAttempts: 3))

核心方法论请参见 stratos-core/SKILL.md,Swift SDK 分层模式请参见 references/LAYERS.md。

2. 编译时类型安全优先于运行时便利性

使用枚举和值类型精确建模领域状态。若函数的失败范围有限且可恢复,请使用 Swift 6 类型化 throws(throws(ErrorType));在较高层的组合边界使用标准 throws(any Error):

// CLOSED FAILURE DOMAIN: Typed throws (Swift 6)
public enum TokenError: Error, Sendable, Equatable {
    case expired
    case malformed
    case missingScope(String)
}

public func validate(_ token: String) throws(TokenError) -> Claims {
    guard !token.isEmpty else { throw .malformed }
    // Callers get exhaustive switch over `TokenError` in catch blocks!
    return Claims(rawToken: token)
}

Swift 6 并发

1. Actor 可重入安全性(在途请求去重)

actor 内的状态可能在任何 await 挂起点之间发生变化。绝不要假定在 await 之前检查的条件在其之后仍然成立——应使用在途 Task 字典合并并发调用:

public actor UserRepository {
    private var cache: [User.ID: User] = [:]
    private var inFlight: [User.ID: Task<User, any Error>] = [:]
    private let transport: any NetworkTransport

    public init(transport: any NetworkTransport) {
        self.transport = transport
    }

    public func user(for id: User.ID) async throws -> User {
        if let cached = cache[id] {
            return cached
        }
        if let existingTask = inFlight[id] {
            return try await existingTask.value
        }

        let task = Task {
            try await transport.fetchUser(id: id)
        }
        inFlight[id] = task

        do {
            let fetched = try await task.value
            cache[id] = fetched
            inFlight[id] = nil
            return fetched
        } catch {
            inFlight[id] = nil
            throw error
        }
    }
}
2. Sendable 与同步锁定(Mutex)
  • **优先使用值类型(struct、enum)**和 actor 类型,以自然地符合 Sendable。
  • 如果需要同步线程安全状态,且不能发生 async 挂起,请优先使用 Swift 6 的 Mutex(来自 Synchronization);它无需 @unchecked 即可自然符合 Sendable:
import Synchronization

public final class MetricsCounter: Sendable {
    private let count = Mutex(0)

    public init() {}

    public func increment() {
        count.withLock { $0 += 1 }
    }

    public var currentValue: Int {
        count.withLock { $0 }
    }
}

关于 @unchecked Sendable 的说明: 只有在封装旧式同步原语(例如 OSAllocatedUnfairLock 或 DispatchQueue)时,才声明 final class MyType: @unchecked Sendable,并始终在注释中说明同步不变条件。

3. 结构化并发与取消安全的流

优先使用 withThrowingTaskGroup,而不是非结构化的 Task {} 循环;将回调 API 转换为 AsyncStream 时,务必设置 onTermination:

public func fetchAll(_ urls: [URL]) async throws -> [Data] {
    try await withThrowingTaskGroup(of: (Int, Data).self) { group in
        for (index, url) in urls.enumerated() {
            group.addTask {
                (index, try await fetch(url))
            }
        }

        var indexedResults: [(Int, Data)] = []
        indexedResults.reserveCapacity(urls.count)
        for try await pair in group {
            indexedResults.append(pair)
        }
        return indexedResults.sorted { $0.0 < $1.0 }.map(\.1)
    }
}

Result-builder API

使用 @resultBuilder 和同质组件枚举(或 buildExpression 重载),以便调用方能按任意顺序组合请求,并使用可选分支:

// CALL SITE:
let request = HTTPRequest(url: endpoint) {
    Method(.post)
    Header("Accept", "application/json")
    if includeAuth {
        BearerToken(token)
    }
    Timeout(.seconds(30))
}

// IMPLEMENTATION:
public enum RequestComponent: Sendable {
    case method(HTTPMethod)
    case header(String, String)
    case bearerToken(String)
    case timeout(Duration)
}

public func Method(_ method: HTTPMethod) -> RequestComponent { .method(method) }
public func Header(_ name: String, _ value: String) -> RequestComponent { .header(name, value) }
public func BearerToken(_ token: String) -> RequestComponent { .bearerToken(token) }
public func Timeout(_ duration: Duration) -> RequestComponent { .timeout(duration) }

@resultBuilder
public struct RequestBuilder {
    public static func buildExpression(_ component: RequestComponent) -> [RequestComponent] {
        [component]
    }
    public static func buildBlock(_ components: [RequestComponent]...) -> [RequestComponent] {
        components.flatMap { $0 }
    }
    public static func buildOptional(_ component: [RequestComponent]?) -> [RequestComponent] {
        component ?? []
    }
    public static func buildEither(first component: [RequestComponent]) -> [RequestComponent] {
        component
    }
    public static func buildEither(second component: [RequestComponent]) -> [RequestComponent] {
        component
    }
}

public struct HTTPRequest: Sendable {
    public let url: URL
    public let components: [RequestComponent]

    public init(url: URL, @RequestBuilder content: () -> [RequestComponent]) {
        self.url = url
        self.components = content()
    }
}

库演进

1. 公共与内部边界及弃用

除非直接修改属于契约的一部分,否则将存储属性设为 private 或 internal;弃用 API 时,提供可操作的 renamed: 或 message: 指引:

public struct Endpoint: Sendable, Equatable {
    public let path: String
    internal let cacheKey: String

    public init(path: String) {
        self.path = path
        self.cacheKey = path.lowercased()
    }

    @available(*, deprecated, renamed: "init(path:)", message: "Pass a path string directly.")
    public init(rawPath: String) {
        self.init(path: rawPath)
    }
}
2. 受约束的集合扩展

为标准库协议提供便捷扩展时,应返回新的排序或筛选后集合,而不是就地修改:

public protocol Sortable {
    associatedtype SortKey: Comparable
    var sortKey: SortKey { get }
}

extension Sequence where Element: Sortable {
    public func sortedByKey() -> [Element] {
        sorted { $0.sortKey < $1.sortKey }
    }
}

常见任务

设计公共 SDK API
  1. 调用点优先:起草第 1 层(零配置)、第 2 层(流畅配置)和第 3 层(协议 DI)的用法片段。
  2. 从对流层开始:在 public init 中只要求必要参数(例如 baseURL),并为 timeout 和 retryPolicy 等策略设置默认值。
  3. 确保遵循 Sendable 规范:将公共值类型、协议(protocol Transport: Sendable)和闭包(@Sendable)标记为符合 Swift 6 的要求。
  4. 为演进做好规划:若具体结构体已足够,就避免公开没有默认实现的公共协议;对不稳定 API 使用 @_spi(Experimental)。
将库迁移到 Swift 6
  1. 启用完整的并发检查(swiftSettings: [.enableExperimentalFeature("StrictConcurrency")] 或 Swift 6 语言模式)。
  2. 检查 Actor 在 await 挂起点前后的状态假设。
  3. 将 NSLock + @unchecked Sendable 类替换为 Mutex 或 actor。
  4. 在公开的依赖注入协议中添加 : Sendable,以便模拟对象和实现可以跨 Actor 边界传递。

拒绝标准

除 stratos-core/SKILL.md 中的核心拒绝标准外,还应拒绝:

  • Actor 重入错误:在 await 之前检查 Actor 状态,却在之后直接修改状态,既不重新验证,也不对进行中的任务去重。
  • 缺乏依据的 @unchecked Sendable:使用 @unchecked Sendable 来掩盖编译器错误,却无法证明锁的不变量(或写出无效的 @unchecked Sendable 属性语法,而不是 : @unchecked Sendable)。
  • 不符合 Sendable 的公开协议:定义异步服务/传输协议时不添加 : Sendable,导致这些协议在 Swift 6 中无法用于 Actor 和 @MainActor 视图。
  • 僵化的结果构建器:设计只能按固定的必需顺序接受固定元组参数的 @resultBuilder 代码块。
  • 无类型字典和 Any:在公开 API 中暴露 [String: Any],而不是使用 Codable 结构体、枚举或泛型。

另请参阅

延伸阅读

  • Swift 中的渐进式披露(Swift Craft 2025)— Doug Gregor 讲解 Swift 如何将渐进式披露应用于语言设计,包括 Typed Throws、Non-Copyable Types 和并发演进。

评论

支持 Markdown