Swift project standard
Skill VoldemortGin/AI-Coding-Skill-Bible/swift-project-standard
Project-standard skills for AI coding agents — engineering charters (Rust / Swift / Python / TypeScript / Kotlin + polyglot core) that make agent output trustworthy by construction.
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.
One thing 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.
What its author says it does
Copied from the file, not written here
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.
SKILL.md
13.7 KB, 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).