Building from source
Requirements, repository gates, safe checks and the release path.
This page follows docs/BUILDING.md in the repository.
Requirements
- macOS 26 Tahoe or later
- Xcode 26
- XcodeGen 2.46.0
- Python 3 for the deterministic fixture tests
- Lefthook 2.1.12, Qlty 0.618.0, actionlint 1.7.12 and zizmor 1.29.0 for the repository gates
Install the Homebrew tools and Qlty's signed release CLI. Download the Qlty installer and read it before running it, rather than piping it into a shell:
brew install xcodegen lefthook actionlint zizmor
curl -fsSL https://qlty.sh -o /tmp/qlty-install.sh
less /tmp/qlty-install.sh
sh /tmp/qlty-install.shHomebrew installs whatever is current, which isn't necessarily the version the gate expects. Check what you got:
./scripts/check-tool-versions.shCI asserts xcodegen --version exactly and installs Qlty from a pinned commit, so a local mismatch shows up as a disagreement with CI rather than a silent pass.
Generate and open the project
xcodegen generate
git diff --exit-code -- IdleScreen.xcodeprojOpen IdleScreen.xcodeproj and run the IdleScreenApp scheme for normal local development. A development build can't use the camera or agent hooks. See Troubleshooting.
Gates
lefthook install
./scripts/check-swift-format.sh changed
./scripts/qlty-check-gate.sh all
./scripts/verify-workflows.sh
./scripts/test-modern-schemes.shscripts/verify-workflows.sh runs actionlint and zizmor when present and reports a skip when either is unavailable, because CI is the authoritative workflow-security gate.
Safe deterministic checks
These don't install the app, register a helper or screen saver, request camera access, open System Settings or change power state:
./scripts/test-repository-layout.sh
./scripts/test-project-contracts.sh
python3 ./scripts/test_xccov_to_lcov.py
./scripts/test-companion-compile-gate.sh
python3 ./scripts/test_performance_r1_report.py
./scripts/test-run-performance-r1.sh
./scripts/test-camera-gate-c8-soak-planner.py
./scripts/test-controlled-physical-soak.py
./scripts/test-idlescreenctl-runtime.sh
./scripts/test-camera-agent-product-fixtures.sh
./scripts/test-synthetic-gate-product-fixtures.sh
./scripts/test-release-archive-provenance-fixtures.sh
./scripts/test-r1-release-candidate-fixtures.sh
./scripts/test-public-release-contract.shCI also runs the modern Xcode unit and integration schemes with Swift warnings as errors, CodeQL, workflow security, Qlty and Codecov coverage.
Physical checks
Scripts that install products, register services, open a camera, start the screen saver, lock the session, change displays or alter system state are separately consent-gated, and refuse by default. Read the script's refusal message, confirm the exact candidate and scope, and authorize only the action being performed. Never copy an opt-in from a fixture or another session.
Release candidates
scripts/build-r1-release-candidate.sh is the Developer ID distribution authority. A real run needs a clean committed source tree, a nonexistent absolute output directory outside the repository, the Developer ID Application identity SHA-1, separate distribution profiles for the app, extension and camera helper, an existing notarytool Keychain profile, and IDLESCREEN_ALLOW_REAL_DISTRIBUTION=YES.
The builder archives the app, embeds release identity, signs nested code from the inside out, creates a DMG, notarizes and staples it, and has Gatekeeper assess the DMG and the mounted app. It never creates or stores credentials and never signs after notarization.
Replay a finished candidate with scripts/verify-r1-release-candidate.sh, generate the Homebrew cask with scripts/generate-homebrew-cask.sh, and generate the SPDX 2.3 SBOM with scripts/generate-release-sbom.sh. Attach the SBOM to the GitHub release under exactly idlescreen-<version>-build<build>.spdx.json; the published download URL is derived from that name.
Ordinary changes target the protected dev/v0.1 branch. main advances only through a reviewed promotion pull request and gets the GA tag immediately.
Repository hygiene
Local roadmaps, raw test evidence, design exports and audit reports belong in the ignored .planning/ directory. Public documentation belongs under docs/.