From ios-spm
Use when the user asks to check Swift Package Manager dependency updates for an iOS Xcode project — typical triggers are "check SPM updates", "what iOS packages can be bumped", "any SPM packages outdated", "look at Package.resolved updates", "find Swift package updates". Also handles applying picked updates ("apply", "bump <package> to X.Y.Z", "update these") by editing project.pbxproj and running xcodebuild -resolvePackageDependencies, optionally verifying with a build, and opening a PR with changelog summaries.
How this skill is triggered — by the user, by Claude, or both
Slash command
/ios-spm:check-spm-updatesThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
End-to-end SPM dependency update flow for Xcode-managed projects:
End-to-end SPM dependency update flow for Xcode-managed projects:
project.pbxproj for picks, run xcodebuild -resolvePackageDependencies to refresh Package.resolved + transitive deps.xcodebuild build to confirm the project still compiles, then re-diff Package.resolved to catch any further auto-resolve the build triggered.Why this skill exists: Renovate doesn't natively manage SPM dependencies declared in .xcodeproj bundles (renovatebot/renovate#9735), and xcodebuild -resolvePackageDependencies alone respects existing pins (Swift Forums #55545). The pbxproj edit + rm Package.resolved + resolve dance is the only reliable path.
Trigger phrases (not exhaustive):
Phase 1 (discover):
Phase 2 (apply):
Phase 3 (open PR):
Phase 4 (build verify, optional):
Do NOT trigger for:
Package.resolved, project.pbxproj, or run git state-changing commands.kind = revision deps. Refuse and ask for the new SHA.# Package.resolved
find . -name "Package.resolved" -path "*.xcodeproj/*" -not -path "*/build/*" -not -path "*/DerivedData/*" | head -5
# project.pbxproj (sibling to the xcodeproj that holds Package.resolved)
find . -name "project.pbxproj" -not -path "*/build/*" | head -5
Confirm with the user if multiple matches and the right one isn't obvious from path.
JSON with a top-level pins array. Each entry looks like:
{
"identity": "<package-identity>",
"location": "https://github.com/<owner>/<repo>.git",
"state": { "revision": "<sha>", "version": "<version>" }
}
Extract (identity, location, version, revision) for every pin.
awk '/XCRemoteSwiftPackageReference/,/^[[:space:]]*\};/' path/to/project.pbxproj
Build a map: URL → { kind, version_or_revision }. URLs in pbxproj may or may not have .git suffix — normalise both sides when matching.
Spec kinds: exactVersion, upToNextMajorVersion/upToNextMinorVersion, revision, branch.
Use the GitHub releases API, not just tags. Tag-only lookup can be wrong for projects with mixed prefix schemes (e.g. a repo that has both vX.Y.Z legacy tags and unprefixed X.Y.Z modern tags — git ls-remote --sort='-v:refname' orders v… higher than digits lexicographically, so you'd get a stale legacy tag back as "latest").
# Primary: releases endpoint, newest first
gh api "repos/$owner_repo/releases?per_page=10" --jq '.[].tag_name' \
| sed 's/^v//' \
| grep -E '^[0-9]+\.[0-9]+(\.[0-9]+)?(\.[0-9]+)?$' \
| head -1
# Fallback for repos without releases:
gh api "repos/$owner_repo/tags?per_page=100" --jq '.[].name' \
| sed 's/^v//' | grep -E '^[0-9]+...' | sort -V | tail -1
For non-GitHub hosts (rare): print "couldn't auto-check, location: <url>" and skip.
Group results into buckets (omit empty ones):
## Direct deps with updates available
### Auto-resolvable (upToNextMajor / upToNextMinor)
- <name>: <current> → <latest> (spec: <kind>)
### Pinned-exact (manual pbxproj edit)
- <name>: <current> → <latest> (spec: exactVersion)
### Pinned-revision (skill won't auto-bump)
- <name>: on commit <short-sha>. No auto-check.
## Transitive deps with updates available
(These move when their owning direct dep moves; not standalone bumpable.)
- <name>: <current> → <latest>
## Up-to-date
N direct + M transitive packages already on latest. (suppressed unless requested)
Prompt: "Want me to apply any of these? Pick specific packages, 'all auto-resolvable', or 'all exact-pinned with non-major bumps'."
Enter only on explicit opt-in. Examples that count: "Yes, apply", "Bump to X.Y.Z", "Bump everything auto-resolvable".
Restate exactly what will be bumped and to what version. Wait for confirmation unless the user already named specific packages + versions.
Use the Edit tool with enough context that the wrong package's version line can't match. Anchor on the repositoryURL line + the version line together:
old_string:
repositoryURL = "<package-url>";
requirement = {
kind = exactVersion;
version = <old-version>;
};
new_string:
repositoryURL = "<package-url>";
requirement = {
kind = exactVersion;
version = <new-version>;
};
After each edit, sanity-check with grep that the new value appears and the old doesn't where it shouldn't. If you see corrupt structure, stop and report.
upToNextMajor / upToNextMinor direct deps: NO pbxproj edit needed. The spec already permits the newer version.
rm <path>/Package.resolved
cd <project-root> && xcodebuild -resolvePackageDependencies \
-project <path-to-xcodeproj> \
-scheme <scheme-name>
The rm is mandatory — without it, -resolvePackageDependencies honours existing pins and won't pick up newer versions even with edited specs.
Package.resolved exists and is non-empty.project.pbxproj and Package.resolved.Trigger only when the user explicitly asks ("verify with a build", "make sure it still compiles", etc.). Otherwise skip.
After Phase 2 succeeds, Package.resolved reflects the new versions but the project hasn't compiled against them yet. A bumped dependency might have removed an API the app uses → resolve passes, build fails. Catching that here is better than at PR review or CI time.
Package.resolved before building:
cp <path>/Package.resolved /tmp/Package.resolved.post-resolve
xcodebuild -list or just look at the directory):
cd <ios-project-dir> && xcodebuild build \
-project <name>.xcodeproj \
-scheme <scheme> \
-destination 'platform=iOS Simulator,name=<simulator-name>' \
-quiet
-quiet to reduce noise; the failures still surface.gradle.properties, Fastfile, or test scripts for an existing pin, or fall back to a recent iPhone.diff /tmp/Package.resolved.post-resolve <path>/Package.resolved
Why: xcodebuild build can auto-trigger another resolve if it detects checkpoint inconsistencies (especially with binary-artifact deps like Intercom). Catch any post-build delta and call it out — it's part of the change set, not noise.Enter only after Phase 2 (and optionally Phase 4) succeeded.
ALWAYS look for .github/PULL_REQUEST_TEMPLATE.md (or .github/pull_request_template.md, docs/pull_request_template.md) in the repo before composing the body. Repos often have one with required sections — failing to use it produces a PR that doesn't match team conventions and may not auto-fill required fields.
# Common locations, in priority order:
find . -maxdepth 3 \
-iname "PULL_REQUEST_TEMPLATE.md" -o \
-iname "pull_request_template.md" 2>/dev/null \
| grep -E '\.github/|docs/' | head -1
If a template exists: map the skill's content into its sections (Description, How to Test, References, etc.). Never bypass it.
If no template exists: use the default structure (Step 2 below).
For each direct dep that was bumped:
https://github.com/<owner>/<repo>/compare/<from>...<to>. Use the package's location URL to get owner/repo.gh api "repos/$owner_repo/releases?per_page=20" \
--jq '.[] | select(.tag_name | test("^v?'<version>'$")) | .body'
Try both with and without the v prefix — tag schemes vary. If no release body exists, write "No release notes published upstream for this tag." and link the compare URL.<release-url>" footer.Identify transitive bumps:
Package.resolved diff that wasn't a direct pick → transitive.Package.swift). Best-effort guess; OK to leave parent unknown.If a PR template exists, parse its section headers and populate each by intent — don't assume specific header names. Common intents to map to whichever sections the template uses:
<details> per direct dep with release notes, plus a one-line note if the skill verified the build locally._n/a_ or other placeholders; empty template sections stay empty.- , NOT - [ ] checkboxes) of per-package spot-checks a reviewer must do manually (e.g. "spot-check <package> usage paths still work"). Do NOT include "CI green" as a list item — green CI is the implicit floor for being mergeable, not a manual test step. Do NOT include "build verified locally" rows here — that's context about what the skill did, belongs in the description section, not in instructions for the reviewer.If a template section doesn't fit any of those intents (some repos have custom sections like "Risk", "Rollback plan", "Migration notes"), leave it empty unless the change actually has content for it.
If no template exists, fall back to this default structure:
## SPM dependency updates
Direct bumps table → Transitive bumps table → <details> blocks per package
## Test plan
- [ ] CI green
- [ ] Spot-check <name> usage paths still work
- ...
## How this was generated
Skill: `check-spm-updates`. ...
git add <path>/project.pbxproj <path>/Package.resolved
git commit -m "chore: update SPM dependencies"
git push -u origin <branch-name>
gh pr create --base dev --title "chore: update SPM dependencies" --body "$(cat /tmp/spm-pr-body.md)"
--no-track-equivalent push semantics; -u sets correct tracking.chore: update SPM dependencies (single PR) or chore: bump <package1>, <package2> (when picks are small enough to enumerate in title).Show the user:
gh pr view --json url --jq '.url')X.Y.Z → A.B.C for every bump.project.pbxproj line range.xcodebuild task; can do directly, but flag if it's unlikely to produce changes.Package.swift. Useful but slow.upToNextMajor.Provides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.
npx claudepluginhub lastrada/claude --plugin ios-spm