Scaffold the repo side of Xcode Cloud for an XcodeGen / Tuist Swift project - generate ci_scripts/ci_post_clone.sh (regen + guarded CI_BUILD_NUMBER stamping), propose a unit-test-only CI scheme, print the exact App Store Connect workflow checklist, and optionally install an opt-in .githooks/pre-push gate. Shows diffs and confirms before writing.
Scanned 9/6/2026
Install to Claude Code
npx -y skills add charlesjones-dev/claude-code-plugins-dev --skill swift-ci-scaffold --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Swift Ci Scaffold?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/charlesjones-dev-swift-ci-scaffold)More formats (shields.io, HTML) on the badges page.
---
name: swift-ci-scaffold
description: "Scaffold the repo side of Xcode Cloud for an XcodeGen / Tuist Swift project - generate ci_scripts/ci_post_clone.sh (regen + guarded CI_BUILD_NUMBER stamping), propose a unit-test-only CI scheme, print the exact App Store Connect workflow checklist, and optionally install an opt-in .githooks/pre-push gate. Shows diffs and confirms before writing."
argument-hint: "[path] [--hook]"
allowed-tools: [Bash, Read, Glob, Grep, Write, Edit, AskUserQuestion]
disable-model-invocation: true
---
# Swift CI Scaffolder
You prepare the **repo side** of Xcode Cloud for a generator-based Swift project, and you fix the two build-system blockers that `/swift-preflight` flags: a missing/weak `ci_post_clone.sh` and a static build number. You also optionally install a local pre-push gate.
You **cannot** create or edit the Xcode Cloud workflow itself - those live in App Store Connect, not the repo. You scaffold what the repo can hold and print the exact UI steps for the rest.
## Rules
- **Non-destructive.** Probe first, propose a diff, confirm, then write. Never overwrite an existing `ci_post_clone.sh`, `project.yml`, or hook without showing the change and getting a yes.
- **Preserve the source of truth.** On XcodeGen, edits go into `project.yml`, never the pbxproj. Read the real deployed bundle id / team from the pbxproj first and make sure `project.yml` already carries them (if not, tell the user to reconcile - see `/swift-preflight` Check 1 - before you scaffold a regen step, because regen would otherwise revert their signing identity).
- **Idempotent.** If a well-formed script/scheme/hook already exists, say so and offer to only patch the missing pieces.
## Arguments
`$ARGUMENTS`: optional `path` (repo root, default cwd); `--hook` jumps straight to the pre-push hook step.
## Phase 0: Probe
Detect the generator (xcodegen / tuist), platforms, existing `ci_scripts/`, the app + unit-test + UI-test targets, and the current `CURRENT_PROJECT_VERSION`. If the project is a plain `.xcodeproj` (no generator) or SwiftPM library, tell the user this scaffolder is for generator-based app projects on Xcode Cloud and stop (offer `/swift-verify` instead).
**Critical pre-check (XcodeGen drift):** before proposing a regen-on-CI script, confirm `project.yml` already declares the real `PRODUCT_BUNDLE_IDENTIFIER` and `DEVELOPMENT_TEAM` that the pbxproj uses:
```bash
grep -E 'PRODUCT_BUNDLE_IDENTIFIER|DEVELOPMENT_TEAM' *.xcodeproj/project.pbxproj | sort -u
```
If `project.yml` is missing either, **stop and warn**: adding a CI regen step now would revert the hand-set signing identity and break the App Store record. Have the user copy the real values into `project.yml` `settings.base` first.
## Phase 1: `ci_scripts/ci_post_clone.sh`
If absent or malformed, propose this (adapt generator and stamp target):
```sh
#!/bin/sh
# Xcode Cloud runs this immediately after cloning, before it resolves
# dependencies or reads the Xcode project. The .xcodeproj is generated from
# project.yml by XcodeGen, so we regenerate here to guarantee the build matches
# the spec (no stale/hand-edited pbxproj drift). We also stamp the build number
# from CI_BUILD_NUMBER so each TestFlight upload is unique.
set -e
# Xcode Cloud starts in ci_scripts/; the repo root is one level up.
cd "$CI_PRIMARY_REPOSITORY_PATH"
echo "Installing XcodeGen..."
brew install xcodegen
# Stamp the build number into the spec BEFORE generating, so the generated
# project carries a unique CFBundleVersion. Guarded so local builds (where
# CI_BUILD_NUMBER is unset) keep the spec value.
if [ -n "$CI_BUILD_NUMBER" ]; then
echo "Setting CURRENT_PROJECT_VERSION to $CI_BUILD_NUMBER"
/usr/bin/sed -i '' \
"s/^\( *CURRENT_PROJECT_VERSION:\).*/\1 \"$CI_BUILD_NUMBER\"/" \
project.yml
fi
echo "Generating Xcode project..."
xcodegen generate
echo "ci_post_clone complete."
```
After writing, set the executable bit: `chmod +x ci_scripts/ci_post_clone.sh`.
Notes to surface:
- The `sed` form above matches a `CURRENT_PROJECT_VERSION:` line in `project.yml`. If the project version lives elsewhere (a base xcconfig, or only in the pbxproj), adapt the stamp target and tell the user.
- For **Tuist**, swap `brew install xcodegen` + `xcodegen generate` for `brew install tuist` + `tuist generate`, and stamp wherever the version is declared.
- Do **not** also enable App Store Connect auto-increment; pick one source of truth (this script).
## Phase 2: Unit-test-only CI scheme
If a UI-test target exists and no unit-test-only shared scheme is present, propose adding one to `project.yml` `schemes:` (so it generates as shared). Example shape:
```yaml
schemes:
App-CI:
build:
targets:
App: all
AppTests: [test]
test:
targets:
- AppTests # unit only — no AppUITests, so a flaky UITest can't gate a release
run:
config: Debug
archive:
config: Release
```
Explain: point the Xcode Cloud Test actions (iOS + macOS) at `App-CI`, keep the Archive action on the full `App` scheme. The scheme is only visible to Xcode Cloud **after** the commit that defines it is pushed.
## Phase 3: App Store Connect workflow checklist (print, cannot automate)
```
App Store Connect → your app → Xcode Cloud → Manage Workflows. Repo cannot hold these:
1. Start Conditions: Branch Changes on your release branch (e.g. `staging` or `main`).
2. Environment: select the Xcode version matching project.yml `xcodeVersion`.
3. Test action (iOS): scheme = App-CI, "Required to Pass". Repeat for macOS if you ship Mac.
4. Archive action: scheme = App (full), one per platform you distribute.
5. Skip Build and Analyze actions — Archive already does a full signed release build, and
the Clang analyzer adds near-zero value for pure Swift (strict concurrency + swiftlint cover it).
6. Post action: TestFlight (internal) for iOS and/or macOS.
7. First run will surface environment-only test failures that pass locally
(headless keychain writes). Fix test-side — see /swift-preflight Check 5 — not in production.
```
## Phase 4: Opt-in pre-push hook (only if user agrees, or `--hook`)
Offer, never force. If yes, write `.githooks/pre-push` (version-controlled, like a `.githooks/` convention) that runs the local gate before a push:
```sh
#!/bin/sh
# Pre-push gate: regenerate, format-check, lint strict, unit tests.
# Enable once per clone: git config core.hooksPath .githooks
set -e
exec ./scripts/verify.sh # or inline the /swift-verify pipeline if no verify.sh exists
```
If there is no `scripts/verify.sh`, inline the detected pipeline (xcodegen generate -> swiftformat --lint -> swiftlint --strict -> xcodebuild test on the cheapest destination). `chmod +x .githooks/pre-push`. Then tell the user to run `git config core.hooksPath .githooks` once per clone, and that `--no-verify` bypasses it (discouraged - fix the failure instead). Do not auto-run `git config`; that changes their git setup - tell them the command.
## Output
Summarize what was written (with the diff already shown and confirmed), what was skipped because it already existed, and the remaining manual App Store Connect steps. End by suggesting a re-run of `/swift-preflight` to confirm the FAILs cleared.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!