Swift project standard
Skill VoldemortGin/AI-Coding-Skill-Bible/swift-project-standard
Enforce a strict, model-agnostic, AI-navigable Swift project standard: Swift 6 language mode + complete strict concurrency + warnings-as-errors as a one-command zero-warning gate, the escape hatches (`!` force-unwrap / `try!` / `as!` / `@unchecked Sendable` / implicitly-unwrapped optionals) shut, `Codable` + newtypes at boundaries (parse don't validate), an SPM package with target-per-domain deep structure, a zero-SDK `Domain` target behind provider protocol seams with a default `MockProvider`, `os.Logger` with privacy interpolation, a strongly-typed `AppConfig`, a swift-format + SwiftLint double gate, a CLAUDE.md in every target, and scaffold/conformance scripts. Use whenever starting or scaffolding a Swift or SwiftUI project; setting up Package.swift / SwiftLint / swift-format / CI; deciding module or target structure; adding an LLM / embedding / vector-store dependency; wiring providers or adapters; or checking that an existing Swift project conforms. Apply it even when the user only says "start a Swift project", "set up the structure", "add an LLM", or "wire up CI" without naming the standard.From its SKILL.md
npx -y skills add VoldemortGin/AI-Coding-Skill-Bible --skill swift-project-standardAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
- runs commandsInstructs the agent to run 6 commands, including `python scripts/scaffold.py <package_name> --target <dir> --domains ingestion retrieval generation agents` and 5 more.
SKILL.md
13.7 KB, ~3.1k tokens by cl100k_base, as published. Nobody here has run it
Swift Project Standard
This skill is the guiding standard for any Swift work. Apply it by default; don't wait to be asked.
Its spine: trust is placed not in the model, but in the machine-checkable code that constrains it. In Swift, like Rust, most of that scaffold is the compiler — types, optionality, exhaustiveness, and (in Swift 6) data-race safety are enforced at compile time with no type erasure, so there's no runtime type-checker to bolt on (unlike the Python sibling's beartype). The job here is to (a) keep the few escape hatches shut and turn on Swift 6 complete strict concurrency, (b) push every external dependency behind a protocol so the model is hot-swappable, (c) organize deep and name-navigable, and (d) mechanize the implicit knowledge a human would otherwise hold — via a zero-warning gate, a per-target contract, and drift guards. The agent's output ceiling equals the tightness of that loop.
Baseline: Swift 6.x (// swift-tools-version: 6.0, swiftLanguageModes: [.v6]), complete strict concurrency, warnings-as-errors at the gate, swift-format (toolchain-bundled), SwiftLint (community, --strict), Codable + newtypes, os.Logger, a Codable AppConfig, and XcodeGen for a thin app shell when there's a UI.
This standard is the project-level engineering charter (the gate + architecture conventions). For how to write the implementation inside a target, it delegates to the existing implementation skills — swift-concurrency, swift-error-handling, swift-protocols, swift-data-flow, swift-testing, swift-networking, swift-persistence, and the rest — rather than restating them. Cite them where relevant; don't duplicate their content.
When starting a new project
python scripts/scaffold.py <package_name> --target <dir> --domains ingestion retrieval generation agents
This mirrors assets/templates/ into an SPM package (Package.swift + Kernel/Domain/Adapters targets + an App executable + a domain target per --domains + a CLAUDE.md per target + ci.sh + ADR), substitutes __PACKAGE__, and registers each domain target in Package.swift's targets/products. Module names (import Kernel, import Domain, …) are project-independent — only the package name, the executable name, and CLAUDE.md headers carry the literal name, so import paths never depend on it. When adapting an existing repo, copy from assets/templates/ by hand.
Then: swift build, and ./ci.sh (or make check) to verify. swift format ships with the toolchain; swiftlint needs brew install swiftlint once. Pass --app to run xcodegen and emit a thin .xcodeproj app shell that links the AppCore library product.
When working on existing code
Apply the rules below, keep new code strict, and verify structural invariants:
python scripts/check_conformance.py <project_root>
It parses the manifest via swift package dump-package (JSON, cleaner than regex) and checks the mechanically enforceable invariants (SPM present, tools ≥ 6 + v6 language mode, target-per-domain, the Domain target has zero vendor-SDK dependencies, warnings-as-errors wired in ci.sh or the manifest, .swift-format + .swiftlint.yml present, ci.sh chaining swift-format/swiftlint/build/test, a CLAUDE.md in every Sources/<Target>). Everything else is enforced by the gate and by applying the standard.
The non-negotiables
Full rationale in references/standard.md.
-
The compiler is the static guarantee; turn on Swift 6 and keep the escape hatches shut.
// swift-tools-version: 6.0+swiftLanguageModes: [.v6]gives complete strict concurrency — compile-time data-race safety, the Swift analog of Rust'sSend/Sync. No bolted-on runtime type check — Swift doesn't need one. The escape hatches are banned: no!force-unwrap, notry!, noas!, no implicitly-unwrapped optionals, no@unchecked Sendable. SwiftLint enforcesforce_unwrapping/force_try/force_cast. A site that genuinely needs one takes the controlled exit:// swiftlint:disable:next force_unwrapping — reason(explicit, auditable, greppable — the Swift analog of a justified# type: ignore), andnonisolated(unsafe)/@unchecked Sendableonly with a// SAFETY:note and a CLAUDE.md entry. -
No silent failures:
throws+ typedthrows(E)+ a concreteError, not!/try!sprinkled around. Recoverable failures arethrows; Swift 6 typed throws (throws(ProviderError)) makes the error set part of the signature, the analog of Rust'sResult<T, E>. Vendor/network/timeout errors normalize at the adapter boundary intoProviderError; program bugs (precondition violations) trap or propagate — never swallowed into a fallback. Seeswift-error-handling. -
Boundaries: parse, don't validate —
Codable+ newtypes. Everything crossing a boundary (config, LLM output, tool results, files) decodes into a strongly-typed value viaCodable; encode constraints in newtypes with a validating init (init(_:) throwsor failableinit?) so invalid states are unrepresentable rather than runtime-checked. This is the Swift mirror of Python's pydantic and Rust'sserde+ newtype. -
Model-agnostic: every external AI dependency behind a protocol in
Domain; SDKs only inAdapters. TheDomaintarget has zero SDK dependencies (the checker parses the resolved manifest to enforce this); protocols stay usable asany LLMexistentials. Real SDKs live in a separate target gated by an SPM package trait (.product(..., condition: .when(traits: ...))) — the precise analog of Cargo features — and normalize toProviderError. The composition root (App) is the one place that selects an impl by config and injects it (any LLM, the analog ofBox<dyn>). A deterministic MockProvider is the default (not a test stub) so the executable, tests, and CI run offline with no SDK or key. Seeswift-protocolsfor theany/some/protocol-witness tradeoff. -
Completion = one zero-warning gate, the agent's only correctness judge.
./ci.shrunsswift format lint --strict→swiftlint --strict→swift build -Xswiftc -warnings-as-errors→swift test(offline, Mock default, smoke + conformance), underset -euo pipefail; an app scenario appendsxcodebuild ... SWIFT_TREAT_WARNINGS_AS_ERRORS=YES SWIFT_STRICT_CONCURRENCY=complete. Run it after every change; fix until green; never "looks fine, commit". Pin it with a pre-push hook; CI mirrors it. -
Two lint gates, both
--strict.swift-formatowns formatting (thecargo fmtanalog);SwiftLintowns lint and the escape-hatch bans (theclippyanalog). They are complementary, not redundant — keep both, both strict, both in the gate.
Structure: SPM package, target-per-domain
The depth comes from the package (multiple targets), not from one target with deep folders. An SPM target ≈ a Cargo crate: target-level dependency isolation plus a resolvable Package.swift. "Fix the reranker" should resolve to Sources/Retrieval/... with no search.
- A target per bounded context (
Sources/<Domain>/), plus the fixed infra targets and a thin executable:Kernel— cross-cutting infra:Config.swift(CodableAppConfig: Sendable; defaults <configs/settings.json< envAPP_*with__nesting),Logging.swift(os.Logger; anenum Logfactory;logProvenance(...); privacy discipline),Prompts.swift(Bundle.moduleresources + strict rendering). NamedKernel, notCore. Zero external deps.Domain— ports (protocols) + models + boundary errors; zero SDK deps.Adapters— protocol impls;MockProvideris the default; real SDKs in a trait-gated sibling target.- domain targets (
Retrieval/Generation/…) depend onDomain+Kernel, never onAdaptersor SDKs. App— the executable and composition root; wires concrete adapters by config. The Xcode app target plays this role in a UI project and links theAppCorelibrary product.
- Dependency direction: domain target →
Domain+Kernel;Adapters→Domain+Kernel;App→ everything.Domainis zero-SDK. - A
CLAUDE.mdin every target (the layered-context rule applied per directory): the root one is a routing table (hard constraints + where to look); each target's states its responsibility, dependency direction, and local contract (Domain's "zero SDK",Adapters' "errors normalize",Kernel's "payloads never logged"). The checker requires one per target. - Go deep: split a target into submodules by sub-capability; names map to paths.
Navigability
Naming-as-path is to navigation what types are to interface contracts — and SPM lifts it to the target level. Group by capability, not by Models//Utils/. Nest until leaf files have a single clear responsibility. A name should resolve to a path with no search.
Principles for AI-touching code (advisory)
Beyond the type system — for any code where a model produces output. Upper-level discipline; not all mechanically checkable.
- Constrain, don't ask. Push non-negotiable properties (no fabrication, must-cite, no privilege escalation) into deterministic control flow so the model physically cannot violate them — don't rely on the prompt. Synthesize answers from typed values in code; discard model prose on the critical path.
- Narrow the emission surface. Make the model pick from a typed
enumof options or call tools returning a tri-state result; take final values from tool results, not free-form model text. A Swiftenum(with associated values, exhaustively matched) is the natural controlled surface. - Guardrails are deterministic, independent, never pluggable. Intent parsing can be swapped; safety decisions are deterministic code re-evaluated from raw input, not trusting a pluggable component's output.
Driving AI on big work (advisory)
Treat AI as supervisable labor, not unsupervised autopilot.
- Decision-first: a numbered, immutable ADR (
docs/adr/) before coding — context + chosen option + rejected alternatives and why. Rejected-reasons stop it re-walking excluded paths. - TDD red-light first; tests are the immutable spec. Write the failing test (Swift Testing
@Testor XCTest), then implement to green; never weaken a test to pass. Seeswift-testing. - Numbered steps, each independently green; one commit per step through the full gate. No giant diffs.
- Adversarial independent review. After writing, run a separate, hostile, multi-perspective review prioritizing what tests can't cover (diagrams, docs, tradeoffs).
Scale to project size
The target split, per-target CLAUDE.md, ADRs, and provider seam are real overhead — overkill for a 200-line CLI. Present them as triggered, scalable patterns ("the moment you call an LLM/embedding/vector store, put it behind a protocol in Domain"; "split a target out the moment it takes a second responsibility"), not blanket mandates. A single-target package with files + the two lint gates + the zero-warning gate is a perfectly good small-scale instantiation.
Scale along two axes, not one — size and domain. The standard has a universal spine that holds for any Swift project whether or not it touches AI: Swift 6 + complete strict concurrency + warnings-as-errors, the zero-warning gate, throws/typed throws with the escape hatches shut (!/try!/as!), Codable + newtypes at boundaries, SPM target-per-domain + deep naming-as-path, os.Logger, a strongly-typed AppConfig, swift-format + SwiftLint. The rest is an AI-triggered layer that only switches on once the project actually calls an LLM / embedding / vector store: the Domain protocol seam + zero-SDK domain target, MockProvider-as-default, bundled prompts + strict rendering, logProvenance, and the constrain-don't-ask discipline. A pure library / CLI / app that never calls a model should take the spine in full and skip the AI layer outright — bolting MockProvider or prompt-embedding onto such a project is cargo-culting, not conformance.
Resources
references/standard.md— the full standard with rationale and complete code for every target (read for the why, edge cases, or exact module contents).assets/templates/— exact SPM boilerplate;__PACKAGE__is the only placeholder. IncludesKernel/Domain/Adapters/App,.swift-format,.swiftlint.yml,ci.sh,project.yml(XcodeGen), a per-target CLAUDE.md,configs/settings.json, and smoke/conformance tests.scripts/scaffold.py— generate a conforming package.scripts/check_conformance.py— verify structural invariants (incl.Domain-zero-SDK and per-target CLAUDE.md).
What ships with it: 30 files
86.4 KB alongside SKILL.md, 3 of them executable
assets/
- templates/ci.shruns834 B
- templates/CLAUDE.md1.9 KB
- templates/configs/settings.json98 B
- templates/docs/adr/0001-record-architecture-decisions.md421 B
- templates/.env.example517 B
- templates/.gitignore154 B
- templates/Makefile826 B
- templates/Package.swift4.5 KB
- templates/project.yml1.0 KB
- templates/README.md2.2 KB
- templates/Sources/Adapters/CLAUDE.md799 B
- templates/Sources/Adapters/MockProvider.swift1023 B
- templates/Sources/App/CLAUDE.md563 B
- templates/Sources/App/main.swift2.2 KB
- templates/Sources/Domain/CLAUDE.md730 B
- templates/Sources/Domain/Errors.swift998 B
- templates/Sources/Domain/Models.swift1.6 KB
- templates/Sources/Domain/Ports.swift953 B
- templates/Sources/Kernel/CLAUDE.md696 B
- templates/Sources/Kernel/Config.swift2.8 KB
- templates/Sources/Kernel/Logging.swift2.4 KB
- templates/Sources/Kernel/Prompts.swift3.2 KB
- templates/Sources/Kernel/Resources/Prompts/rag/answer.md191 B
- templates/.swift-format1.9 KB
- templates/.swiftlint.yml1.3 KB
- templates/Tests/ConformanceTests/ProviderConformanceTests.swift2.5 KB
- templates/Tests/SmokeTests/SmokeTests.swift2.4 KB
references/
- standard.md27.8 KB
scripts/
- check_conformance.pyruns9.6 KB
- scaffold.pyruns10.6 KB