Add SwiftLint, pre-commit hooks, and CI workflow - #12
Conversation
- .swiftlint.yml: opt into a handful of stricter rules (force unwrapping, implicitly unwrapped optionals, empty_count, etc.), cap line length, and whitelist the small set of short/underscored identifiers already in use rather than loosening the rule globally. - .pre-commit-config.yaml: local SwiftLint hook scoped to staged Swift files via the pre-commit framework. - .github/workflows/ci.yml: build, test, and lint on macOS on push/PR. - Autocorrect the resulting mechanical violations (trailing commas, trailing newlines, colon spacing) across existing files; everything else surfaces as an advisory warning rather than blocking. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| uses: actions/cache@v4 | ||
| with: | ||
| path: .build | ||
| key: ${{ runner.os }}-spm-${{ hashFiles('Package.resolved') }} |
There was a problem hiding this comment.
hashFiles('Package.resolved') always hashes an empty match here because Package.resolved is not committed, so the cache key stays the same across every PR even when Package.swift or the floating branch: main deps drift. Caching the whole .build tree on top of that can also restore stale build products across PRs or toolchain changes. Would it make sense to key off Package.swift (or commit Package.resolved) and cache SwiftPM’s package caches instead of the full .build directory?
There was a problem hiding this comment.
Good catch. Fixed: keyed off Package.swift instead, and narrowed the cached paths to ~/Library/Caches/org.swift.swiftpm + .build/checkouts + .build/repositories (fetched sources only) rather than the whole .build tree, so a restore just saves the floating-branch deps an incremental git fetch instead of a full clone — it can no longer serve stale compiled objects across dependency or toolchain drift.
| "Index.noindex", | ||
| "ModuleCache.noindex", | ||
| "CompilationCache.noindex", | ||
| "CompilationCache.noindex" |
There was a problem hiding this comment.
SwiftLint’s default trailing_comma rule forbids trailing commas (mandatory_comma: false), which is why autocorrect dropped them on multiline arrays like this. A lot of Swift projects flip that to mandatory_comma: true so list edits stay quieter in diffs. Do we want that for this repo?
There was a problem hiding this comment.
Agreed, flipped to mandatory_comma: true and re-ran autocorrect to put the trailing commas back.
| - uses: actions/checkout@v4 | ||
|
|
||
| - name: Install SwiftLint | ||
| run: brew install swiftlint |
There was a problem hiding this comment.
brew install swiftlint tracks Homebrew’s latest formula, so an upstream rule change can fail this job with no repo diff. Want to pin a SwiftLint version here so the lint gate stays reproducible?
There was a problem hiding this comment.
Pinned to 0.63.3 (same version installed locally) via the portable_swiftlint.zip release asset instead of brew install, so the lint gate can't go red from an upstream rule change with no diff in this repo.
- Cache key off Package.swift instead of the uncommitted Package.resolved (which always hashed to an empty match), and scope the cache to fetched dependency sources rather than the whole .build tree so it can't silently restore stale build products across dependency or toolchain changes. - Pin the lint job's SwiftLint to an exact version (0.63.3, matching what contributors install locally) via the portable release binary, instead of `brew install swiftlint` tracking whatever Homebrew's formula currently points at. - Flip trailing_comma to mandatory_comma: true per review — trailing commas on multiline collection literals keep future list edits to single-line diffs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
.swiftlint.yml): opts into a handful of stricter rules (force unwrapping, implicitly unwrapped optionals, empty_count, etc.), caps line length at 120/200 (warning/error), and whitelists the small set of short/underscored identifiers already used in this codebase (s,t,fm,op, and thevXX_Yversion-literal test names) rather than loosening those checks globally..pre-commit-config.yaml): a local hook that runsswiftlint lintagainst staged Swift files via the pre-commit framework. Documented in the README under Development..github/workflows/ci.yml): two parallel macOS jobs on push/PR — build+test (Xcode pinned viasetup-xcode, SPM build cache) and lint (swiftlint lint).swiftlint --fixfor the mechanical violations this surfaced (trailing commas, trailing newlines, one colon-spacing fix) across existing files, plus onecount > 0→!isEmptycleanup. Everything else that remains is an advisory warning, not blocking — pre-existing structural patterns (function length/param count, an implicitly-unwrapped optional in the FSEvents callback context) are left for their own future PRs rather than a drive-by rewrite here.Test plan
swift buildandswift testpass (40 tests)swiftlint lintexits 0pre-commit install+ smoke-tested that the hook actually blocks a commit with a real violation, then verified a clean commit passesci.ymlworkflow runs green on GitHub (first run once this PR is opened)🤖 Generated with Claude Code